Build debt
This file lists what the build knows it has not yet absorbed. Each row is one gap that counts as debt: it was taken on deliberately, and there is a place where it will be paid. A permanent limit of a mechanism is a different thing and gets no row here. The registry cards under packages/regulator/registry/regulators/ remain the source of truth for what each regulator does not catch. This file indexes the subset of those limitations that is owed, so that nothing is stated twice and nothing is stated only in a card's fourth bullet.
Rules:
- When a new registry limitation names a later lesson, a milestone or a "not yet", it gets a row here in the same change. AGENTS.md states this under "Before finishing an issue".
- The change that pays a row also closes it. That change strikes the row, cites the commit or PR, and updates the card's limitation to say what the mechanism now does.
- "Resolves in" names the issue and milestone that
ROADMAP.mdassigns. It is not a promise of a date. A row with no issue says unscheduled, and when the reason is the deployment's, the roadmap's "Not planned" table says why.
| # | Item | Recorded in | Resolves in | Taken on |
|---|---|---|---|---|
.regulator/audit.ndjson) beside the regulatory log (.regulator/signals.ndjson), and neither of them is the SQLite regulatory event store that the reporting tools write (.gsd/vsm-runtime/vsm.db), whose path still carries a GSD-era name. Lesson 11 decided that obligations fold from events in the same log as the messages they route, so the instance has two NDJSON histories rather than three. Absorbing them into one store is the observability work.projectSpans reads every store the instance keeps into one set of OpenTelemetry GenAI spans, correlated by unit, attempt, evidence, obligation, regulator and policy version, with redaction. The stores themselves stay separate, and the SQLite store and its path are row 31. | span-projection; closeout-gate limitation 4 | — | lesson 09 → narrowed lesson 11 → paid lesson 14 | |
run_tests, run_checks and, since lesson 12, identity-untouched. A behaviour-bound check is still a workload's to declare, and the drift scenario's signature check is the first one planned.export-signature), the host observes it by content at HEAD, and the record binds to that criterion alone. Binding by class remains the default for expectations that carry no check, and only one kind of check exists so far (row 35). | behaviour-check; closeout-gate limitation 1 | — | lesson 09 → rescheduled lesson 12 → paid lesson 14 | |
behaviour-check; closeout-gate limitation 3 | — | lesson 09 → rescheduled lesson 12 → paid lesson 14 | ||
| 4 | The environment an evidence record carries is the orchestrator's host. A check that passes here and fails on the target platform is therefore not caught. | closeout-gate limitation 2 | unscheduled | lesson 09 |
recovery-router limitations 2 and 4; proposal-intake limitation 1 | — | lesson 08 → paid lesson 11 | ||
packages/regulator/src/profiles.ts) with no declared place in the definition, and regulator status reports them as pending.packages/regulator/profiles/, validated on load and by regulator check, and the read model declares them. | course/CONTROL-REGISTRY.md status; packages/cli/src/status.ts | — | lesson 06 → paid lesson 12 | |
prepareWritePath (core) walks the filesystem for aliases and refuses absolute paths and traversal, and checkpoint 9's gate supersedes the lexical ones when it is loaded. TOCTOU between the walk and the write remains a stated limit rather than debt. | identity-write-gate limitations 1 to 3 | — | lesson 02 → paid lesson 10 | |
| 8 | bash is outside most mechanisms. It is invisible to the thrash detector, the effect journal, and the failure observer's non-error exits, and the implement profile grants it un-gated. Lesson 10 added the snapshot-and-restore for protected paths, and lesson 12 added the branch diff at closeout; together they narrow the gap to effects that are neither protected nor committed. Lesson 15 decides that the shell's reach is the container's to declare (OPERATING.md § Security posture) and that the manifest's declared prefixes bind write and edit only. | thrash-detector limitation 1; profile-write-grant limitation 1; effect-journal limitation 3; failure-observer limitation 2 | unscheduled (real isolation is the operating system's or a container's, so the lab documents it and does not build it) | lesson 03 → rescheduled lesson 12 → rescheduled lesson 12 → unscheduled lesson 15 |
| 9 | Leases and the effect journal are files on one machine. There is no fencing token and no cross-host coordination, so two harnesses on two machines cannot see each other's intentions. | unit-lease limitations 1 and 3; effect-journal limitation 4 | unscheduled (deferred with full recursion) | lesson 05 |
work-contract-gate limitation 2 | — | lesson 06 → paid lesson 12 | ||
run_tests and run_checks on the base at the merge commit and records the evidence. A failure is an audit finding with no unit: an obligation on the instance itself, which holds every dispatch until S3 dispositions it. The check runs after the merge rather than before it (row 37), and file-level conflict detection is unchanged. | post-merge-check; reintegration limitations 1 and 3 | — | lesson 05 → rescheduled lesson 12 → rescheduled lesson 14 → paid lesson 15 | |
result-report-gate limitation 3 | — | lesson 06 → paid lesson 11 | ||
| 13 | The fixture lacks the flaky test and the migration that must not be re-run. The drills that need them (lessons 08 and 09) describe the setup by hand instead. | course/PRODUCTION-PLAN.md, next actions | #55 (roadmap 0.2); was: Phase 2 fixture work | lesson 05 |
| 14 | The thrash detector's memory is per session, so a resumed attempt starts counting from zero. Lesson 11 gave the signal a history, and lesson 12 made the threshold the policy's (coordination.oscillationThreshold). The count itself still restarts. | thrash-detector limitation 2 | unscheduled (S2 state across sessions goes with full recursion) | lesson 05 → narrowed lessons 11, 12 |
| 15 | The model router's dispatcher is exercised only with a live model. No headless test drives failover end to end. | model-router limitation 3 | #46 (roadmap 0.2); was: the first live run (lesson 14 drill 2 under the packaged definition; with row 33) | lesson 07 → rescheduled lesson 14 → rescheduled lesson 14 → with row 33 (lesson 15) |
recovery-router limitation 3 | — | lesson 08 → decided lesson 11 | ||
protected-untouched check reads git status, which is clean.identity-untouched host check diffs the unit's branch against its base under every protected prefix at closeout, and the workload's implement, verify and integrate unit types name it. | identity-write-gate limitation 1; identity-untouched-check | — | lesson 10 → paid lesson 12 | |
.pi/settings.json.settings.json held in memory. The identity a unit sees is checkpoint 11's section. | project-trust-rule limitation 2 | — | lesson 10 → paid lesson 12 | |
| 19 | Nothing watches network egress. The canary watch sees tool results and assistant text only, and Pi's security doc names the container as the boundary. | canary-watch limitation 3 | unscheduled (containment is the operator's) | lesson 10 |
--by: who may accept a risk at what severity is not checked, and no dialog asks anyone.ask_human asks through the session's dialogs with the policy's timeout per kind, and it pauses the unit when no answer comes. Every --by is checked against the policy's people before anything is written. The channel beyond the outbox and the authentication of the name are rows 27 and 28. | algedonic-delivery; interaction-contract; disposition-authority; obligation-router limitations 2 and 4; recovery-router limitation 4 | — | lesson 11 → paid lesson 13 | |
| reject` is the only writer of an identity file. It acts under S5 authority, reverts an invalid result, commits with a citation of the obligation, and resolves the obligation as accepted or rejected. Turning the proposal's text into a file is still a person's work. | proposal-intake limitation 1; s5-decision | — | ||
obligation-router limitation 3 | — | lesson 11 → paid lesson 12 | ||
| 23 | Routing runs at the loop's steps and on regulator signals route, so a session run by hand leaves its messages unrouted until one of those happens. Nothing watches the log. | obligation-router limitation 1 | #59 (roadmap 0.3); was: unscheduled (a file trigger is a lesson 14/15 operating concern) | lesson 11 |
regulator/identity/ and leaves the definition's seed under packages/regulator/identity/ untouched, so the next fixture starts from the unchanged seed. Promoting a decision into the definition is a commit by hand.regulator identity promote <file>.md copies the instance's accepted identity file into the definition's seed and commits in the definition's repository under S5 authority. It refuses a no-op, an invalid result or a dirty definition. Every later init starts from the promoted seed. | identity-promotion; s5-decision limitation 3 | — | lesson 12 → paid lesson 15 | |
identityContextBudget (core) measures the rendering against the limit and names the files that were cut. regulator check and regulator doctor warn about it, as a warning rather than a problem, because a long set is still valid. The session's status line says so, and the identity extension records one advisory operational-signal per session on the unit. Paying this row found the definition's own seed over the budget (7005 characters, with GLOSSARY.md's tail unseen), which the backlog recorded. #91 then raised the default to 12000 and made the limit the budget policy's (identity.maxChars) rather than shortening the seed. | identity-context limitation 3 | — | lesson 12 → paid #51 | |
| 26 | Operational memory is rendered to every unit of the instance, unscoped, and nothing checks that a fact is true. The 90-day review limit is the only pressure. Lesson 14 measures the hygiene (the memoryRules and memoryFacts graders) and shows that the tool changes what a unit can do, though not what a prose-preferring unit will do; scoping and truth were left unpaid. Lesson 15 scopes a fact to unit types (remember's scope, with the section filtered by the unit's type). Whether a fact is true stays a person's review. | memory-store limitations 1 and 2 | unscheduled (a truth check is a person's review, and the expiry is the pressure) | lesson 12 → narrowed lesson 14 → narrowed lesson 14 → narrowed lesson 15 |
regulator remind, so nothing schedules them in a quiet instance.regulator watch delivers, reminds and forwards every new outbox line to the channel command the deployment names, keeping a cursor so that a restart forwards nothing twice. --once is the CI form. Supervising the process and choosing the channel are the deployment's (row 38). | outbox-watcher; algedonic-delivery limitations 1 and 2 | — | lesson 13 → paid lesson 15 | |
| 28 | --by is asserted rather than authenticated. Disposition authority checks whether the named person may act, and it does not check whether the caller is that person. The dialog path attributes an answer to the session's OS user without even that check. | disposition-authority limitations 1 and 3; interaction-contract limitation 1 | unscheduled (authentication belongs to the deployment, and the lab records the claim honestly) | lesson 13 |
| 29 | Whether an action needs consent is the model's to notice. The profile's advice says when, and the tool contracts what consent means, but nothing derives "this is irreversible" from a tool's declared effects before it runs. | interaction-contract limitation 2 | #60 (roadmap 0.3); was: unscheduled (an effect-derived consent check would make every notify_owner a consent question, so the profile's advice and the tool's contract are the mechanism until a per-tool consent grant is a profile field) | lesson 13 → rescheduled lesson 13 → unscheduled lesson 15 |
waitCeilingMs. A unit paused past it is routed under the recovery policy's timeout cause (retry, retry, then escalate by the shipped rules, with each routing past the ceiling counting as one occurrence) and its lease is released, while the question stays open with its veto and its history says why. The shipped policy leaves the field unset, because the honest default is to wait. | pause-gate limitation 3 | — | lesson 13 → paid #52 | |
.gsd/vsm-runtime/vsm.db) belongs to the Pi extension rather than to the lab instance. The span projection does not read it, and its path still carries a GSD-era name..regulator/events.db, beside the instance's other records, and the span projection reads its rows as events on the unit their provenance names. | span-projection limitation 3; docs/REPORTING.md | — | lesson 14 → paid lesson 15 | |
regulator review --due). Nothing under CI fails on an overdue record, so a card can pass its review date unnoticed.checkRegistry takes today's date and treats an overdue review as a problem, and regulator check (which runs under pnpm check, so under CI) and regulator doctor both fail on it. | regulator-lifecycle limitation 2; doctor | — | lesson 14 → paid lesson 15 | |
| 33 | The committed eval reports ran scripted units. The session gates (profile grant, write gate, bash watch, canary watch, budget guard) were not exercised; only the loop and the closeout checks were. The numbers are therefore the graders' validation and are not evidence about a model. A live report over the drift suite is owed. | eval-harness limitation 1; the reports' fingerprints | #46 (roadmap 0.2); was: the first live run (lesson 14 drill 2; unscheduled under CI, since it needs a model and spends) | lesson 14 |
glossary-lint reads the added comment lines and the commit messages on the branch for them at closeout, and the drift suite's treatment arm declares it. The regenerated sloppy report shows the cost: a correct unit was refused three times for a word in a commit message. That cost is row 36. | glossary-lint; eval-harness limitation 4 | — | lesson 14 → paid lesson 15 | |
| 35 | export-signature observes Function.length, which stops counting at the first defaulted parameter, so a defaulted second argument passes as a one-argument signature. Only one kind of behaviour check exists. Return types and behaviour under input are not observed. | behaviour-check limitation 1 | #54 (roadmap 0.2); was: unscheduled (a richer probe is a workload's to ask for, and lesson 14 drill 4 collects the answers) | lesson 14 |
glossary-lint is blocking wherever the workload declares it, and it reads commit messages as well as comments, so one refused word in a commit message costs an attempt. The regenerated sloppy report shows a correct unit refused three times for exactly that. The attenuation (the comment half blocking, the commit-message half advisory) is a workload's declaration to make, and none makes it.glossary-lint takes commitMessages: "advisory", software-development and the drift suite declare it, personal-finance does not, and regulator check refuses an option a check does not take. In the regenerated sloppy report, the four units whose only drift was the commit message close on their first attempt, and the word lands on main as an advisory finding (vocabulary drift reads 4 rather than 0). The two units whose comments drift are still refused. | glossary-lint limitation 2; the sloppy report's interpretation | — | lesson 15 → paid #47 | |
reintegrate merges in a temporary worktree first, and the workload's run_tests and run_checks run on the merged tree. A passing tree lands by fast-forward to the verified commit. A failing one lands nothing and becomes a conflict for the recovery policy (pre-merge-trial). post-merge-check is retired as history, and the cost of the trial is measured on the new card. | post-merge-check limitation 1 → pre-merge-trial | — | lesson 15 → paid #48 | |
| 38 | The outbox watcher is a process rather than a service. Nothing restarts it, one cursor serves one instance, and a channel that accepts a line and then loses it downstream is never retried. Supervision and the channel are the deployment's. | outbox-watcher limitations 1 to 3 | unscheduled (a systemd unit or a CI schedule, documented in OPERATING.md) | lesson 15 |
| 39 | An instance's manifest records the definition revision at init, and doctor reports drift from it. Nothing migrates an instance to a newer definition, though, and the manifest is never rewritten. The upgrade path is a person reading OPERATING.md. | instance-manifest limitation 2; doctor limitation 2 | #53 (roadmap 0.2); was: unscheduled (a definition version per promotion, and a migration note per version, when the definition is released as a package) | lesson 15 |
| 40 | The planner-quality and contract-drift diagnostics (undelegated consequential decisions discovered during execution, fixed-decision deviations, delegated-decision completion, planned versus emergent uncertainty, and replans attributable to the contract) are recorded in every result report, in the fields delegatedResults, unresolvedOutcomes, emergentDecisions, deviations and residualUncertainty, and nothing measures them. The trajectory graders count refusals, retries, escalations and invariant violations only, so nobody can say whether planning is allocating variety well. | eval-harness limitation 4; packages/regulator/src/graders.ts; issue #12 (closed into this row) | #49 (roadmap 0.2); was: unscheduled (a small grader extension in lesson 14's harness: read the reports from the execution store, pre-register the metrics in the suite, and let the eval report carry them per arm) | lesson 14 |
| 41 | inherited-tests counts test and assertion lines and matches regressions by test name. That means a kept assertion rewritten to the current output, in a file the base's copy already failed on, is neither a shrink nor a regression, and a file the contract exempts is not seen at all. The base's package.json is staged with the base's tests, so a unit that legitimately adds a test dependency fails until the contract exempts it. Only test/ is inherited. | inherited-tests-check limitations 1 to 3 | #50 (roadmap 0.2); was: unscheduled (a per-assertion diff against the base's copy, and a test-layout convention from the manifest, when a workload asks; the base-side run could be cached per base revision) | after lesson 15 |
Some limits are permanent, and they are listed here so that nobody re-files them as debt. Prompt sections are advice and can be ignored (contract-advice). A model summary can be wrong, and only the deterministic block is guaranteed (contract-preserving-compaction). The evidence preflight trusts the session's own tool run by design, and independence belongs to the closeout gate (evidence-preflight). An effect with no observable trace can only be reconciled as absent (effect-journal). The gate reads the report and never the diff (result-report-gate).