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>
This commit is contained in:
2026-06-06 21:48:21 +02:00
co-authored by Claude Opus 4.8
parent 064382c0cc
commit 53064b051e
8 changed files with 102 additions and 37 deletions
+12 -8
View File
@@ -45,9 +45,9 @@ pql ticket show T-440 --with-blockers # what blocks this
pql ticket block T-7 --by T-440 # T-440 blocks T-7
pql ticket unblock T-7 --from T-440 # remove that edge
# Labels (also used as milestones — see below)
pql ticket label T-440 add phase:4
pql ticket label T-440 remove phase:4
# Labels (action is add | rm)
pql ticket label T-440 add needs-refinement
pql ticket label T-440 rm needs-refinement
# Refinement (tickets with no description are "unrefined")
pql ticket refine list
@@ -80,14 +80,18 @@ pql decisions sync # parse governance/*.md -> pql.db
pql decisions validate # malformed-record gate (pre-commit)
```
## Milestones → labels
## Phases → the cascade hierarchy (no milestone entity)
pql has **no milestone entity** (the legacy milestone subsystem was vestigial). Phase
gating is expressed with **labels**: the active phase is `phase:4`.
pql has **no milestone entity** (the legacy one was vestigial). Phase gating is the
**ticket hierarchy**: the 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 active phase is the phase epic with status `in_progress`
(sequential per D-166 → the lowest-numbered non-`done` phase epic; currently `T-750`, Phase 4).
```bash
pql ticket list --label phase:4 --unblocked # the kanban "what's ready in this phase"
pql ticket label T-950 add phase:4 # put a ticket in the phase
pql ticket list --under T-750 --unblocked # the kanban "what's ready in this phase"
pql ticket setparent T-990 T-750 # put a ticket in the phase (re-parent)
pql ticket show T-745 --tree # the whole cascade
```
## Key rules
+8 -6
View File
@@ -40,7 +40,7 @@ pql ticket assign T-440 dudley
pql ticket team T-440 server # comma teams allowed: server,client
pql ticket setparent T-9 T-2
pql ticket append T-440 "extra context" # --file PATH / --stdin
pql ticket label T-440 add|remove <label>
pql ticket label T-440 add|rm <label>
```
### Dependencies
@@ -50,11 +50,13 @@ pql ticket block T-7 --by T-440 # T-440 blocks T-7
pql ticket unblock T-7 --from T-440
```
### Phases (labels, not a milestone entity)
### Phases (the cascade hierarchy, not labels or a milestone entity)
```bash
pql ticket list --label phase:4 --unblocked # ready work in the active phase
pql ticket label T-950 add phase:4
pql ticket list --under T-750 --unblocked # ready work in the active phase (Phase 4)
pql ticket setparent T-990 T-750 # put a ticket in the phase (re-parent)
pql ticket show T-745 --tree # the cascade: initiative -> 6 phase epics
```
The active phase is the `in_progress` phase epic under T-745 (sequential per D-166).
### Refinement & planning
```bash
@@ -72,6 +74,6 @@ pql plan status # decision + ticket dashboard / counts
2. Initiatives break into **epics** (major work areas)
3. Epics break into **stories** (user-facing deliverables)
4. Stories break into **tasks** (concrete work items)
5. **Phase gating** is expressed with labels (`phase:4`), not a milestone entity
6. `/whats-next` selects the next batch via `pql ticket list --label phase:4 --unblocked`
5. **Phase gating** is the cascade hierarchy (initiative T-745 → six phase epics), not labels/milestones
6. `/whats-next` selects the next batch via `pql ticket list --under <active-phase-epic> --unblocked`
7. Mutations flow to the git-tracked `.pql/changelog/`; the pre-commit hook exports + stages it
+15 -5
View File
@@ -19,13 +19,23 @@ batch selection → refinement review → batch activation.
### 1a. List the ready work in the current phase
pql has **no milestone entity** — phase gating is a label, and the active phase is
`phase:4`. `--unblocked` does the dependency walk for you (returns only tickets whose
blockers are all `done`/`cancelled`), collapsing the old milestone + per-ticket deps
queries into one:
Phase gating is the **cascade hierarchy**, not a label or milestone entity. The
initiative `T-745` (Development Cascade) holds the six phases as epics under it; a
ticket belongs to a phase by being parented under that phase epic (self-maintaining —
no label to apply or forget). Find the **active phase epic** (status `in_progress`
under T-745; the cascade is strictly sequential per D-166, so equivalently the
lowest-numbered non-`done` phase epic):
```bash
pql ticket list --label phase:4 --unblocked
pql ticket list --under T-745 2>/dev/null | \
python3 -c "import json,sys;[print(t['id']) for t in json.load(sys.stdin) if t['type']=='epic' and t['status']=='in_progress']"
```
Then list its ready subtree — `--under` gathers the whole phase, `--unblocked` does the
dependency walk (only tickets whose blockers are all `done`/`cancelled`):
```bash
pql ticket list --under <active-phase-epic> --unblocked # e.g. T-750 for Phase 4
```
If this is empty, the phase is either complete or fully in progress — tell the user and stop.
@@ -0,0 +1,3 @@
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-750', 'status', 'backlog', 'in_progress', NULL, '2026-06-06 19:39:58', '2026-06-06 19:39:58', '2026-06-06 19:39:58', NULL, '04918b62c238bdf9673ce3af1d8d2014', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-974', 'parent_id', NULL, 'T-750', NULL, '2026-06-06 19:39:58', '2026-06-06 19:39:58', '2026-06-06 19:39:58', NULL, 'a4fda37d5b5564819a488b95c2267efa', 1) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1008', 'parent_id', NULL, 'T-750', NULL, '2026-06-06 19:39:58', '2026-06-06 19:39:58', '2026-06-06 19:39:58', NULL, 'bb365ea061397033b1861f4951b19d27', 1) ON CONFLICT(hash) DO NOTHING;
+42
View File
@@ -88,3 +88,45 @@ INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at,
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1015', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 00:00:00', NULL, NULL, 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1016', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 00:00:00', NULL, NULL, 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1017', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 00:00:00', NULL, NULL, 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-956', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '0a81b6e5806ecb476812d29f850e3a39', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1017', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '11cb115ab97189b6dc6b942de04f11ce', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-965', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '1225400996249bf0ba3ca59e8f39cb93', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-951', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '139afd1a281e87fc5cf27ff3c4629462', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-980', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '1e97030146e894ece5109f12075bf18e', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-971', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '1fff76028a7f100262a01a7543ad66d2', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1016', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '25b3617624742ffdba72fb6b5504cb1e', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-953', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '25fc6dadb86a7c42ddbfa033571ea5ac', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-967', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '26d2f483f4e4b66c26d798e2baebdffc', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-959', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '28f07404e47f53060244afb5d5847276', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-957', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '2d138241aad0d1bd5f57b28929813da6', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-974', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '34d3d13dbe05f3036076f14247809799', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-958', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '3509ebf05d2945cb04b698b9a1e809e4', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-969', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '388411b045cc139c512538eff0ed0a71', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-968', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '393be9e5d9a75093fecd867ce7793f25', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-960', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '404288d8e662fc6c4688e6383020c3de', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-979', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '407cad028ada0ff61c14ef86cdd7216e', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1012', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '4394741e5b12381dc491d5235136844b', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1005', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '59a1b5b6433e663b188801e364d5b5c9', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-977', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '5e995a9f84567f82964b84fd9454d494', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-964', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '6949acb6fb2638f614618975a2b56037', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-981', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '6aacc8af7350ac302684ede015d9ea02', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1013', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '6d3b6e10e57124d793dbc83473303064', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-955', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '849a5cd9d42d50011f057502ffdad155', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-976', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '86d252ac4ded723f73300537b1da8a3b', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-954', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '87a73b8f40bfad7869959bde9b0bdc82', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-963', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '8a346f989b7ff3c6148e0cb1e180b636', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1008', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '8af6fdb8e3695624d5554c5ed79b33a2', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1011', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '9a11bf2dfbe0191e08f337e40ef3aec2', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-978', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', '9e0f07e69c5152f8912f72894a34adb4', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1015', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'a65f8c1f7ed30ba247ed21cc2d7b6d55', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-950', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'a6a292a3977b9e2baf2b793c772caffc', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-970', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'a8857d95c2872255e86952bd939f1346', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-952', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'c3e22ff8aaf9b1193aa1483a4119d262', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-961', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'ce4dc53c41cda6f1b1e4960c1bd968cd', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1010', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'da83f131846524421f5a89f7a8d9ad76', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-972', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'e186b28925d887d2533c6fe84ab8e7d3', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1006', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'e18fa974d409c44770ccc9b9ca16a321', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1014', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'e1c32feea25737986e4f42e7ca70bf82', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-962', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'e3a41a4138b9edb654fc65c2da7282c6', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1009', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'ebd71bb24918d9c274508ea29889896e', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
INSERT INTO ticket_labels (ticket_id, label, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-966', 'phase:4', '2026-06-06 00:00:00', '2026-06-06 19:40:20', '2026-06-06 19:40:20', 'f4d19ea7b2fb4e6836ebbbe7c93e65c2', 1) ON CONFLICT(ticket_id, label) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_labels.updated_at OR (excluded.updated_at = ticket_labels.updated_at AND excluded.hash > ticket_labels.hash);
+3
View File
@@ -160,3 +160,6 @@ Refinement (2026-05-26): READY behind 1006. Type defs move to 1006; this story o
---
Refinement (2026-06-05, /whats-next): gaps resolved. GAP1 zone_type_id selection — AUTHORED a (ZoningType x economic_role x setting) -> candidate-ZoneTypeId-slice table for the PLANETARY cascade; setting is a TWEAKER (Maritime->port_maritime/fishing/aquaculture/extraction_platform; Agricultural->rural bias; Wilderness->wilderness_frontier/dispersed), NOT a surface/station switch. Station-only ids (residential_station, extraction_space, port_space, rural_orbital) EXCLUDED — separate cascade (see Q-109). Every cell non-empty; unknown role->ZoningType default; seed-pick from slice. Record as D-229 amendment during impl. GAP2 BuildingEntryClass low-prosperity->BreachOnly uses D-217 bands (<0.23 BreachOnly, 0.23-0.43 Restricted, etc.) + Commission/Organic credential fork. GAP3 era = D-229 (founding_age_years + prosperity_baseline + seed); DROP the ungrounded round-3 distance-to-origin phrase. GAP4 remove BlockSkeleton.era String stub (era lives on BuildingPropertyTag); fix construction sites. GAP5 FloorExtent = D-220 density-class midpoints + seed-jitter within range. GAP6 BuildingPropertyTag.doors OUT OF SCOPE -> belongs to #979 (DoorSpec/D-231); stub Vec::new() here. Confirmed present: #1006 type defs (generator.rs), 31 zone-type RON files (server/content/global/zone-types/), district_mix.rs three-component mix, GenCompletion::SkeletonGenerated already carries QuarterWorldState (gap closed prior merge). New seam Q-109 filed (cascade generation-source dispatch).', 'done', 'high', NULL, 'server', 'D-220', '2026-05-22 17:42:34', '2026-06-06 09:01:11', NULL, NULL, 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-750', 'epic', 'T-745', 'Phase 4: World generation (tile/chunk/block) — deterministic multilayer seed-to-tile cascade + per-layer Atlas maps + asset pipeline', 'Deliverable: Walkable generated world + asset creation catalog with production status. Automated tile/chunk/block generation (NPC layer ignored). Parallel asset pipeline: rural walls, barns, station walls, auto doors, cars, lamp posts, TVs, billboards etc. Depends on Phases 1-4 (world structure + rendering pipeline exist).', 'in_progress', 'high', NULL, NULL, NULL, '2026-03-25 13:30:00', '2026-06-06 19:39:58', NULL, '97cc3a980ac3afd4ef733e19002297cc', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-974', 'epic', 'T-750', 'Atlas-to-tile derivation model', 'Foundational world-derivation model from the atlas-derivation workshop. D-227 (deterministic-rebuild world model: pure function of seed+atlas, derived on demand + cached, only tile mutators persisted, fully volumetric voxel/subvoxel, vertical extent physical not floor-count, variable-height floors) + D-228 (composite tile schema: orthogonal axes TerrainMaterial/FloorMaterial/Vegetation/Water/elevation, derived shape/tactical-form, region-level morphology zones, cohesion matrix, dynamic clock-bound region seasonal state). Most implementation lands as the cascade reaches the tile layers (Phase 4+). Open design tracked in Q-100 (biome authority), Q-101 (refinement contract + body-class river density + ocean mask), Q-102 (cohesion-matrix algorithm), Q-103 (mutator op schema), Q-104 (floor-to-voxel-z mapping), Q-105 (region seasonal/clock state: water/snow/weather/crops).', 'backlog', 'high', NULL, 'server', 'D-228', '2026-05-25 10:51:49', '2026-06-06 19:39:58', NULL, '1dd474c79c4f59cd69166e503f66d3a9', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-1008', 'task', 'T-750', 'Author economy label for Glimkop (GJ1245Bb) — NULL economic_role in placeholder system', 'Glimkop (GJ1245Bb, pop 4200, system PLACEHOLDER_087) has economic_role NULL despite inhabited>0 — surfaced during #1001 regen-verify. Pre-existing content gap, not a normalization bug. Either author a canonical economic_role in the wiki source, or confirm PLACEHOLDER_* systems are excluded from the Phase-4 generation cascade (in which case close as wontfix).', 'backlog', 'low', NULL, 'server', NULL, '2026-05-31 09:27:38', '2026-06-06 19:39:58', NULL, 'bef2efb8489c51072d520adc58ce4be8', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
+2 -2
View File
@@ -51,7 +51,7 @@ Development follows a strict cascade. Each phase has a concrete deliverable. **D
## Work Modes
### Kanban mode (default)
Work flows continuously through the current phase. `/whats-next` selects the next epic-sized batch from the dependency graph (`pql ticket list --label phase:4 --unblocked`), refines ticket context via parallel Si agents, then activates a worktree. `/pr-process` (from worktree) and `/pr-review` (from main) handle the review cycle through Gitea PR comments. Phase gating is expressed with **labels** (`phase:4`), not a milestone entity — a ticket can carry several labels.
Work flows continuously through the current phase. `/whats-next` selects the next epic-sized batch from the dependency graph (`pql ticket list --under <active-phase-epic> --unblocked`), refines ticket context via parallel Si agents, then activates a worktree. `/pr-process` (from worktree) and `/pr-review` (from main) handle the review cycle through Gitea PR comments. Phase gating is the **cascade hierarchy** (initiative `T-745` → six phase epics), not labels or a milestone entity — a ticket is in a phase by being parented under that phase epic. The active phase is the `in_progress` phase epic (sequential per D-166; currently `T-750`, Phase 4).
### Pair session
Human and Claude work together interactively on a single task. No background agents, no autonomous work. Used for load-bearing architecture changes where the human needs to make judgment calls as the work progresses — not approve a finished result.
@@ -99,7 +99,7 @@ binary-DB backup ritual). Decisions are markdown-sourced under
| Decisions | `pql decisions show`, `list`, `claim`, `validate`, `sync` | `.claude/rules/ticket-cli.md` |
| Vault query | `pql query`, `search`, `backlinks`, `related`, `context` | — |
Phase gating uses **labels** (`phase:4`), not a milestone entity: `pql ticket list --label phase:4 --unblocked`.
Phase gating is the **cascade hierarchy** (initiative `T-745` → six phase epics), not a milestone entity: `pql ticket list --under <active-phase-epic> --unblocked` (active phase = the `in_progress` phase epic, currently `T-750`).
### Testing preferences
+17 -16
View File
@@ -155,24 +155,25 @@ string. (1 of 1013 — genuinely low.)
---
## 9. Bless a "labels-as-milestones" pattern (or an optional milestone entity) — **LOW / nice-to-have**
## 9. No milestone entity needed — the ticket hierarchy IS the milestone — **RESOLVED (no ask)**
**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**.
**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`).
**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.
**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.
**Lineage note.** pql dropping the milestone entity that this very repo carried is almost certainly
*deliberate* — it productized the lesson that our milestone subsystem was vestigial. So this is less
a gap than a confirmation: labels-as-milestones is the right call, and our migration adopts it
(active milestone `phase:4`, the completed one → `milestone:process-rewire`). Documenting the
pattern (point 1 above) is the only ask.
**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.**
---
@@ -188,4 +189,4 @@ pattern (point 1 above) is the only ask.
| 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) |
| 9 | Milestone entity / labels-as-milestones | ~~Low~~ **Resolved** | N/A — use the initiative/epic hierarchy |