Files
settled-reach/pql-requirements.md
T
jpmschweitzerandClaude Opus 4.8 53064b051e refactor(meta): gate phases on the cascade hierarchy, not labels
Replaces the labels-as-milestones pattern (carried over from the retired SQLite
milestone subsystem) with pql's native initiative/epic hierarchy, which already
modelled this: initiative T-745 (Development Cascade) holds the six phases as epics.
A ticket is in a phase by being parented under that phase epic — self-maintaining,
no label to apply or forget.

The `phase:4` label had already drifted (66 tickets under the Phase-4 epic T-750 but
only 42 labelled). Fixes:
- T-750 (Phase 4) -> in_progress to mark the active phase (sequential per D-166).
- Re-parented the 2 strays (T-974 Atlas-to-tile epic, T-1008 economy task) under T-750.
- Dropped all 42 phase:4 labels (tombstoned in the changelog).
- /whats-next, /ticket, ticket-cli.md, CLAUDE.md now gate on
  `pql ticket list --under <active-phase-epic> --unblocked` instead of `--label phase:4`.
- Fixed the label-action verb in the docs: pql uses `add|rm`, not `add|remove`.
- pql-requirements #9 flipped: no milestone entity / labels-as-milestones needed — the
  initiative/epic tree is the answer.

Verified: `pql plan rebuild` reconstructs the mutations; `--under T-750 --unblocked`
returns the active phase's ready work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:48:21 +02:00

10 KiB

title, description, type, status, created, updated, tags
title description type status created updated tags
pql Requirements — gaps surfaced by the Settled Reach migration Feature gaps and friction points found while migrating a 1013-ticket + 358-decision project onto pql, for the pql team requirements draft 2026-06-06 2026-06-06
pql
requirements
feedback

pql Requirements — gaps for the pql team

Compiled while planning a migration of The Settled Reach (a Godot+Rust game) off a SQLite-backed ticket CLI + markdown-decisions-sync onto pql. Scale: 1013 tickets, 481 dependency edges, 48 labels, 16 history rows; 358 decision records (237 D / 109 Q / 12 R) with 1040 cross-refs; a ~4385-file markdown vault (decisions, docs, 22 workshops, ~3500 wiki pages).

pql is a strong fit and most of our system maps cleanly — unsurprisingly, since pql was developed off the lessons of this repo's planning structures (the SQLite ticket+decision schema, the decisions/ domain split, the kanban/whats-next flow). The enums, the DQR tree, and the kanban model match almost exactly; pql's deliberate departures (TEXT ids over our integer ids, a git-tracked text changelog over our binary-DB-plus-backup ritual, and no milestone entity) are the productized lessons. So the items below are best read as feedback to a descendant, not gaps in a foreign tool — and a few are deliberate simplifications we should adopt rather than ask pql to re-add. Each lists why, a suggested shape, and a priority; viable workarounds are noted.


1. Seeded / explicit ticket IDs + id-counter advance — RESOLVED (non-issue)

Why it looked critical. Our 1013 tickets have integer ids referenced as #440 in thousands of immutable git commits, in decision "Ticket: #N" lines, and by PR automation. We need the pql id to preserve the number — T-440 ≡ old #440. The fear was that, since pql ticket new has no --id flag and auto-assigns T-N sequentially, a post-seed native create would mint T-1 and collide.

Why it's actually fine (verified against pql 1.6.2). Bulk seeding goes through direct-INSERT into pql.db + pql plan export, not ticket new. And pql's id counter is derived from max(id) — exactly the behaviour our own integer-id CLI had. After seeding T-1…T-1021, the next pql ticket new minted T-1022, not T-1. No collision, no recycling, no held creation. The --id gap only bites the (rare) case of hand-creating a specific id via the CLI — irrelevant to a bulk numbered migration.

Optional nicety (low priority). A pql ticket new --id T-1022 flag would still be convenient for the occasional manual back-fill, but it is not required for a clean migration.


2. Duplicate-ID detection in pql decisions sync / validateHIGH

Why. Our markdown had a genuine integrity bug: D-035 defined in two domain files, R-011 defined three times. pql decisions sync silently last-writer-wins-collapsed them (reported broken: 0), so the duplication was invisible. Our legacy Python check-dupes errors on it. A duplicate canonical-ID is almost always a data bug, not an intended merge.

Suggested shape. pql decisions validate (and sync --strict) should warn or error on duplicate D/Q/R ids across the vault, reporting file:line for each occurrence.

Workaround. Keep our Python check-dupes wired into pre-commit alongside pql — extra tooling we'd like to retire.


3. core.hooksPath awareness in pql initHIGH

Why. Our repo sets core.hooksPath = .config/hooks (so hooks are version-controlled). pql init installs its pre-commit/post-merge/post-checkout hooks into .git/hooks/, which git ignores when core.hooksPath is set. pql's changelog-staging + replay hooks would be silently dead — the whole durable-versioning model depends on them. This is a quiet, dangerous failure (data appears to work, but .pql/changelog/ is never staged).

Suggested shape. pql init/pql doctor should detect core.hooksPath and either install into the configured dir or emit a loud warning with the manual fold-in snippet.

Workaround. Manually fold pql's hook logic into .config/hooks/* (we will), but pql doctor should flag the mismatch so others don't get bitten.


