Skip to content

Design: dependency-aware queue sequencing

Epic-to-Epic dependencies in pitlab are invisible: 30 Epics sit in the delivery queue with one dependency link between them, so nothing stops work whose prerequisite is unfinished from reaching the top of the list. This design makes those dependencies captured facts — recorded by a human at the moment work is groomed or designed — and makes them sort the queue rather than gate it. No new skill is built; three existing skills gain a capability and one weekly detective watches whether the practice is actually happening.

Prior art — the Layer 0 sweep, and what it changed

Section titled “Prior art — the Layer 0 sweep, and what it changed”

Board swept across eleven mechanism-class vocabularies, title and description: roadmap, dependency, critical path, sequenc, prioriti, graph, keystone, what next, planning, depmap, link. Corpus swept with two phrasings — dependency-mapping/critical-path, then forcing-function/reconciler-drift — across designs/, adr/ and standards/.

No existing Epic covers Epic-dependency sequencing, so this design is not a second copy. Four hits changed it materially:

Prior artVerdict
Epics #2420 “1. claude-config as a governed repo”, #2441 “2. Agent permissions as governed config”, #2436 “3. Blocked-action deferral for /go”, #2251 “4. Delivery verbs — /go replaces /justdoit”Does not cover the goal, but supplies a precedent. A human already sequences related Epics by hand using an ordinal title prefix, and #2441’s title reads “blocked on 4”. The [N] prefix this design keeps is therefore existing convention, not an invention
Design: /groom and its debt detectiveDoes not cover the goal; supplies the pattern. groom_backlog_debt.py — monthly, Zabbix items plus Prometheus textfile, paging owned by triggers, an age dead-man — is the shape this design’s detective copies rather than reinvents
Backlog grooming runbookAlready documented that ChangedDate is unusable for age: “any bulk edit, re-parent or tag fix bumps ChangedDate”. A live measurement this session re-derived that fact independently; it should have been retrieved, not rediscovered
Epic #2348, Grill GateAdjacent, and the reason a gate was rejected here. It is a fail-closed deploy-time design-authority gate with thirteen resolved decisions and, by its own status note, “no code, no standard, no pipeline wiring”. Building a second design-authority control before the first exists is how controls become bypassable
Epic #2260 servicemap, ADR-0359Different domain. It maps service topology — what runs on what — not what blocks what. Reusing its renderer would produce one graph answering neither question cleanly

The measurement that killed the derivation approach

Section titled “The measurement that killed the derivation approach”

The design originally proposed deriving dependencies from Epic descriptions. That half was rejected after measurement, not assumed away. All 30 Epics in the Up Next and Grilled columns were pulled and read on 2026-08-17:

ObservationValue
Epics in the queue30
Existing dependency links between them1
Descriptions effectively empty (<40 chars)4
Descriptions containing any dependency phrase5
Epic-to-Epic dependencies actually stated in text0
Descriptions citing another work item18 — none citing another queued Epic

Every one of the five phrase hits was read in full context and none survived: #1599 (“every other child depends on it”) and #2348 (“start with Issue #2349… must land atomically”) are intra-Epic child ordering; #2412 (“Batch 1 = … then …”) is intra-Epic batching; #1802 was a regex false positive on the word “Precedent”; #2102 (“prerequisite for any Proxmox firewall use”) names a dependency on a future capability that is not a queued Epic.

The knowledge is not in the cards. It exists in the owner’s head at the moment work is designed, which is why capture beats derivation here and why no derivation skill is built. Deriving over the 102 To Do Epics — 5,151 pairs, 92 of them ungroomed — would have produced a complete-looking graph whose majority was authored rather than observed (FC-19).

