Files
settled-reach/pql-requirements.md
T
jpmschweitzerandClaude Opus 4.8 07e5ba005d docs(meta): pql migration plan + requirements for the pql team
Deep analysis of migrating decisions + ticketing from the SQLite CLI to pql:
- pql-migration.md: phased branch-only plan (decisions→DQR tree, ticket changelog
  seed with T-N≡#N, #N→T-N find-replace, big-bang consumer cutover, docs/workshops/
  wiki fold-in, SQLite retirement) + verification gate + benefits.
- pql-requirements.md: 9 surfaced gaps for the pql team (critical: seeded ticket
  ids + counter-advance; high: dup-id detection, core.hooksPath awareness, changelog
  seed-format docs).

Key finding: milestones are vestigial (milestone_deps empty, 42/1013 tickets
linked) → mapped to a label, dropped as an entity.

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

172 lines
8.4 KiB
Markdown

---
title: "pql Requirements — gaps surfaced by the Settled Reach migration"
description: "Feature gaps and friction points found while migrating a 1013-ticket + 358-decision project onto pql, for the pql team"
type: requirements
status: draft
created: 2026-06-06
updated: 2026-06-06
tags: [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. The items below are the gaps we hit.
Each lists **why we need it**, a **suggested shape**, and a **priority**. Where we have a viable
workaround, it's noted — but the workaround is friction we'd rather not ship.
---
## 1. Seeded / explicit ticket IDs + id-counter advance — **CRITICAL**
**Why.** Our 1013 tickets have integer ids referenced as `#440` in thousands of immutable git
commits, in decision "Ticket: #N" lines, and by our PR automation. We need the pql id to preserve
the number — `T-440 ≡ old #440` — so the mapping stays a trivial bijection. pql's `id` is a TEXT
column, so we can seed `'T-440'` directly into the changelog. **But `pql ticket new` auto-assigns
`T-N` sequentially starting at `T-1`** — after seeding `T-1…T-1013`, the next native create would
mint `T-1` and collide.
**Suggested shape.**
- `pql ticket new --id T-1014` (accept an explicit id), AND/OR
- on `pql plan rebuild`/import, **advance the internal id counter past the max existing id** so
native creates never recycle a seeded id.
**Workaround.** None safe — we'd have to hold all native ticket creation until this lands, or
hand-manage a sentinel. This is the one true blocker for a clean numbered migration.
---
## 2. Duplicate-ID detection in `pql decisions sync` / `validate` — **HIGH**
**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 init` — **HIGH**
**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.
---
## 5. Bare-text reference detection / auto-link for `D-NNN` / `T-NNN` — **MEDIUM**
**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.
---
## 6. WIP-limit enforcement (advisory) on `ticket status … in_progress` — **MEDIUM**
**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 show` — **MEDIUM**
**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. Bless a "labels-as-milestones" pattern (or an optional milestone entity) — **LOW / nice-to-have**
**Why.** Our schema has a milestones subsystem (milestones, ticket_milestones, milestone_deps,
cascade_phase) — but in practice it's **vestigial**: 2 milestones (1 active, 1 completed-with-0-tickets),
`milestone_deps` empty, 42/1013 tickets linked, cascade ordering never used. We're mapping the one
active milestone to a **label** (`phase:4`) and dropping the rest. This works today, so it is **not
blocking**.
**Suggested shape.** Either bless **labels-as-milestones** as the documented pattern (with helper
queries / rollups), or offer an optional lightweight milestone entity (M:N ticket links, status,
ordering, done/total rollups) for projects that actually use phase gating. We'd only need the latter
if we later adopt real cross-phase dependency gating.
---
## Summary
| # | Requirement | Priority | Have workaround? |
|---|-------------|----------|------------------|
| 1 | Seeded/explicit ticket id + counter-advance | **Critical** | No (real blocker) |
| 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 | Labels-as-milestones blessing / optional entity | Low | Yes (labels) |