4. Document the changelog seed format + hash/canonical_version algorithm — HIGH

Why. pql plan import --legacy is documented as retired; the real durable artifact is .pql/changelog/<table>/<YYYY-MM>.sql. To migrate 1013 tickets we must emit those SQL rows directly. We reverse-engineered the UPSERT shape, but the per-row hash and canonical_version columns + the LWW guard (WHERE excluded.updated_at > … OR (… AND excluded.hash > …)) are opaque. If our seed hashes don't match pql's scheme, later native edits still win by updated_at (so correctness holds), but the LWW tie-break becomes unpredictable.

Suggested shape. Document (a) the canonical changelog row format per table, (b) the hash input + algorithm, (c) how canonical_version increments — OR provide a supported one-shot pql plan seed <json|csv> import that computes them.

Workaround. Emit our own stable per-row hash; rely on updated_at for correctness. Works, but under-specified.


Why. Across ~4385 vault files (esp. ~3500 generated wiki pages), decision/ticket references are mostly bare text (D-117, #440) rather than markdown/wikilinks. pql search finds them, but pql backlinks/related only follow real links — so "what references D-117?" is incomplete. Rewriting 3500+ generated files to add links is impractical (they're regenerated from a pipeline).

Suggested shape. Optional reference recognizers (configurable regex → entity, e.g. D-\d+→decision, T-\d+→ticket) so backlinks/related treat bare-text mentions as edges without requiring link syntax in source.

Workaround. Adopt a link convention in authored files only; accept that generated files are search-only.

Related gap — frontmatter list-membership in the DSL. Our Phase-5 provenance puts the decisions each workshop touches in a decision_refs: [D-010, …] frontmatter list. pql indexes the list and SELECT fm.decision_refs round-trips it, but we found no DSL operator that filters by membership: fm.decision_refs ~ 'D-010' and … contains … are lex/parse errors, and 'D-010' in fm.decision_refs parses but matches nothing. So "which workshops touch D-010?" needs a SELECT + client-side filter or a pql search. A documented list-membership operator (or making in work against frontmatter arrays) would close this.


6. WIP-limit enforcement (advisory) on ticket status … in_progressMEDIUM

Why. Our kanban flow enforces a WIP limit (max 5 in_progress); our activation step relied on the CLI warning when exceeded. pql has no forced state machine / WIP concept.

Suggested shape. An optional config (wip_limit: 5) that makes pql ticket status <id> in_progress emit an advisory warning (not a hard block) when the in_progress count would exceed it.

Workaround. Compute pql ticket list --status in_progress | count in our skill and warn there.


7. Fan-out / downstream-unblock count on ticket showMEDIUM

Why. Our batch-selection ranks ready tickets partly by fan-out — how many downstream tickets a ticket unblocks. We currently compute it from the dependency graph. pql has --unblocked (great — it replaced our manual blocker walk) but no "how many does this unblock" signal.

Suggested shape. A blocks_count / unblocks_count field on pql ticket show/list, or a pql ticket list --orders-by fanout.

Workaround. Compute client-side from ticket show --with-context dep data.


8. Multi-team / array team field — LOW

Why. One ticket carries a comma-separated team (client,server). pql treats team as a single string. (1 of 1013 — genuinely low.)

Suggested shape. Accept an array/comma team, or document the single-team constraint.

Workaround. Pick the primary team for the one affected ticket.


9. No milestone entity needed — the ticket hierarchy IS the milestone — RESOLVED (no ask)

Why it looked like a gap. Our old schema had a milestones subsystem (milestones, ticket_milestones, milestone_deps, cascade_phase) — but it was vestigial (2 milestones, milestone_deps empty, 42/1013 linked, cascade ordering never exercised). The migration first mapped the active milestone to a label (phase:4).

What we landed on — and it's better. The labels-as-milestones pattern immediately drifted (66 tickets lived under the Phase-4 epic but only 42 carried the label). The fix wasn't a milestone entity or a label convention: it was to use pql's native initiative/epic hierarchy, which already modelled this. The repo has an initiative T-745 (Development Cascade) with the six phases as epics under it; a ticket belongs to a phase by being parented under that phase epic (pql ticket list --under <phase-epic>), and the active phase is the in_progress phase epic. This is self-maintaining — parent a ticket and it's in the phase; no label to apply, forget, or let rot.

Net for the pql team: dropping the milestone entity was the right call — don't add one back, and don't bless labels-as-milestones either. The initiative→epic→story→task tree already expresses phase gating cleanly. The only nice-to-have is a documented "active node" convention (status in_progress on a grouping ticket) and maybe a --under done/total rollup. Not blocking; no entity needed.


Summary

# Requirement Priority Have workaround?
1 Seeded/explicit ticket id (--id flag) Critical Resolved N/A — direct-INSERT + max-id counter already works
2 Duplicate-ID detection in sync/validate High Yes (keep our checker)
3 core.hooksPath awareness High Yes (manual fold-in)
4 Documented changelog seed format + hash algo High Partial
5 Bare-text ref detection for backlinks Medium Partial (authored files only)
6 WIP-limit advisory Medium Yes (skill-side)
7 Fan-out / unblocks count Medium Yes (client-side)
8 Multi-team field Low Yes
9 Milestone entity / labels-as-milestones Low Resolved N/A — use the initiative/epic hierarchy