derive across all 102declare + derive the queuemeasured: ZEROnew /depmap skillamend 3 skills + 1 detectivehard gate at /grillmesortinggenerated docs pageboard-nativeunmet_predecessors 0 onlycoverage AND driftadd a 5th 'ready' tagSequenced: stampEpic dependencies areinvisibleWhere do edges comefrom?REJECTED 5,151 pairs,92 ungroomed, mostlyfictionmeasure firstAre dependenciesstated in the 30descriptions?derivation half REJECTEDcapture onlyWhat shape?REJECTED nothing toderive,overlaps /groom's Q&A walkcapture where a humanalready reads the cardForcing function?REJECTED duplicatesGrill Gate #2348, which hasno codeunmet predecessor sinksand cannot be markedsequencedClarity artifact?REJECTED by ownera file to keep fresh and driftagent reports in-session;script emits metrics onlyWhat does the detectivemeasure?REJECTED 1 link exists,so green forever (FC-03)queue_unmapped burnsdown;unmet_predecessors growsmeaningfulGovernance?REJECTED vocabularywouldgrow twice in one weektags stay at four;ADR-0399 title clause only
derive across all 102declare + derive the queuemeasured: ZEROnew /depmap skillamend 3 skills + 1 detectivehard gate at /grillmesortinggenerated docs pageboard-nativeunmet_predecessors 0 onlycoverage AND driftadd a 5th 'ready' tagSequenced: stampEpic dependencies areinvisibleWhere do edges comefrom?REJECTED 5,151 pairs,92 ungroomed, mostlyfictionmeasure firstAre dependenciesstated in the 30descriptions?derivation half REJECTEDcapture onlyWhat shape?REJECTED nothing toderive,overlaps /groom's Q&A walkcapture where a humanalready reads the cardForcing function?REJECTED duplicatesGrill Gate #2348, which hasno codeunmet predecessor sinksand cannot be markedsequencedClarity artifact?REJECTED by ownera file to keep fresh and driftagent reports in-session;script emits metrics onlyWhat does the detectivemeasure?REJECTED 1 link exists,so green forever (FC-03)queue_unmapped burnsdown;unmet_predecessors growsmeaningfulGovernance?REJECTED vocabularywouldgrow twice in one weektags stay at four;ADR-0399 title clause only
DecisionChoice madeRationaleRejected alternatives
Edge sourceDeclared by a human at grooming and design time92 of 102 To Do Epics are ungroomed, and zero dependencies are stated in the queue’s text — there is nothing to deriveDerive across all 102 (fiction at scale); derive the 30-card queue (measured empty); declare-only with no backfill path
ShapeAmend /groom, /grillme, /getready; add one detective script. No new skill/groom already walks cards with the owner in Q&A — one extra question is nearly free, and capture belongs where a human is already readingA /depmap derivation skill (nothing to derive); a /depmap capture skill (overlaps /groom); script-only (no backfill path)
Forcing functionDependencies sort, they do not block. An Epic with an unmet predecessor sinks and cannot be marked sequencedReuses a mechanism that already exists in /getready for stale cards; nothing to bypass; degrades safely to a merely-unsorted queueHard gate at /grillme (duplicates unbuilt Grill Gate #2348); detective-only (board never self-corrects — groomed_pct reached 0 with a healthy detective); artifact-only (relies on remembering to read it)
Clarity artifactNone on disk. Board holds the links; the agent reports critical path, root set and keystones in-session; the script emits metrics onlyOwner’s ruling. Removes every drift and freshness risk a generated page carries, and matches “I am the interface — never defer him to a source he does not read”A generated docs page with Mermaid; extending servicemap; a Grafana panel alone; board-native with no reporting path at all
Detective signalsTwoqueue_unmapped (coverage) and unmet_predecessors (drift), plus cycles and an age dead-manWith one link across 30 Epics a drift-only trigger reads 0 whether the queue is perfect or nobody ever looked — indistinguishable states, FC-03. Same volume-plus-tail logic as the grooming detectiveDrift only (green forever); coverage only (goes silent permanently once mapped); a single composite health percentage (hides which of three things moved, so the alert cannot name an action)
“Examined and independent”A DepMapped: YYYY-MM-DD description stampWithout it, “no dependencies” is indistinguishable from “never looked” — the exact gap the groomed tag closes for grooming. Reuses the Groomed: stamp pattern already liveA fifth Epic tag (vocabulary growth); nothing at all (coverage metric becomes unmeasurable)
Queue positionA Sequenced: YYYY-MM-DD stamp, not a ready tagA dated stamp is self-evidently stale; a bare tag lies silently. Keeps the Epic tag vocabulary closed at four and removes the reconciliation burden a rotting tag would imposeThe ready tag with an ADR-0399 amendment; StackRank only (unreadable in any report)
Ordinal prefixKeep [N] in the title; amend ADR-0399’s title clause onlyAlready live convention on #2420 → #2251. A queue position is a value, and ADR-0399’s own (HC DD-MM-YY) clause establishes that values belong in the titleDrop the prefix (position invisible outside the board); leave it unconformant
CadenceWeeklyQueue drift is actionable on the timescale work is picked up. The grooming detective’s monthly cadence suits debt that moves on weeks; a monthly nudge about this week’s queue is noiseExtend groom_backlog_debt.py (monthly cadence conflicts); daily (nothing changes that fast)
RequirementResolution
MonitoringFour Zabbix items — depmap.queue_unmapped, depmap.unmet_predecessors, depmap.cycles, depmap.age — with depmap.age as the dead-man, registered in the cron-heartbeats table. The dead-man matters more than usual here: a mapped queue publishes low numbers, so a collector that died is indistinguishable from a healthy board
ObservabilityPrometheus textfile collector scraped via Alloy, carrying the same four values for trending
AlertingAlertmanager/Zabbix triggers on coverage above threshold, any cycle, any unmet predecessor, and a stale heartbeat. Paging owned by the triggers, never by the script — one alerting path, ADR-0036. Pushover only, priority 0, subject to the 21:00–07:00 quiet-hours mute
SBOM / vulnerability coverageNo new image and no new dependency — a stdlib Python script in an existing repo, covered by that repo’s existing Trivy and Dependency-Track posture
Documentation as codeThis design doc, a governing ADR, the ADR-0399 title-clause amendment, and a runbook, all in the same delivery
Config as codeScript in ansible/scripts/ with its deployment task (RULE 6 category 1 — host cron); schedule declared in schedule_config.yml; Zabbix items and triggers deployed by playbook. Nothing manual
Software update and patch lifecycleThe repo-versioned script pattern, not the pinned-tag container pattern: no image, no upstream release. Bump trigger is an ordinary code change. Gated before landing by --self-test plus the trigger red-test. Rollback is git revert. Staleness here is not a stale version but a stale board model — a script whose assumptions no longer match how the board is used — detected by the dead-man and by the coverage metric behaving implausibly
Planned maintenanceNo service disruption; the only live-system change is adding Zabbix items, covered by a maintenance window during deployment
NotificationsPushover via the Zabbix trigger path. No email
TaggingDocs tags from the registered mkdocs.yml vocabulary; Zabbix items carry standard tags; Prometheus metrics carry join keys; ADO fields populated
Enterprise patternDependency-aware scheduling with critical-path awareness — the planning half of theory of constraints: sequence around the bottleneck, because the longest dependency chain is the true floor on delivery time and no amount of parallelism beats it
DORA / SREThis is a planning control, not a service, so the Four Keys apply only indirectly: it targets lead time by preventing work from starting blocked. Its SLI is queue coverage — the share of queued Epics examined for dependencies. Error budget is expressed as unmapped-Epic-weeks
Minimal decomposition2 sub-issues, 1 prompt. Mechanism and governance must land atomically; rollout and the readiness review follow

The compliance surface here is genuinely thin, and inflating it would be theatre. This is an internal work-planning control: it touches no data, no identity, no network exposure and no third party. It is recorded honestly rather than mapped to security frameworks it does not serve.

Control / decisionFramework mappingHow it is tested and evidencedResidual risk
Dependencies recorded as durable links rather than held in one person’s headNIST CSF 2.0 Govern (accountability for prioritisation decisions); ISO 27001 A.5.37 documented operating proceduresCoverage metric trends the population; the DepMapped: stamp evidences examinationA captured link can be wrong; a wrong link is read as fact by every later run
Detective control with a dead-man behind itCPS 234 testing of controls, not merely implementationTriggers proven red against a forced condition before being trusted green; dead-man proven by ageing its stampunmet_predecessors has no measured baseline, which ADR-0361 expects for a >0 trigger — it cannot have one until coverage exists
Sorting rather than gatingISO 27001 A.8.32 change management, lightlyThe sort rule proven by planting a real link and observing the demotionA sort is advisory — nothing prevents the owner from picking a blocked Epic deliberately, which is accepted as correct behaviour
  • Service record. A control within cc-pool agent tooling, tier T3. No host, no hostname, no NetBox asset, no material service provider. Its unavailability delays a sequencing pass and affects no running service.
  • Reliability contract. No availability SLO. The operating signal is coverage: unmapped queue Epics, trending down. When coverage stalls, the response is a /groom pass, not an escalation.
  • Runbook. Owed at docs/operations/, covering how capture works inside /groom, what each of the four metrics means, how to correct a wrongly-captured link, and the failure modes. Must be walked cold by a fresh session before acceptance.
  • Test strategy. Per sub-issue: every trigger proven RED against a forced condition before being trusted green; --self-test on the reconciler; the sort rule proven by planting one real Predecessor link and observing the dependent Epic demote and lose its Sequenced: stamp. No testplan.yaml is owed — the Service Test Plan Standard governs services with a golden path, and this is agent tooling plus a detective whose dead-man is its gate, the same reasoning accepted for /groom.
  • Day-2 BAU and toil. This design removes planning toil rather than adding it: the recurring obligation is answering one extra question during a /groom pass that is already happening. The reconciler is automated and declared in the schedule config.
  • Backup and restore. No datastore. All state is ADO work-item metadata plus version-controlled scripts; recovery is a git checkout and the links persist in ADO.
  • Standing owner. The weekly reconciler is the detective; its dead-man is the backstop; /health surfaces Zabbix state. Nothing new needs remembering, which is the point.
  • Lifecycle exit. Retiring this means removing the reconciler, its schedule entry and its Zabbix items, and deciding whether the captured links stay. The links are ordinary ADO relations and survive independently of the tooling.

Issue #2504 — mechanism, governance and the three skill amendments. Must land atomically: the sort rule is meaningless without capture, and the detective measures a practice that must already exist. Scope: /groom gains the dependency question, the Depends on: brief line and the DepMapped: stamp; /grillme captures links at close-out; /getready drops the ready tag for a Sequenced: stamp and gains the unmet-predecessor sort rule; depmap_reconcile.py with its cron, schedule slot, four Zabbix items, triggers and Prometheus textfile; the ADR-0399 title-clause amendment; the governing ADR; the runbook.

Issue #2505 — rollout, backfill and the readiness review. Scope: a real /groom pass over the 30-Epic queue so coverage has data and the drift signal becomes meaningful; the PRR with evidence — alerts red-then-green through the real trigger, metrics populated, dead-man proven by ageing its stamp, runbook walked cold; a two-week early-life soak reviewing captured links for wrong edges, since a persisted wrong link is read as fact by every later run.

Filled by /wrapup Check O at Epic close. Empty by design — nothing has been built.