Skip to content

bug(unic-archon-dlc): the config maps commands to nodes and the Boxes name tools, so a check runs by luck or not at all #430

Description

@orioltf

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.

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 grep -cE "pnpm |lint|typecheck|install|node_modules" unic-dlc-pr-review.yaml → 0
Configuration 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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 merge when: 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.

  9. 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.)

  10. 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.

  11. 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.

  12. 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.

  13. 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.

  14. 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.

  15. 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.

  16. /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.

  17. 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.

  18. 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.

  19. 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.

  20. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. What coverage-gate and verification step 3 run once the literal is gone RULED 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.
  8. 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.
  9. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app:unic-archon-dlcbugSomething isn't workingp1High priorityready-for-agentFully specified, ready for an AFK agentwayfinder:grillingWayfinder ticket: decide it by grilling

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions