You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Box cannot check a claim about a build, a lint, a type-check or a test run, and the reason is three
faults stacked on one another. Grilled with the maintainer on 2026-09-03; this body carries the decisions
and the criteria. The two comments below carry the measurements and two corrections to what this body
first said, and they stay on record.
Axis
What is wrong
Where
Capability
an Archon worktree may or may not carry installed dependencies, and no Box asks for them
12 of 16 worktrees on the maintainer's machine have node_modules, 4 do not, and no Box instructs any node to install
Instruction
unic-dlc-pr-review instructs no build, lint, type-check or test command anywhere, and neither does the code-review Method it hosts
the config maps a command one key per intended node, so a command has an owner instead of a home, and a Box names a tool where the config says nothing
unic-dlc-build.yaml:403 falls back to the literal pnpm test
The three interact. Run 2's verification reported PASSED and really did run a green suite, because that
worktree happened to have dependencies and the Box happened to fabricate the right command for a pnpm
repository. Both were luck. On a Python or a .NET Consumer the same node runs a command that does not
exist and reports a failure that has nothing to do with the code.
And run 2's honest "node_modules is absent in this worktree, so neither axis could execute" came from a
sub-agent volunteering a check nobody asked it for. That is the instruction axis: an install alone
changes nothing in pr-review, because nothing there is told to run.
Why it matters more than it looks
The review's whole value is checking claims the author makes. On run 1's PR the author claimed a green
build, a clean lint, clean types and a passing test run, and none of the four was checked, so the verdict
rested on reading the diff alone. DXP-DesignSystem has no pipeline, so when a Box cannot run a check, nothing mechanical ever runs it. A human on their own machine is the only path, and both runs 1 and 2
ended that way.
The decision
The config becomes the abstract layer that maps what a software development process needs, and every
Box reaches the whole of it. The plugin names no tool, ever. A Consumer fills in whatever its stack
uses, and a node decides at run time which of those needs it has.
Three rules carry the weight:
A key names a need of the development process, never a tool.test is a need. pnpm test is a
tool. check-types is somebody's script name; typecheck is the need. This is the rule already
written at AGENTS.md:57 for formatter exclusions, applied to commands.
Discretion to add, never to subtract. Any node reaches any key it judges it needs. A short floor
is mandatory at the two gates that advance work, and abstaining there is a reported non-result, not
silence.
A command that cannot run is never a pass. Today /qa skips without blocking and /build
fabricates. Those are the same defect from opposite ends.
The three-option menu this body used to carry is spent. Options 1 and 2 collapsed into one when the
install cost was measured (4.4 s, 16 MB, warm store, APFS). Option 3 is unavailable, because #430 is an
instrument for run 3 and an instrument that cannot observe is broken, not honest.
Acceptance criteria
The config declares the SDLC's needs in one flat block, named sdlc_needs. The template /unic-archon-dlc:setup writes gains a top-level sdlc_needs block holding exactly nine nullable
keys: install, build, test, e2e, lint, format, typecheck, dev, coverage. Every value defaults to null. Amended 2026-09-03 19:22: the ninth key comes from the maintainer's ruling on did-not-decide item 8,
recorded as criterion 20; the first version of this criterion said eight. Amended 2026-09-03 19:35, found by DLC-430 under hold:README.md § Configuration reference gains one row per key in the same PR. commands/setup.md:262 calls that table the source of every default and :285 forbids asking the operator for a
key the table omits, so criterion 16's proposals are illegal by /setup's own rule until the rows exist. The name is snake_case, and that is measured rather than chosen by taste: every key in the
Consumer's live config is snake_case without exception (artifacts_dir, coverage_threshold, fresh_context_red_green, out_of_scope_dir), so sdlcNeeds and SDLC_needs both lose.
The block states its own rule, and states which keys nothing reads yet. Its comment says that a
key names a need and never a tool. It also says that build, lint, format and dev have no node
reading them on the day this lands, and are declared anyway because /setup writes this file once and
thereafter only reports on it, so a key added later costs another reconfigure in every Consumer.
bootstrap emits the whole block as one object, and no prose assigns a key to a node. In all
three Boxes the bootstrap output schema carries sdlc_needs as a single object field. The per-node
scalars test_command and e2e_command are gone from those schemas. A reader can check that no
node's prose says a given key belongs to it.
No Box that runs a command names a tool. Three sites go: unic-dlc-build.yaml:403's else \pnpm test`, unic-dlc-build.yaml:51's (e.g. "pnpm test")in the inference instruction, andunic-dlc-qa.yaml:134's e.g. pnpm test --coverage. After this, a grep for pnpm across the three Box files returns **nothing at all**. **Corrected 2026-09-03 19:35 (DLC-430):** the first version said "nothing outside slopcheck"; slopcheckholdsnpm (unic-dlc-build.yaml:84, :130), never pnpm, so an implementer expecting survivors there would read zero as over-deletion. unic-dlc-explore.yaml:101is the onepnpm` left in
the plugin's workflows and § Out of scope keeps it.
The two e2e_command keys are retired, and the retirement is stated as deliberate./qa's qa.e2e_command ?? build.e2e_command two-tier resolution is replaced by the single sdlc_needs.e2e.
The per-Box override is removed on purpose, because all four values are null in the only Consumer
that exists, so nothing has ever used that tier. The ADR of criterion 12 says so, rather than leaving
it to read as an oversight. Amended 2026-09-03 by the orchestrator, four-reads pass before dispatch:
the two documents that describe those keys change in the same PR — README.md's config table
(build.e2e_command, build.coverage_threshold rows) and docs/boxes/unic-dlc-qa.md (the resolution
line and its config table). Nothing else in this ticket named them, and a reader of either would have
been told a key exists that the config no longer has. Third site, added 19:35 (DLC-430):commands/setup.md:273,
the **build** line of Step 6's field list, names both retired keys and changes in the same PR.
Each Box installs once, at bootstrap, and says what it did. One Archon worktree serves a whole
run and the build Box's RED and GREEN phases are fresh contexts inside that same worktree, so one
install covers everything. All three Boxes install, with no per-Box exception, and pr-review is not
exempt even though criterion 11 gives it no instructed check: run 2 recorded a sub-agent volunteering
a check of its own and being unable to execute it, so an install there decides that case on merit
instead of on which worktree the run drew. One uniform rule is also less to remember than a rule with
an exception. bootstrap reports either that it ran sdlc_needs.install, or that sdlc_needs.install is
not declared. Where it is not declared, dependencies may still be present by the luck this ticket
exists to remove, so that report is what stops a later green check reading as proof of a deliberately
built environment.
A command outcome has three states, and unresolved is never a pass. Every node that runs a
command reports pass, fail or unresolved. One unresolved covers both causes, the command not
being declared and the command being declared but unable to run. The cause goes in the prose, not in
the state. A node names which need went unresolved. A pass carries the counts the command reported,
and a pass with a zero count is reported as suspicious rather than as a green result, because that
is the shape a command declared against the wrong target takes.
Where a Box has a gate that advances work, unresolved holds it, and nowhere else blocks. /qa's merge and /build's evidence are those gates. pr-review has no such gate, and the
per-slice loop inside run-build is not one either, so an unresolved type-check there reports and the
loop continues. Rewritten 2026-09-04 20:45 by the orchestrator on the maintainer's ruling (Q10 of bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's
grilling: "a"), because bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 changes evidence's mechanism after this ticket lands. The verdict evidence emits carries three states, pass, fail or unresolved, in the JSON it prints to stdout and in
the file it writes, and no reader treats unresolved as green. The reader of that verdict is a node when:,
never the engine: evidence_policy checks presence only, and once bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 makes the file unconditional it fires
only when evidence itself crashes before writing, which is the backstop bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 keeps it for. Whether the
file is written always or only on green, and which node carries the when: that stops open-pr on red,
are bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's decisions. This ticket edits verification and evidence for the third state; the sentence in verification's and goals-check's prompts that tells the reader what evidence does with their output
states the three-state outcome, and both prompts carry the same sentence, so both change. bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 rewrites
that sentence again for always-write and the backstop, against the tree this ticket leaves, and owns the evidence node's comment block whole (the DAG loader parses script-node comments for $node.output
tokens, so nobody tidies it in passing). Between the two merges the printed verdict has no reader, by
design: today nothing in the Box reads $evidence.output (report reads verification and goals-check
directly), and the when: that will consume it is bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's decision. A reviewer who finds a field with no
reader in this ticket's diff has found this sentence, not a defect; criterion 10's block reads the unresolved
needs from verification's output and needs nothing from evidence. /qa's mergewhen: holds on unresolved the same way. Added 19:35 (DLC-430): the plugin AGENTS.md § Plugin doctrines
evidence-gate bullet (AGENTS.md:91) says the node writes "only when both are true"; that sentence is revised in
the same PR, one layer up from ADR-0034, the same way criterion 5 names the documents that describe retired keys.
The floor: /qa's merge gate and /build's evidence verdict require a test outcome. No test
outcome at either is unresolved, which criterion 8 then holds. No other need is mandatory anywhere.
(Reworded 2026-09-04 with criterion 8: "cannot advance" described the withholding mechanism bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 retires;
the floor itself is unchanged.)
A check a Box could not verify reaches the reader as its own visible block, not as a sentence in
a paragraph. Any summary that carries a check outcome or an install report also carries, in a
structurally separate block a developer skimming cannot pass over, the needs that went unresolved
and whether sdlc_needs.install was declared. A reviewer checks this by reading the summary's shape,
not its wording. pr-review's summary states, in that same block, which commands the config
declares and that this Box executes none of them by instruction. Criterion 11 is the reason, and
without this sentence that admission lives only in this ticket, where the developer reading a review
never sees it. Amended 2026-09-03 by the orchestrator, four-reads pass before dispatch: the block is durable — it lands in a file under <artifacts_dir>/<slug>/, the repo-relative Session dir, and a
block that exists only in a node's console output does not satisfy this criterion. Without this sentence /qa satisfies it in scrollback: its e2e and coverage-gate summaries have no reader in the DAG and
the Box has no report node, so the block would die with the worktree exactly like the summary it sits
in. Found by DLC-438-grilling while grilling bug(unic-archon-dlc): a review node produced thirteen findings and recorded them nowhere #438, verified from the orchestrator seat.
pr-review gains no procedure that runs a check against an author's claim. That procedure
belongs to a Method, not to the Harness (ADR-0030), and its home is feat(unic-archon-dlc): the review angles become a Method of ours, harvested from unic-pr-review and rewritten stack-agnostic #433 as a named review angle.
So the title of this ticket stays partly true after this lands: pr-review still runs no lint and no
type-check against a claim. What it can no longer do is stay quiet about that. This ticket's body says
so rather than leaving a reader to discover it.
An ADR records the config-as-abstraction decision, referenced by slug and never by number. It
carries the need-not-a-tool rule, the discretion-to-add-never-to-subtract rule, the floor, and the
retirement of the two e2e_command keys. It also records three things a later reader would otherwise
re-derive. Why the block is sdlc_needs: a key names a need, so a name that named the tools a
need resolves to would encode the opposite of the block's own rule, which is why toolchain lost; commands is reserved twice over and capabilities is taken. Where SDLC comes from: Anthropic's The AI-Native SDLC playbook (https://claude.com/blog/the-ai-native-sdlc-playbook), which defines
the lifecycle as idea to production across Plan, Design, Build, Test, Deploy and Maintain, and names
build, test and lint commands alongside linting, formatting and type-checking. Eight of the nine keys come
from that vocabulary; coverage comes from criterion 20 and the ADR says so. And that criterion 16 widens an existing sweep rather than adding a third,
with the reason: a sweep that re-reads the same manifests for a second purpose is the drift this
plugin keeps filing defects about.
The delivery is named, and it ends inside the Consumer.commands/setup.md is in GUARDED in packages/release-tools/scripts/lib/changelog-gate.mjs, so the PR carries a version bump and a
changelog entry. Reaching run 3 then needs claude plugin update in DXP-DesignSystem, whose .claude/settings.json sets "autoUpdate": false, then /unic-archon-dlc:setup, which reports
the missing block and changes nothing because the config is tenant-owned, then /unic-archon-dlc:setup reconfigure, which is the one override that rewrites it. A criterion that
ended at "the template gains a block" would be satisfied by a change run 3 never sees.
After the delivery, DXP-DesignSystem's config carries real values for sdlc_needs.install and sdlc_needs.test. Without them criterion 4 has removed the fabricated fallback and criterion 9 holds
the gates, so run 3's build leg stops at unresolved. That is the correct failure and it still stops
the run.
The two test_command surfaces stay distinct, and a reader can tell which is which./tickets
writes a per-slice test_command into issues.json (commands/tickets.md:165), read by the RED and
GREEN phases at unic-dlc-build.yaml:279 and :296. That surface is untouched. Only bootstrap's
project-wide inference becomes sdlc_needs.test. The name collision that made this hard to see goes
away, and nothing rewires the per-slice command by mistake.
/setup proposes the values from the stack it detected, and never asks blind — by widening the
sweep it already runs, not by adding a third. Step 4's second sweep, The toolchain that formats or
lints at commands/setup.md:148, already reads "the manifests, task files and scripts this project
actually runs — whatever the language" and keeps only what formats or lints, as FORMATTERS. That
same read now harvests the whole of the SDLC's needs, and the sweep's heading widens with it. It is
not the Step 4 Capabilities sweep at :138, which builds a different registry (mcp | cli | skill,
tool availability) and keeps both its name and its purpose. The harvested values are offered as
proposals a human confirms or corrects, one need at a time, so a Consumer running Python sees a
proposal for test drawn from what was detected rather than an empty prompt. This keeps a wrong value
from being the path of least resistance; it does not stop a deliberate one.
Every node that resolves a command carries the need-not-a-tool rule inline. The sentence lives
in each prompt that reads the sdlc_needs block, not only in the ADR of criterion 12. The root AGENTS.md states the reason under its quality bar: a rule that lives only in an AGENTS.md is
invisible at run time, and every prompt that must honour a rule carries it inline. AGENTS.md:57
already carried this rule for formatter exclusions and the plugin broke it in five places, which is
the evidence that a document-only home does not hold. A reader finds the sentence in the prompt.
CONTEXT.md gains an entry for sdlc_needs, with the forms that lose named. The entry says
what the block holds, that a key names a need and never a tool, and that a key with no reader is
intended rather than a gap. Its _Avoid_ line names commands (the bare word is already forbidden at CONTEXT.md:241, where a Claude Code slash command claims it and an Archon workflow command template
claims it again), toolchain, capabilities (taken by System-skill at CONTEXT.md:38 for a
team-provided capability) and lifecycle_needs. This criterion exists because the first draft of
this ticket named the block commands, breaking a rule CONTEXT.md already stated — the same
failure criterion 17 was written against, one layer up.
SDLC becomes this plugin's term, declared once, and "AI development lifecycle" becomes its
gloss. The acronym appears zero times in the plugin today, while "AI development lifecycle"
appears at CONTEXT.md:3, README.md:3, AGENTS.md:7 and AGENTS.md:47. Adopting an acronym for a
concept the plugin already names another way is synonym drift unless it is managed, which is what CONTEXT.md exists to stop. So CONTEXT.md gains an entry for SDLC, expanded, naming the article
of criterion 12 as its source and its six phases where they earn their place; it states which of the
two is the term and which is the gloss; and the four sites above are made consistent with that ruling
in the same commit. A reader cannot come away thinking they are two different things.
coverage is the ninth key, and the number stays outside the block. Ruled by the maintainer on
2026-09-03 19:22, option (b) of did-not-decide item 8. sdlc_needs.coverage names the need that produces a
coverage figure; build.coverage_threshold stays where it is, because it is a number and not a need, and
§ Out of scope still holds for it. coverage-gate (unic-dlc-qa.yaml:120) and verification step 3
(unic-dlc-build.yaml:410) resolve sdlc_needs.coverage and nothing else once criterion 4 removes the
literal; neither appends a flag of its own to sdlc_needs.test or to any other declared command. A null coverage, or a declared one that cannot run, is unresolved under criterion 7. The threshold decides pass
against fail; whether the node reports at all is item 9's ruling, not this criterion's. The ADR of
criterion 12 records why the key exists: without it both nodes hold a threshold and nothing to execute.
Criterion 2's list of keys with no reader is unchanged, because this one has two.
Which criteria are not plugin work
Named here so nobody hunts for a change that is not there.
Criterion 11 is a scope statement, and this body satisfies it by saying so. There is nothing to
build for it. What it does carry into the plugin is criterion 10's last sentence.
Criterion 14 is the maintainer's, inside DXP-DesignSystem, after the plugin PR ships. So is the
second half of criterion 13, from claude plugin update onwards.
Out of scope
Making DXP-DesignSystem have CI. The Consumer's own missing pipeline, recorded on #373.
coverage_threshold moving. It is a number, not a command, so it stays outside the sdlc_needs
block. Nothing changes about it and no criterion above covers it.
slopcheck. It reads package.json and queries the npm registry, so it is JavaScript-only by
construction, but it self-skips honestly at unic-dlc-build.yaml:106 and no criterion touches it.
unic-dlc-explore.yaml. It runs no build, lint, type-check or test command; its Bash use is
git-shaped. Its package.json and pnpm-workspace.yaml read at :101 is the same class of problem and
is not fixed here.
Running a dependency upgrade as any part of this. Archon is held at 0.7.0 until phase 1 ships its
first stable release.
What this did not decide
Whether a declared command is the right command.RULED 2026-09-03 by the maintainer: this is
the Consumer's problem, not the plugin's. Criterion 16 reduces the chance at declaration time and
criterion 7 reports a zero-count pass as suspicious. Past that, a Consumer that deliberately sets sdlc_needs.test to a no-op passes forever, and nothing detects a semantically wrong command. That is
out of scope by decision, not by omission — a tenant who games their own gate is not an adversary a
Harness can defend against, and trying would make the plugin judge a tenant's toolchain, which is the
thing it refuses to name in the first place. Do not reopen this as a defect; it is a stated ceiling.
Whether lint ever gains a reader.RE-FRAMED 2026-09-03 by the maintainer: not a gap. A key
in this block names a need of software development that a Consumer may have, not a command this
plugin must run. lint having no reader today is the design working: the block declares what the
project can do, and a node reaches for whichever need it has at run time. A key with no reader is
exactly as intended as a key with three. Do not add a lint-running node to close this.
What an absent unresolved means outside the floor. Beyond test at the two gates, a node
deciding it does not need a check reports nothing, and a reader cannot tell that from a node that
never considered it. Accepted as the price of discretion; named here so nobody reads silence as
coverage.
What catches a node added later without the inline rule. Criterion 17 puts the sentence in every
prompt that resolves a command today, so the rule is visible where the work happens. Nothing checks
that the next node to read the sdlc_needs block carries it, and this repository deliberately does not
grep agent prose to find out. Between two hands, a missing sentence surfaces on a read, or not at all.
Whether .archon/workflows/*.yaml belongs in GUARDED. Today a PR changing only a Box merges
with no bump and no changelog entry. Criterion 13 works around it because setup.md is guarded. The
question is a separate ticket if the answer is yes.
Whether a slice's test_command may be narrow at all. That is #448, and criterion 15 only keeps the two
surfaces apart rather than answering it.
What coverage-gate and verification step 3 run once the literal is goneRULED 2026-09-03 19:22 by
the maintainer: option (b), a ninth key coverage. Criterion 1 amended, criterion 20 added; the text below
stays as the record of the two options. (found 2026-09-03 by DLC-430-grilling, verified by the orchestrator). unic-dlc-qa.yaml:134 and unic-dlc-build.yaml:410 say "run coverage" and today know what to run only
through the literal criterion 4 removes. Criterion 1 fixes the block at eight keys with no coverage, and
§ Out of scope keeps coverage_threshold outside the block. So after the edit both nodes hold a threshold
and nothing to execute; an implementer holding the criteria alone adds a ninth key (breaks 1) or keeps the
literal (breaks 4). Two ways out: (a) read the coverage figure out of what sdlc_needs.test reported,
with a null threshold or a null test reported as unresolved, and never append a flag of the Box's own to
a project's command — no new key, and a Consumer whose test emits no coverage gets unresolved whenever a
threshold is set, which is honest; (b) a ninth key coverage, amending criterion 1. The orchestrator
recommends (a) with its cost written into the ADR of criterion 12. Whichever is ruled becomes a criterion.
How criteria 7 and 8 read together at a node that exists to run one need (same source, same date).
"A node that abstains reports nothing" and "unresolved covers the command not being declared" hold together
only if the distinction is wanting a need, not declaring it: the e2e node always wants e2e, so sdlc_needs.e2e: null there is unresolved, never a skip; "reports nothing" covers only a node that never
reached for the check. Consequence: skip leaves the result enum of e2e (unic-dlc-qa.yaml:112) and coverage-gate (:145). Decided by reading, recorded here so the implementer does not keep skip and
satisfy both criteria by changing nothing.
Why run 2's goals-check returned four acceptance-criteria failures. The register's correction
established that the suite really ran and really passed, so the missing install does not explain those
four. Open, not answered.
What this is
A Box cannot check a claim about a build, a lint, a type-check or a test run, and the reason is three
faults stacked on one another. Grilled with the maintainer on 2026-09-03; this body carries the decisions
and the criteria. The two comments below carry the measurements and two corrections to what this body
first said, and they stay on record.
node_modules, 4 do not, and no Box instructs any node to installunic-dlc-pr-reviewinstructs no build, lint, type-check or test command anywhere, and neither does thecode-reviewMethod it hostsgrep -cE "pnpm |lint|typecheck|install|node_modules" unic-dlc-pr-review.yaml→ 0unic-dlc-build.yaml:403falls back to the literalpnpm testThe three interact. Run 2's
verificationreported PASSED and really did run a green suite, because thatworktree happened to have dependencies and the Box happened to fabricate the right command for a pnpm
repository. Both were luck. On a Python or a .NET Consumer the same node runs a command that does not
exist and reports a failure that has nothing to do with the code.
And run 2's honest "node_modules is absent in this worktree, so neither axis could execute" came from a
sub-agent volunteering a check nobody asked it for. That is the instruction axis: an install alone
changes nothing in
pr-review, because nothing there is told to run.Why it matters more than it looks
The review's whole value is checking claims the author makes. On run 1's PR the author claimed a green
build, a clean lint, clean types and a passing test run, and none of the four was checked, so the verdict
rested on reading the diff alone.
DXP-DesignSystemhas no pipeline, so when a Box cannot run a check,nothing mechanical ever runs it. A human on their own machine is the only path, and both runs 1 and 2
ended that way.
The decision
The config becomes the abstract layer that maps what a software development process needs, and every
Box reaches the whole of it. The plugin names no tool, ever. A Consumer fills in whatever its stack
uses, and a node decides at run time which of those needs it has.
Three rules carry the weight:
testis a need.pnpm testis atool.
check-typesis somebody's script name;typecheckis the need. This is the rule alreadywritten at
AGENTS.md:57for formatter exclusions, applied to commands.is mandatory at the two gates that advance work, and abstaining there is a reported non-result, not
silence.
/qaskips without blocking and/buildfabricates. Those are the same defect from opposite ends.
The three-option menu this body used to carry is spent. Options 1 and 2 collapsed into one when the
install cost was measured (4.4 s, 16 MB, warm store, APFS). Option 3 is unavailable, because #430 is an
instrument for run 3 and an instrument that cannot observe is broken, not honest.
Acceptance criteria
The config declares the SDLC's needs in one flat block, named
sdlc_needs. The template/unic-archon-dlc:setupwrites gains a top-levelsdlc_needsblock holding exactly nine nullablekeys:
install,build,test,e2e,lint,format,typecheck,dev,coverage. Every value defaults tonull. Amended 2026-09-03 19:22: the ninth key comes from the maintainer's ruling on did-not-decide item 8,recorded as criterion 20; the first version of this criterion said eight. Amended 2026-09-03 19:35, found by
DLC-430under hold:README.md§ Configuration reference gains one row per key in the same PR.commands/setup.md:262calls that table the source of every default and:285forbids asking the operator for akey the table omits, so criterion 16's proposals are illegal by
/setup's own rule until the rows exist. The name is snake_case, and that is measured rather than chosen by taste: every key in theConsumer's live config is snake_case without exception (
artifacts_dir,coverage_threshold,fresh_context_red_green,out_of_scope_dir), sosdlcNeedsandSDLC_needsboth lose.The block states its own rule, and states which keys nothing reads yet. Its comment says that a
key names a need and never a tool. It also says that
build,lint,formatanddevhave no nodereading them on the day this lands, and are declared anyway because
/setupwrites this file once andthereafter only reports on it, so a key added later costs another
reconfigurein every Consumer.bootstrapemits the whole block as one object, and no prose assigns a key to a node. In allthree Boxes the
bootstrapoutput schema carriessdlc_needsas a single object field. The per-nodescalars
test_commandande2e_commandare gone from those schemas. A reader can check that nonode's prose says a given key belongs to it.
No Box that runs a command names a tool. Three sites go:
unic-dlc-build.yaml:403'selse \pnpm test`,unic-dlc-build.yaml:51's(e.g. "pnpm test")in the inference instruction, andunic-dlc-qa.yaml:134'se.g. pnpm test --coverage. After this, a grep forpnpmacross the three Box files returns **nothing at all**. **Corrected 2026-09-03 19:35 (DLC-430):** the first version said "nothing outsideslopcheck";slopcheckholdsnpm(unic-dlc-build.yaml:84,:130), neverpnpm, so an implementer expecting survivors there would read zero as over-deletion.unic-dlc-explore.yaml:101is the onepnpm` left inthe plugin's workflows and § Out of scope keeps it.
The two
e2e_commandkeys are retired, and the retirement is stated as deliberate./qa'sqa.e2e_command ?? build.e2e_commandtwo-tier resolution is replaced by the singlesdlc_needs.e2e.The per-Box override is removed on purpose, because all four values are
nullin the only Consumerthat exists, so nothing has ever used that tier. The ADR of criterion 12 says so, rather than leaving
it to read as an oversight. Amended 2026-09-03 by the orchestrator, four-reads pass before dispatch:
the two documents that describe those keys change in the same PR —
README.md's config table(
build.e2e_command,build.coverage_thresholdrows) anddocs/boxes/unic-dlc-qa.md(the resolutionline and its config table). Nothing else in this ticket named them, and a reader of either would have
been told a key exists that the config no longer has. Third site, added 19:35 (
DLC-430):commands/setup.md:273,the
**build**line of Step 6's field list, names both retired keys and changes in the same PR.Each Box installs once, at
bootstrap, and says what it did. One Archon worktree serves a wholerun and the build Box's RED and GREEN phases are fresh contexts inside that same worktree, so one
install covers everything. All three Boxes install, with no per-Box exception, and
pr-reviewis notexempt even though criterion 11 gives it no instructed check: run 2 recorded a sub-agent volunteering
a check of its own and being unable to execute it, so an install there decides that case on merit
instead of on which worktree the run drew. One uniform rule is also less to remember than a rule with
an exception.
bootstrapreports either that it ransdlc_needs.install, or thatsdlc_needs.installisnot declared. Where it is not declared, dependencies may still be present by the luck this ticket
exists to remove, so that report is what stops a later green check reading as proof of a deliberately
built environment.
A command outcome has three states, and
unresolvedis never a pass. Every node that runs acommand reports
pass,failorunresolved. Oneunresolvedcovers both causes, the command notbeing declared and the command being declared but unable to run. The cause goes in the prose, not in
the state. A node names which need went unresolved. A
passcarries the counts the command reported,and a
passwith a zero count is reported as suspicious rather than as a green result, because thatis the shape a command declared against the wrong target takes.
Where a Box has a gate that advances work,
unresolvedholds it, and nowhere else blocks./qa's merge and/build'sevidenceare those gates.pr-reviewhas no such gate, and theper-slice loop inside
run-buildis not one either, so an unresolved type-check there reports and theloop continues. Rewritten 2026-09-04 20:45 by the orchestrator on the maintainer's ruling (Q10 of bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's
grilling: "a"), because bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 changes
evidence's mechanism after this ticket lands. The verdictevidenceemits carries three states,pass,failorunresolved, in the JSON it prints to stdout and inthe file it writes, and no reader treats
unresolvedas green. The reader of that verdict is a nodewhen:,never the engine:
evidence_policychecks presence only, and once bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 makes the file unconditional it firesonly when
evidenceitself crashes before writing, which is the backstop bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 keeps it for. Whether thefile is written always or only on green, and which node carries the
when:that stopsopen-pron red,are bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's decisions. This ticket edits
verificationandevidencefor the third state; the sentence inverification's andgoals-check's prompts that tells the reader whatevidencedoes with their outputstates the three-state outcome, and both prompts carry the same sentence, so both change. bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 rewrites
that sentence again for always-write and the backstop, against the tree this ticket leaves, and owns the
evidencenode's comment block whole (the DAG loader parses script-node comments for$node.outputtokens, so nobody tidies it in passing). Between the two merges the printed verdict has no reader, by
design: today nothing in the Box reads
$evidence.output(reportreadsverificationandgoals-checkdirectly), and the
when:that will consume it is bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439's decision. A reviewer who finds a field with noreader in this ticket's diff has found this sentence, not a defect; criterion 10's block reads the unresolved
needs from
verification's output and needs nothing fromevidence./qa'smergewhen:holds onunresolvedthe same way. Added 19:35 (DLC-430): the pluginAGENTS.md§ Plugin doctrinesevidence-gate bullet (
AGENTS.md:91) says the node writes "only when both are true"; that sentence is revised inthe same PR, one layer up from ADR-0034, the same way criterion 5 names the documents that describe retired keys.
The floor:
/qa's merge gate and/build'sevidenceverdict require atestoutcome. Notestoutcome at either is
unresolved, which criterion 8 then holds. No other need is mandatory anywhere.(Reworded 2026-09-04 with criterion 8: "cannot advance" described the withholding mechanism bug(unic-archon-dlc): open-pr precedes its gate, and a config-value gate has no safe middle #439 retires;
the floor itself is unchanged.)
A check a Box could not verify reaches the reader as its own visible block, not as a sentence in
a paragraph. Any summary that carries a check outcome or an install report also carries, in a
structurally separate block a developer skimming cannot pass over, the needs that went
unresolvedand whether
sdlc_needs.installwas declared. A reviewer checks this by reading the summary's shape,not its wording.
pr-review's summary states, in that same block, which commands the configdeclares and that this Box executes none of them by instruction. Criterion 11 is the reason, and
without this sentence that admission lives only in this ticket, where the developer reading a review
never sees it. Amended 2026-09-03 by the orchestrator, four-reads pass before dispatch: the block is
durable — it lands in a file under
<artifacts_dir>/<slug>/, the repo-relative Session dir, and ablock that exists only in a node's console output does not satisfy this criterion. Without this sentence
/qasatisfies it in scrollback: itse2eandcoverage-gatesummaries have no reader in the DAG andthe Box has no report node, so the block would die with the worktree exactly like the summary it sits
in. Found by
DLC-438-grillingwhile grilling bug(unic-archon-dlc): a review node produced thirteen findings and recorded them nowhere #438, verified from the orchestrator seat.pr-reviewgains no procedure that runs a check against an author's claim. That procedurebelongs to a Method, not to the Harness (ADR-0030), and its home is feat(unic-archon-dlc): the review angles become a Method of ours, harvested from unic-pr-review and rewritten stack-agnostic #433 as a named review angle.
So the title of this ticket stays partly true after this lands:
pr-reviewstill runs no lint and notype-check against a claim. What it can no longer do is stay quiet about that. This ticket's body says
so rather than leaving a reader to discover it.
An ADR records the config-as-abstraction decision, referenced by slug and never by number. It
carries the need-not-a-tool rule, the discretion-to-add-never-to-subtract rule, the floor, and the
retirement of the two
e2e_commandkeys. It also records three things a later reader would otherwisere-derive. Why the block is
sdlc_needs: a key names a need, so a name that named the tools aneed resolves to would encode the opposite of the block's own rule, which is why
toolchainlost;commandsis reserved twice over andcapabilitiesis taken. Where SDLC comes from: Anthropic'sThe AI-Native SDLC playbook (https://claude.com/blog/the-ai-native-sdlc-playbook), which defines
the lifecycle as idea to production across Plan, Design, Build, Test, Deploy and Maintain, and names
build, test and lint commands alongside linting, formatting and type-checking. Eight of the nine keys come
from that vocabulary;
coveragecomes from criterion 20 and the ADR says so. And that criterion 16 widens an existing sweep rather than adding a third,with the reason: a sweep that re-reads the same manifests for a second purpose is the drift this
plugin keeps filing defects about.
The delivery is named, and it ends inside the Consumer.
commands/setup.mdis inGUARDEDinpackages/release-tools/scripts/lib/changelog-gate.mjs, so the PR carries a version bump and achangelog entry. Reaching run 3 then needs
claude plugin updateinDXP-DesignSystem, whose.claude/settings.jsonsets"autoUpdate": false, then/unic-archon-dlc:setup, which reportsthe missing block and changes nothing because the config is tenant-owned, then
/unic-archon-dlc:setup reconfigure, which is the one override that rewrites it. A criterion thatended at "the template gains a block" would be satisfied by a change run 3 never sees.
After the delivery,
DXP-DesignSystem's config carries real values forsdlc_needs.installandsdlc_needs.test. Without them criterion 4 has removed the fabricated fallback and criterion 9 holdsthe gates, so run 3's build leg stops at
unresolved. That is the correct failure and it still stopsthe run.
The two
test_commandsurfaces stay distinct, and a reader can tell which is which./ticketswrites a per-slice
test_commandintoissues.json(commands/tickets.md:165), read by the RED andGREEN phases at
unic-dlc-build.yaml:279and:296. That surface is untouched. Onlybootstrap'sproject-wide inference becomes
sdlc_needs.test. The name collision that made this hard to see goesaway, and nothing rewires the per-slice command by mistake.
/setupproposes the values from the stack it detected, and never asks blind — by widening thesweep it already runs, not by adding a third. Step 4's second sweep, The toolchain that formats or
lints at
commands/setup.md:148, already reads "the manifests, task files and scripts this projectactually runs — whatever the language" and keeps only what formats or lints, as
FORMATTERS. Thatsame read now harvests the whole of the SDLC's needs, and the sweep's heading widens with it. It is
not the Step 4 Capabilities sweep at
:138, which builds a different registry (mcp | cli | skill,tool availability) and keeps both its name and its purpose. The harvested values are offered as
proposals a human confirms or corrects, one need at a time, so a Consumer running Python sees a
proposal for
testdrawn from what was detected rather than an empty prompt. This keeps a wrong valuefrom being the path of least resistance; it does not stop a deliberate one.
Every node that resolves a command carries the need-not-a-tool rule inline. The sentence lives
in each prompt that reads the
sdlc_needsblock, not only in the ADR of criterion 12. The rootAGENTS.mdstates the reason under its quality bar: a rule that lives only in anAGENTS.mdisinvisible at run time, and every prompt that must honour a rule carries it inline.
AGENTS.md:57already carried this rule for formatter exclusions and the plugin broke it in five places, which is
the evidence that a document-only home does not hold. A reader finds the sentence in the prompt.
CONTEXT.mdgains an entry forsdlc_needs, with the forms that lose named. The entry sayswhat the block holds, that a key names a need and never a tool, and that a key with no reader is
intended rather than a gap. Its
_Avoid_line namescommands(the bare word is already forbidden atCONTEXT.md:241, where a Claude Code slash command claims it and an Archon workflow command templateclaims it again),
toolchain,capabilities(taken by System-skill atCONTEXT.md:38for ateam-provided capability) and
lifecycle_needs. This criterion exists because the first draft ofthis ticket named the block
commands, breaking a ruleCONTEXT.mdalready stated — the samefailure criterion 17 was written against, one layer up.
SDLC becomes this plugin's term, declared once, and "AI development lifecycle" becomes its
gloss. The acronym appears zero times in the plugin today, while "AI development lifecycle"
appears at
CONTEXT.md:3,README.md:3,AGENTS.md:7andAGENTS.md:47. Adopting an acronym for aconcept the plugin already names another way is synonym drift unless it is managed, which is what
CONTEXT.mdexists to stop. SoCONTEXT.mdgains an entry for SDLC, expanded, naming the articleof criterion 12 as its source and its six phases where they earn their place; it states which of the
two is the term and which is the gloss; and the four sites above are made consistent with that ruling
in the same commit. A reader cannot come away thinking they are two different things.
coverageis the ninth key, and the number stays outside the block. Ruled by the maintainer on2026-09-03 19:22, option (b) of did-not-decide item 8.
sdlc_needs.coveragenames the need that produces acoverage figure;
build.coverage_thresholdstays where it is, because it is a number and not a need, and§ Out of scope still holds for it.
coverage-gate(unic-dlc-qa.yaml:120) andverificationstep 3(
unic-dlc-build.yaml:410) resolvesdlc_needs.coverageand nothing else once criterion 4 removes theliteral; neither appends a flag of its own to
sdlc_needs.testor to any other declared command. A nullcoverage, or a declared one that cannot run, isunresolvedunder criterion 7. The threshold decides passagainst fail; whether the node reports at all is item 9's ruling, not this criterion's. The ADR of
criterion 12 records why the key exists: without it both nodes hold a threshold and nothing to execute.
Criterion 2's list of keys with no reader is unchanged, because this one has two.
Which criteria are not plugin work
Named here so nobody hunts for a change that is not there.
build for it. What it does carry into the plugin is criterion 10's last sentence.
DXP-DesignSystem, after the plugin PR ships. So is thesecond half of criterion 13, from
claude plugin updateonwards.Out of scope
DXP-DesignSystemhave CI. The Consumer's own missing pipeline, recorded on#373.
coverage_thresholdmoving. It is a number, not a command, so it stays outside thesdlc_needsblock. Nothing changes about it and no criterion above covers it.
slopcheck. It readspackage.jsonand queries the npm registry, so it is JavaScript-only byconstruction, but it self-skips honestly at
unic-dlc-build.yaml:106and no criterion touches it.unic-dlc-explore.yaml. It runs no build, lint, type-check or test command; itsBashuse isgit-shaped. Its
package.jsonandpnpm-workspace.yamlread at:101is the same class of problem andis not fixed here.
first stable release.
What this did not decide
Whether a declared command is the right command.RULED 2026-09-03 by the maintainer: this isthe Consumer's problem, not the plugin's. Criterion 16 reduces the chance at declaration time and
criterion 7 reports a zero-count pass as suspicious. Past that, a Consumer that deliberately sets
sdlc_needs.testto a no-op passes forever, and nothing detects a semantically wrong command. That isout of scope by decision, not by omission — a tenant who games their own gate is not an adversary a
Harness can defend against, and trying would make the plugin judge a tenant's toolchain, which is the
thing it refuses to name in the first place. Do not reopen this as a defect; it is a stated ceiling.
WhetherRE-FRAMED 2026-09-03 by the maintainer: not a gap. A keylintever gains a reader.in this block names a need of software development that a Consumer may have, not a command this
plugin must run.
linthaving no reader today is the design working: the block declares what theproject can do, and a node reaches for whichever need it has at run time. A key with no reader is
exactly as intended as a key with three. Do not add a lint-running node to close this.
unresolvedmeans outside the floor. Beyondtestat the two gates, a nodedeciding it does not need a check reports nothing, and a reader cannot tell that from a node that
never considered it. Accepted as the price of discretion; named here so nobody reads silence as
coverage.
prompt that resolves a command today, so the rule is visible where the work happens. Nothing checks
that the next node to read the
sdlc_needsblock carries it, and this repository deliberately does notgrep agent prose to find out. Between two hands, a missing sentence surfaces on a read, or not at all.
.archon/workflows/*.yamlbelongs inGUARDED. Today a PR changing only a Box mergeswith no bump and no changelog entry. Criterion 13 works around it because
setup.mdis guarded. Thequestion is a separate ticket if the answer is yes.
test_commandmay be narrow at all. That is#448, and criterion 15 only keeps the two
surfaces apart rather than answering it.
WhatRULED 2026-09-03 19:22 bycoverage-gateandverificationstep 3 run once the literal is gonethe maintainer: option (b), a ninth key
coverage. Criterion 1 amended, criterion 20 added; the text belowstays as the record of the two options. (found 2026-09-03 by
DLC-430-grilling, verified by the orchestrator).unic-dlc-qa.yaml:134andunic-dlc-build.yaml:410say "run coverage" and today know what to run onlythrough the literal criterion 4 removes. Criterion 1 fixes the block at eight keys with no
coverage, and§ Out of scope keeps
coverage_thresholdoutside the block. So after the edit both nodes hold a thresholdand nothing to execute; an implementer holding the criteria alone adds a ninth key (breaks 1) or keeps the
literal (breaks 4). Two ways out: (a) read the coverage figure out of what
sdlc_needs.testreported,with a null threshold or a null
testreported asunresolved, and never append a flag of the Box's own toa project's command — no new key, and a Consumer whose
testemits no coverage getsunresolvedwhenever athreshold is set, which is honest; (b) a ninth key
coverage, amending criterion 1. The orchestratorrecommends (a) with its cost written into the ADR of criterion 12. Whichever is ruled becomes a criterion.
"A node that abstains reports nothing" and "
unresolvedcovers the command not being declared" hold togetheronly if the distinction is wanting a need, not declaring it: the
e2enode always wantse2e, sosdlc_needs.e2e: nullthere isunresolved, never a skip; "reports nothing" covers only a node that neverreached for the check. Consequence:
skipleaves theresultenum ofe2e(unic-dlc-qa.yaml:112) andcoverage-gate(:145). Decided by reading, recorded here so the implementer does not keepskipandsatisfy both criteria by changing nothing.
goals-checkreturned four acceptance-criteria failures. The register's correctionestablished that the suite really ran and really passed, so the missing install does not explain those
four. Open, not answered.