v1.20.0 ·
The gates never checked that two files agree
The problem
Every gate in this framework asks the same kind of question, and it took a while to notice that none of them asks the one that matters most.
The questions they ask are about existence. Does requirements.md exist and is it marked complete. Does
a review file exist with at least one real finding in it. Does every acceptance criterion have a test
somewhere that names it. Is the person advancing the phase the one who claimed it. Each of those is a
perfectly good check, and together they are most of what makes the lifecycle worth running.
But every one of them is a statement about a single moment. None of them is a statement about two documents still agreeing with each other.
So here is what happened, repeatedly, and what nothing caught. The requirements phase closes on a
Tuesday. On Thursday something changes — a customer needs JSON as well as CSV, or a review shows a
criterion cannot be implemented as written, or reality simply intervenes the way it does. Somebody opens
requirements.md and edits the criterion. That is the obvious thing to do, and it is the wrong thing,
and nothing says so.
The phase file still reads complete, because nothing rewrote it. The design document still describes
the old criterion, because nobody went back to it. The tests still reference the criterion by its
original number, so the traceability gate still finds coverage for something that no longer says what it
used to say. Run the gate. It exits zero. Run the whole suite. Green. Ship it.
The framework calls this silent drift, and the name is precise about where the problem lives. The information is not missing — the change is right there in the file, in the git history, plainly visible to anyone who looks. What is missing is any mechanism that notices the change invalidated something downstream. The gates were not failing at their job. Checking that artifacts still agree was never their job, and nobody had written the thing whose job it was.
There was one clue that this had been thought about before and then narrowed almost out of existence. The instance state file carries a scope history: a list of every time the work's scope changed, with the before, the after, the reason, the timestamp, and who did it. That is change control. It is careful, well-designed change control, applied to exactly one field. Scope changes get an audit trail. Requirements changes get nothing.
And the transitions log had no vocabulary for the situation either. It records phase moves with a type — a normal advance, a deliberate skip, a scope upgrade. Three types, all of them describing forward motion. There was no way to write down "we went back", which means that even if you did reopen a phase by hand, the log would record it as an ordinary step forward and the churn would be invisible to anyone reading the history later. Cost attribution and lead-time calculation would both quietly agree with that reading.
The roadmap item asking for this was captured in mid-August and immediately put on hold, with a note calling it the largest piece of lifecycle machinery on the board and estimating weeks of work. That estimate turned out to be wrong, and wrong in an interesting direction. It was written before three other things shipped: criterion IDs that can be formally retired and superseded, task lists that link each task to the criteria it satisfies, and a cost model that already tracks which phase was open when each dollar was spent.
By the time anyone came back to the item, most of the machinery it assumed would need building was already sitting in the codebase, unused for this purpose. Retiring a criterion already parsed. Going backwards through the phases already worked, because the resolver that picks the next phase simply walks the list in order and returns the first one that is not finished — set an earlier phase back to unfinished and it routes there, no special case required. Both the cost windows and the lead-time arithmetic already handled a phase being entered twice.
What was actually missing was smaller and more specific than the item had assumed: a record of why a criterion was retired, a name in the log for going backwards, and a way to find out which tasks, tests and documents still cite something that no longer means what they think it means.
How it could be solved
Option A — Amendment as prose plus a manual reopen. The roadmap item's literal design: agents write
an ## Amendments block with a free-text Impact: line and hand-edit phase YAML. Cheapest to build
(hours) and requires no CLI change. Rejected: the impact line is prose, so nothing can check it, and the
project has already measured what prose follow-ups cost — cost-tracking's deployment record named a fix
that cost-claim-attribution then rediscovered from scratch three days later. A record nothing reads is
a record that drifts.
Option B — Amendment as structured record, impact derived, reopen as a transition type. The
amendment block carries machine-readable Retires: / Issues: fields; a new change/ module walks the
AC references that already exist in tasks.md and test files to compute the affected phases; aidlc amend writes the reopen record and flips the phase states. Days, not weeks, because it composes with
three shipped seams rather than building new ones. Chosen.
Option C — Full consistency gate. A new gate criterion that fails any transition while any retired AC is still referenced anywhere. Rejected on the CLAUDE.md rule: it is a new blocking gate whose defect surface multiplies across every future instance, and it would fail instances that legitimately keep a historical reference in a changelog. The existing traceability warnings already surface stale refs as data; that is the right severity.
How AIDLC solves it
Requirements move after the phase that wrote them closed. Until now nothing in AIDLC noticed, because
every gate is a statement about whether an artifact exists rather than about whether two artifacts still
agree with each other. Editing requirements.md a week after its phase completed left the phase saying
complete, left the design describing the old criterion, and left aidlc gate exiting zero. This
release closes that hole with a record, a transition type, and a walk.
Amendments are now machine-readable. A change to a criterion is written as an
## Amendment <n> — <date> section carrying Retires:, Issues:, Reopens: and Reason: bullets.
The format was not invented for this release — it was measured off the corpus first, where two
conventions already existed: five hand-written numbered amendments in one instance, and four unnumbered
### Amendments from … sections in three others. The grammar accepts the numbered form and warns on
the rest, because a near-miss that reads as absence is the defect this project has paid for five separate
times.
Reopening a phase is a first-class transition. aidlc amend <instance> sets the phase named in
Reopens: — and every phase between it and the current one — back to in-progress, then appends a
type: reopen record whose detail names the amendment, the retired and issued criteria, and the phases
actually rewritten. Churn is now visible in history instead of reading as a normal forward pass, and cost
and lead-time attribution follow the reopened phase without any change to how they work.
The feature adds no new gate. That was the design constraint that mattered most, because this
repository has measured what lifecycle machinery costs: every gate added is a tax every future instance
pays. Reopening works by setting a phase back to in-progress, which re-arms the required-artifacts gate
that already exists. GateViolation gained no member, evaluateTransition gained no check, and a test
pins its eleven gates exhaustively.
The impact walk suggests and never decides. aidlc amend <instance> --impact is read-only and lists
every task, test and artifact that still cites a retired criterion. It deliberately does not choose which
phases to reopen. The first design derived the reopen set from that walk, and the result was degenerate:
requirements.md always cites every retired criterion — the retirement bullet itself, the
superseded by clause, the amendment's own Retires: field — so every amendment would have reopened the
requirements phase and dragged its adversarial-review gate back in. A destructive state write should not
depend on a prose-scanning heuristic, so the author states Reopens: and the walk offers a suggestion to
paste.
What was deliberately left out. The original specification also recorded a digest of each artifact at
phase completion, so an un-amended edit could be detected. That half is not here. Phases reach
complete by three different routes — the normal transition write, a second writer for the final phase,
and by hand, which the shipped phase skills explicitly instruct — and a digest written on one route
detects nothing on the other two. Worse, an absent digest has to read as legacy-allowed for the
forty-five instances that predate it, which would have made the blindness silent. Shipping it would have
produced a check that reads clean because it never ran. It is deferred as its own roadmap item, gated on
those three writers becoming one.
Also shipped: the amendment protocol is now stated in the requirements, design, implementation and testing skills and in the framework overview, so an agent reads the rule at the moment it would otherwise edit a completed artifact.