Skip to content

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.md assigns. 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.
#ItemRecorded inResolves inTaken on
1The audit log is an NDJSON file (.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. Paid in lesson 14 by projection rather than by merging. 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
2Evidence binds to contract criteria by class rather than by content, so a passing suite that never exercises the changed behaviour still satisfies a test-class criterion. The software workload declares 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. Paid in lesson 14. An evidence expectation may now carry a check (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
3Runtime-class evidence is treated like semantic evidence, that is, as human acceptance, because no host mechanism observes runtime behaviour. Paid in lesson 14. A runtime criterion that has fresh host evidence from a content check is satisfied by that evidence, and a person is asked only where no probe exists.behaviour-check; closeout-gate limitation 3—lesson 09 → rescheduled lesson 12 → paid lesson 14
4The 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 2unscheduledlesson 09
5Remediate, replan, clarify, pause and escalate are recorded and the unit waits, but nothing performs them, reminds anyone, or delivers the algedonic signal beyond the read model. Policy proposals (lesson 10) land in the same sink and meet the same fate. Paid in lesson 11. Each waiting action now opens an obligation owed to the consumer the routing policy names, and proposals and escalations are obligations owed to S5 or to a person. The veto holds the unit until a disposition, and the disposition reaches the next attempt. Delivery and reminders remain row 20.recovery-router limitations 2 and 4; proposal-intake limitation 1—lesson 08 → paid lesson 11
6Capability profiles are code (packages/regulator/src/profiles.ts) with no declared place in the definition, and regulator status reports them as pending. Paid in lesson 12. Profiles are files under 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
7Path gates are lexical. There is no symlink, hard-link, absolute-path or TOCTOU protection in the vendor write gate, the profile write grant, or the lease's coverage of the working directory. Paid in lesson 10. 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
8bash 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 2unscheduled (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
9Leases 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 4unscheduled (deferred with full recursion)lesson 05
10Authority references on fixed decisions are strings, and nothing verifies that the cited invariant or decision exists. Paid in lesson 12. A reference must resolve to an invariant the instance's identity declares, a regulator the registry declares, an obligation the instance holds, or a named person; otherwise the contract is refused. A person's name is trusted as given (row 20).work-contract-gate limitation 2—lesson 06 → paid lesson 12
11Reintegration detects file-level conflicts only. Two units can change disjoint files and break each other, and nothing runs the checks on the merged base, because the closeout gate verifies the unit's branch at its HEAD rather than the merge result. Paid in lesson 15. After every reintegration the loop runs 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
12Emergent decisions below high consequence, and residual uncertainty, are recorded in the report only, and nothing routes them. Paid in lesson 11. Every emergent decision, deviation and residual uncertainty becomes a signal, and the routing policy decides which of them open an obligation and which are noted as trace with the reason.result-report-gate limitation 3—lesson 06 → paid lesson 11
13The 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 worklesson 05
14The 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 2unscheduled (S2 state across sessions goes with full recursion)lesson 05 → narrowed lessons 11, 12
15The 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)
16The recovery router counts occurrences per cause, so a unit alternating between two causes never reaches either rule's third action and stops at the attempt ceiling. Whether that is the intended behaviour is undecided. Decided in lesson 11: it is by design. Per-cause occurrence is the policy's contract, the attempt ceiling is the global stop, and the alternation is visible in the unit's decisions and obligations. A policy that wants a global count declares shorter rules. The card states it as a limit.recovery-router limitation 3—lesson 08 → decided lesson 11
17A protected change made and committed by one shell command survives the bash watch. The working tree is restored but the commit is not, and the closeout gate's protected-untouched check reads git status, which is clean. Paid in lesson 12. The 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
18The dispatcher's loader refuses the project's extensions, skills, prompts and themes, but it still reads the project's context files and .pi/settings.json. Paid in lesson 12. The loader takes no context files from the worktree, and the session runs on the definition's 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
19Nothing 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 3unscheduled (containment is the operator's)lesson 10
20An obligation owed to a person is exposed by the read model and the control room but never delivered. Nothing notifies, nothing reminds, and the CLI trusts --by: who may accept a risk at what severity is not checked, and no dialog asks anyone. Paid in lesson 13. What is owed to a person is delivered to the outbox through the effect journal and reminded after the interaction policy's interval. 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
21A proposal is an obligation owed to S5, but the S5 decision itself has no workflow. Paid in lesson 12. `regulator identity acceptreject` 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—
22Effective severity is the message's own (uncertainty impact aside), so a message that names a protected invariant is not raised by that fact. Paid in lesson 12. The routing policy declares floors, which are patterns over what a message says, and the lab's policy raises anything that names an invariant id or the identity path to blocking.obligation-router limitation 3—lesson 11 → paid lesson 12
23Routing 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
24An identity decision changes the instance's copy under 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. Paid in lesson 15. 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
25The identity rendered into a unit's prompt is truncated at 6000 characters, so a set longer than that is partly advice the model never sees, and nothing warns about it. Paid by #51. 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
26Operational 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 2unscheduled (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
27Delivery of what is owed to a person ends at the outbox, a file in the instance. No channel reaches a person who does not read it, and reminders run only at the loop's steps and on regulator remind, so nothing schedules them in a quiet instance. Paid in lesson 15. 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 1unscheduled (authentication belongs to the deployment, and the lab records the claim honestly)lesson 13
29Whether 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
30A paused unit holds its lease and worktree indefinitely. Nothing expires the wait, and a question nobody answers is visible but never times out at the loop's level. Paid by #52. The interaction policy may declare 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
31The SQLite regulatory event store that the reporting tools write (.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. Paid in lesson 15. The store is now .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
32Review dates are checked on demand (regulator review --due). Nothing under CI fails on an overdue record, so a card can pass its review date unnoticed. Paid in lesson 15. 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
33The 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
34Vocabulary drift and rules written into prose are measured by the graders and absorbed by nothing, and the sloppy report shows them identical in both arms. A glossary lint at closeout is the obvious check, and none is declared. Paid in lesson 15. The identity's glossary names the words this instance does not use, 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
35export-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
36glossary-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. Paid by #47. A check entry may carry options, 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
37The post-merge check runs after the merge rather than before it, so the base is red for as long as S3 takes to decide. A pre-merge trial on a temporary merge commit would refuse the reintegration instead, at the cost of a second worktree per close. Paid by #48. 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
38The 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 3unscheduled (a systemd unit or a CI schedule, documented in OPERATING.md)lesson 15
39An 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
40The 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
41inherited-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).

Released under the Apache-2.0 License.