📝 docs: two false claims about what assent writes — --arm and REVIEW auto-merge (D-134/D-135, blocks v0.2.0) - #40
Merged
Merged
Conversation
`cfg.arm` had zero discriminating coverage. Proven, not asserted: on the pristine tree, replacing `summarize(result.Decision, cfg.arm, …)` with `summarize(result.Decision, true, …)` at run.go:447 leaves the whole cmd/assent package `ok` — the ~30 tests that pass `--arm` do so ritually, and deleting the flag would red them on flag-parse error, not behaviour. Since c05cde0 (E4-S06) the forge-probed PreconditionProbe gates approve and merge, so the `arm=<bool>` token in the run summary is the flag's ONLY observable effect. TestRunArmFlagIsAdvisoryOnly pins both polarities of that token and pins the advisory semantics itself: the write outcome and the rest of the summary are identical with and without the flag. Not red-first: cfg.arm is already threaded to summarize, so there is no implementation to add. The proof obligation is mutation-kill instead, run at authoring time and recorded in the test's own doc comment: summarize(…, true, …) -> reds without_flag_reports_arm_false + flag_changes_nothing_but_the_token summarize(…, false, …) -> reds with_flag_reports_arm_true
The --arm help string and two doc comments promised approve/merge happen "only when set AND decision APPROVE". False since c05cde0 (E4-S06), which replaced `Preconditions.ArmEligible: cfg.arm` with the forge-probed `armEligible` and updated buildDesired's own note but nothing else — `git show c05cde0 -- cmd/assent/run.go | grep BoolVar` returns nothing. Because the default is false, EVERY run that never passes --arm was in the state the help string described as safe. Measured through runRun against fakeGitLab on an APPROVE fixture: args=[] -> exit=0 approvals=1 merges=1, "decision=APPROVE arm=false -> 3 forge operation(s) written". Four surfaces, zero behaviour change: run.go:86 runRun exit-code comment: names the forge-probed precondition run.go:136 flag help: advisory only, points at `assent doctor` provider_host.go:34,102 "ArmEligible stays --arm and APPROVE" -> the forge-probed PreconditionProbe (:102 was a second copy of the same false claim, not in the reported finding) The help string carries no backquotes on purpose: flag.PrintDefaults reads `...` as the value-name placeholder, which rendered "-arm assent doctor".
…-134) cli.md shipped a false safety guarantee: the flag row promised approve and merge "only when set AND the decision is APPROVE", and the exit-codes paragraph read "an APPROVE without --arm is still a clean 0" — the exit code right, the implied no-write wrong. Both are false since c05cde0. The unsafe path needed no operator error: follow walkthrough.md, copy its CI snippet (which omits --arm), harden the project until `assent doctor` reports the precondition met, and the pipeline starts approving and merging while this page said it would not. Every claim below was measured through runRun against fakeGitLab, not transcribed: eligible, args=[] exit=0 approvals=1 merges=1 "decision=APPROVE arm=false -> 3 forge operation(s) written" eligible, args=[--arm=false] identical eligible, args=[--arm=true] exit=0 approvals=1 merges=1 C3 gate missing, --arm exit=0 approvals=0 merges=0 "decision=APPROVE arm=true -> advisory-only (arming precondition unmet, no approve/merge)" .assent/** self-edit exit=0 approvals=0 merges=0 discussions=0 "decision=BLOCK -> assent-policy self-edit, no forge writes (fail-closed)" Adds a "What gates approve and merge" section naming the three default-deny preconditions from internal/forge/precondition.go (C17 protected external CI, C3 discussions-resolved gate, C6/C7 non-Free approval-rules API), their typed refusal codes, the four further guards, and how to actually stay advisory. The flag row and run.go's help string are kept substantively identical, since line 75 claims `assent run -h` prints the same list.
Records that --arm ceased to be a necessary condition for approve/merge at c05cde0 (E4-S06) and is now purely cosmetic, and explicitly supersedes D-041's operator-facing claim "unarmed APPROVE -> advisory-only, zero writes", which has been false since with no later row retracting it. Superseded, not edited: D-041 stays byte-identical (verified against origin/main). The new row states that D-041 was TRUE when recorded -- `git merge-base --is-ancestor 4addc3d c05cde0` confirms its evidence commit precedes the one that invalidated it -- so the supersession names when the claim expired rather than implying it was wrong on arrival. Append-only: the diff adds exactly one line.
Review catch on my own deliverable. D-134 asserted the walkthrough prose
"describes doctor and forge-armed auto-merge accurately" at lines 134/149.
Line 149 was never measured, and it does not hold: it says that on the REVIEW
path assent "had already armed the forge's auto-merge ... the moment the last
thread is resolved, GitLab itself merges".
Measured instead: buildDesired's REVIEW/BLOCK branch sets desired.Thread only
and returns a zero forge.Preconditions{} (no Approve, no Merge, no
ArmEligible), and the forge.Forge port has no auto-merge verb at all --
Approve and MergeCAS are the only writes, and MergeCAS is an immediate
compare-and-swap. A require-review-unproven run with --arm gives exit=0
approvals=0 merges=0 discussions=1, "decision=REVIEW arm=true -> 2 forge
operation(s) written".
That is a SECOND, DIFFERENT docs defect from the --arm one. It is recorded in
D-134 as newly found and deliberately left for its own lane with its own
measurement and its own row, rather than folded in here.
The D-row claim is narrowed to what was actually checked: the CI snippet omits
--arm and no surrounding prose reintroduces the --arm claim.
Also drops the numeral from cli.md's "Four further guards" -- D-134 enumerates
five (it adds completeForMerge()), and two surfaces written in one lane should
not disagree on a count.
Operator override: fixed in this lane rather than deferred, on the same
reasoning that made the --arm defect tag-blocking -- walkthrough.md ships
with the release, so republishing a known-false claim about what assent does
to other people's merge requests is a knowing act.
walkthrough.md:149 read: "assent had already armed the forge's auto-merge,
pinned to the evaluated commit -- so the moment the last thread is resolved,
GitLab itself merges (ADR-0009 amendment)."
This is implementation-vs-design DRIFT, not a transcription slip. ADR-0009's
challenge-resolution amendment really does specify "approves conditionally and
arms forge auto-merge pinned to the evaluated SHA". The mechanism was never
built. Verified three ways:
- buildDesired's REVIEW/BLOCK branch sets desired.Thread only and returns a
zero forge.Preconditions{} -- no Approve, no Merge, no ArmEligible;
- the forge.Forge port has NO deferred-merge verb. Approve and MergeCAS are
the only write verbs and MergeCAS issues an immediate PUT /merge?sha=;
grep for merge_when/auto_merge/when_pipeline over internal/ + cmd/ returns
nothing but AutoMergeEligible, a doctor-report field never used for arming;
- Thread.Resolved is read only inside internal/forge's thread-idempotence
logic, never by internal/core -- resolution is not evidence.
Measured through runRun against fakeGitLab, replaying the walkthrough's own
narrative (require-review unproven):
run 1 exit=0 threads=1 notesPosted=1 approvals=0 merges=0
"decision=REVIEW arm=false -> 2 forge operation(s) written"
contributor resolves every thread
run 2 exit=0 threads=1 notesPosted=1 notesUpdated=1
approvals=0 merges=0, decision REVIEW unchanged
The MR never merges.
Direction of the error, which differs from D-134's: this OVER-states
automation, so its risk direction is fail-safe -- no unintended write follows
from it. It is still false rather than merely optimistic: the mechanism is
absent, and the failure mode is someone waiting indefinitely for a merge that
will never arrive with no diagnostic saying why. A "what this means in
practice" callout closes that loop.
The Step 6 banner keeps its `> **Shipped**` prefix (DOC-09 pin) and now names
the unimplemented arming as an explicit exception.
NOT changed: ADR-0009 itself. Its amendment still specifies the unbuilt
mechanism. Amending an ADR is an architecture decision, and the honest options
-- build the deferred arming, or retract the amendment -- are the operator's
call, not this lane's. Flagged as an open item in D-135.
Third-surface sweep, prompted by the D-135 fix making the word "armed"
load-bearing. Grep for "merge when|when pipeline|pipeline succeeds|forge
merges|GitLab itself|when every thread" over docs/, README.md and cmd/ finds
NO user-facing string promising DEFERRED arming:
- `assent doctor` prints "arming precondition MET -- forge-probed,
auto-merge may be armed" / "advisory-only -- auto-merge NOT armed", which
is assent's own ArmEligible vocabulary: permission to perform the
IMMEDIATE approve+merge, which exists;
- README, docs/index.md and docs/vision.md use "auto-merge" as the product
category (assent merges automatically), not the forge's deferred feature;
- the only surviving deferred-arming text is ADR-0009's amendment (left to
its own amendment per D-135) and the two docs/planning/forge-dossier-*.md
research notes, which are out of the mkdocs nav per GUIDELINES and
correctly describe what the FORGE can do (GitLab C11), not what assent
does.
So no further defect -- but cli.md's `assent doctor` section is a surface this
lane already edited, and a reader arriving there after the walkthrough
correction needs to know which arming is meant. One sentence added: "armed"
means assent may approve and merge immediately and SHA-pinned, not that it
hands the forge a deferred merge-when-pipeline-succeeds instruction.
The doctor SYNOPSIS line is deliberately untouched -- it is pinned verbatim by
TestCLIDocCoversSubcommands against main.go's dispatch table, so rewording it
would be a CLI change rather than a docs change.
…ted (D-135)
A truth annotation, not a design change. ADR-0009 ships with the docs site --
mkdocs.yml's nav carries `ADRs: adr/README.md`, whose index links every ADR
page -- so it was a published document specifying a mechanism the tool does
not have, with a Status caveat ("Accepted (partial: one-shot arming
restrictions per ADR-0017 §4; P2-E5)") that does not name the gap.
The Amendment now carries an "Implementation status" note stating that point
1's deferred forge auto-merge is not implemented, with the three code facts
that establish it (zero forge.Preconditions{} on the REVIEW/BLOCK branch; no
deferred-merge verb in the forge.Forge port; Thread.Resolved confined to
thread-idempotence) and the end-to-end measurement, and naming the two honest
resolutions as OPEN OPTIONS without picking one:
(a) build the deferred arming (GitLab C11: PUT .../merge with
auto_merge=true combined with sha=), or
(b) retract the amendment and re-derive the challenge-resolution story
around the immediate merge that exists.
The amendment's normative text is untouched and the decision stays open.
Recording that a specified mechanism is unbuilt is a verifiable statement of
fact (AGENTS.md rule 6); choosing what to do about it is the architecture
decision, and that remains the operator's.
Placed in the amendment BODY by choice, not by constraint. Probed empirically:
appending to the Status row and re-running hack/docs/truthlag_pins_test.sh
still gives "PASS ADR index: 20 ADR status rows agree with their files" -- the
pin compares only the first whitespace token of the Status cell against the
index column, so "Accepted ..." stays green however much follows. Status is
nonetheless the wrong home: it is the ADR lifecycle field the index renders, an
implementation gap is a different axis, and trailing free-text there would
never surface on the index page a reader scans.
D-135 and the ADR note now link to each other in both directions.
…Q-29 Review REQUEST CHANGES on PR #40. One P1 introduced by my own diff, plus four findings landed while in these files. ARM-08 (P1, self-inflicted): the "stay advisory" advice I added to cli.md recommended `phase: observe`, which is false in the UNSAFE direction, twice. Both measured, not transcribed: (i) CoverWithPhaseCeiling over an enforcing BLOCK rule -- require:[signal] enforce/observe/off -> BLOCK / REVIEW / REVIEW no require: enforce/observe/off -> BLOCK / APPROVE / APPROVE Structural exclusion removes the findings withholding approval. Only an enforce-phase rule marks a required obligation covered (coverage.go:150), so `require:` is the only thing holding the line -- and it is OPTIONAL in the frozen schema ("Absent or empty => vacuously covered"), which is the first-pack-rollout state the advice targeted. (ii) spec.phase is inert unless --pack is passed (run.go:247). Against a fake serving no pack route: no --pack -> exit=0 approvals=1 merges=1 (proving no pack is read); with --pack -> the read happens and fails loudly. Edit the manifest, change nothing else, stay fully enforcing -- ARM-01's exact shape reintroduced by ARM-01's fix. Replaced with the measured truth: no dry-run mode exists (`assent run --dry-run` exits 2, "flag provided but not defined: -dry-run", verified on the built binary), no phase is a safety switch (`off` approves too), and the only reliable lever is leaving an arming precondition unmet. The decision matrix is published rather than summarised. SURF-07: ADR-0009's mode table specifies six entrypoints; only `run` exists. explain/serve/scan/stats all exit 2. Annotated with the same restraint as the amendment -- fact recorded, normative text untouched, options named, neither picked -- following the "Planned -- assent explain does not exist" precedent. This is the ROOT CAUSE of ARM-08: the sanctioned advisory mechanism was never built, so the docs substituted one, and the substitute was unsafe. ADR9-ST: Status row extended to "Substantially unimplemented as of 2026-08-09" so an index reader sees the gap. Free -- the pin reads only the first token. SURF-09: the p4-e1-s11 evidence README keeps its historical text verbatim and gains a note that --arm semantics changed at c05cde0, pointing at D-134. D135-CAL: corrected my own overstatement. D-135 said the walkthrough defect was "tag-blocking on the same reasoning" as D-134. Same class, not same reasoning: ARM-01 blocked because it ran UNSAFE on the primary surface; this one over- states automation and fails loudly and recoverably. Standing alone it is P2. SURF-08 -> OQ-29 (recorded, NOT fixed): PolicyProfile.spec.writes:false is a frozen-schema field documented as an architectural invariant ("Never calls Reconcile") with zero runtime enforcement -- cmd/assent/run.go contains "Profile" zero times -- while the single-writer-profile lint hard error compels adopters to author it. Needs an operator ruling, not a docs edit.
…he arm-docs lane
Only CHANGELOG.md conflicted; it is regenerated in the next commit. Both
append-only tables auto-merged with nothing lost:
decisions.md 129 rows on each parent -> 131 merged (D-129/D-130 from
#36 plus D-134/D-135 from this lane); no D-id missing from
either side.
open-questions.md no OQ-id missing from either side; OQ-28 (#36) and OQ-29
(this lane) both present.
Subject is `:wrench: chore(release):` on purpose -- cliff.toml skips exactly
that and `:memo: chore(release):`, and everything else falls through to the
`.*` catch-all and would render a merge subject into the changelog (D-125).
The "How to keep assent advisory" section opens with "There is no dry-run mode today", and the reader it is written for is looking for a safe trial run against real merge requests. It never said the run still comments on them. Measured through runRun with the C3 gate absent (arming precondition unmet -- the state the section recommends): APPROVE threads=0 notesPosted=1 approvals=0 merges=0 REVIEW threads=1 notesPosted=1 approvals=0 merges=0 BLOCK (ordinary effect) threads=1 notesPosted=1 approvals=0 merges=0 BLOCK (.assent self-edit) threads=0 notesPosted=0 approvals=0 merges=0 forge.Reconcile posts the summary comment before the arming branch is reached, so it lands on every path except the self-edit guard. The reviewer's suggested clause was "(and, on REVIEW/BLOCK, one resolvable thread)". That holds for REVIEW and for an ordinary block effect, but NOT for the .assent/** self-edit BLOCK, which writes nothing at all -- every run-path BLOCK fixture in the suite happens to be that guard, so an ordinary effect: block policy was authored to measure the other half honestly. The exception is named rather than omitted: this page has already shipped two false safety instructions, so a claim with a known counterexample is not worth the brevity. Scope held: the advisory definition is not restated beyond the clause itself, the phase matrix is untouched, and cliff.toml is left to lane/cliff-skip-merge-commits (CL-01).
konih
added a commit
that referenced
this pull request
Aug 9, 2026
…ng it (CL-02/CL-03)
Review finding CL-02: D-136's first version killed D-125's reason for
`if: github.event_name != 'pull_request'` and immediately asserted a successor
reason nobody had measured — the same defect one release later, inside the
commit that corrects it.
The successor reason ("the merge ref carries every commit landed on main since
the branch forked, so the render is a union the branch's file cannot match") was
measured three ways and is false:
1. PR #41's live refs/pull/41/merge (491bb2a, head 49eebb3 into base 7513d79),
rendered with the new cliff.toml -> `verify-changelog: ok`, 0 diff lines.
2. The direct counterexample: the same head merged into a main that HAD moved
(1d8aa60, containing PR #40) -> `verify-changelog: ok`, 0 diff lines.
3. Synthetic sandbox, both sides adding a commit to the same cliff group and
both regenerating -> `CONFLICT (content): Merge conflict in CHANGELOG.md`,
so the PR is unmergeable, no merge ref is minted and the gate never runs.
The mechanism overlooked: the merge ref's CHANGELOG.md is not "the branch's
committed file", it is the three-way merge RESULT, which already contains the
base's lines.
A fourth measurement fell out of this lane's own merge: merge DIRECTION decides
the render order. Merging origin/main INTO the lane (lane as first parent)
auto-merges CHANGELOG.md cleanly and then reds verify-changelog on ordering
alone, while the base-first direction GitHub actually mints matched. Recorded
too, because it is the "third outcome" the first draft of the OQ called untested.
D-136 now retires the premise without replacing it, and states plainly that the
guard is retained WITHOUT a demonstrated reason because changing CI gating needs
its own evidence. OQ-30 carries all four measurements and the ruling needed. The
four sites that asserted the dead premise say only that and point at OQ-30; the
gate test's section 3 assertion now pins the guard's PRESENCE as recorded state
rather than claiming a mechanism.
CL-03: cliff.toml's cost sentence now cites section 7b as the measurement behind
"the merged branch's own commits still render", which is the whole safety
argument for skipping on shape rather than text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P1 blocking the v0.2.0 tag — documentation only, zero behaviour change
assentapproves and merges other people's merge requests. The--armflag's helpstring and the published CLI reference promised approve/merge happens "only when set
and the decision is APPROVE." That has been false since
c05cde0(E4-S06, 2026-08-04),which replaced
Preconditions{ArmEligible: cfg.arm}with the forge-probedarmEligibleand updatedbuildDesired's own comment but nothing else —git show c05cde0 -- cmd/assent/run.go | grep BoolVarreturns nothing.The default is
false, so every run that never passes--armwas in the state thehelp string described as safe. The unsafe path needs no operator error: follow
walkthrough.md, copy its CI snippet (which omits--arm), harden the project untilassent doctorreports the precondition MET, and the pipeline begins approving andmerging while
cli.mdsays it will not.cli.mdships with the release, sore-publishing it under a new tag is a knowing act.
Measured, not transcribed
Through the production entry point
runRunagainst the repo's ownfakeGitLab:[]decision=APPROVE arm=false → 3 forge operation(s) written[--arm=false][--arm=true]decision=APPROVE arm=true → 3 forge operation(s) written[--arm]… → advisory-only (arming precondition unmet, no approve/merge)[].assent/**self-edit[]decision=BLOCK → assent-policy self-edit, no forge writes (fail-closed)What is NOT changed
The engine behaviour is correct and intentional (D-074/D-078,
REQ-E4-S06-05) and ispinned by
TestRunForgeProbedArmingGatesWrites, which passes no--armand asserts thewrites happen. Making
--armgate writes, or removing it, are post-tag follow-ups needingan openspec change proposal and their own red tests.
Surfaces corrected
cmd/assent/run.go:136— flag help: advisory only, points atassent doctorcmd/assent/run.go:86—runRunexit-code commentcmd/assent/provider_host.go:34and:102— a repo-wide sweep found a secondcopy of "ArmEligible stays
--arm∧ APPROVE", outside the reported findingdocs/usage/cli.md— flag row + exit-codes paragraph, plus a newWhat gates approve and merge section naming the three default-deny preconditions
(C17 / C3 / C6-C7), their typed refusal codes, the four further guards, and how to
actually stay advisory
docs/usage/walkthrough.md— its CI snippet already omits--armand no prosereintroduces that claim, but Step 6 carried a different false claim, now also fixed
here as D-135 — see below
The flag help carries no backquotes on purpose:
flag.PrintDefaultsreads`as thevalue-name placeholder, and a first draft rendered as
-arm assent doctor.D-134 supersedes D-041
D-041 stays byte-identical (verified against
origin/main); the diff todecisions.mdadds exactly one line. D-134 retracts D-041's live-forge claim"unarmed APPROVE → advisory-only, zero writes" and states that it was true when
recorded —
git merge-base --is-ancestor 4addc3d c05cde0confirms its evidence commitprecedes the one that invalidated it.
ARM-04 closed
cfg.armhad zero discriminating coverage — verified, not accepted: on the pristinetree, mutating
summarize(result.Decision, cfg.arm, …)tosummarize(…, true, …)atrun.go:447leaves the wholecmd/assentpackageok.TestRunArmFlagIsAdvisoryOnlypins the
arm=<bool>token — the flag's only remaining observable effect — in bothpolarities, plus the advisory semantics itself (write outcome and rest of summary
identical with and without the flag). Mutation-proven each direction:
…, true, …→ redswithout_flag_reports_arm_false+flag_changes_nothing_but_the_token…, false, …→ redswith_flag_reports_arm_trueDeliberately not red-first:
cfg.armwas already threaded tosummarize, so there was noimplementation to add; mutation-kill is the honest form of the TDD obligation here, and
the test's doc comment says so.
Gates
task checkexit 0, all 14 stages: fmt · vet · lint · test · coverage · build ·dogfood-comparison · compare-exitgate-test · changelog-verify · release-changelog-gate-test ·
release-verify-tag-gate-test · docs-gates · lint-depguard-test · lint-workflow-pins-test.
Plus
go vet -tags e2e ./...clean. No docs truth-lag pin was keyed to the old wording.Second defect, ALSO fixed here (D-135) — operator override
walkthrough.md:149claimed that on the REVIEW path "assent had already armed the forge'sauto-merge… the moment the last thread is resolved, GitLab itself merges (ADR-0009
amendment)." This is implementation-vs-design drift, not a transcription slip: ADR-0009's
challenge-resolution amendment really does specify "approves conditionally and arms forge
auto-merge pinned to the evaluated SHA". The mechanism was never built.
Verified three independent ways:
buildDesired's REVIEW/BLOCK branch setsdesired.Threadonly and returns a zeroforge.Preconditions{}— noApprove, noMerge, noArmEligible.forge.Forgeport has no deferred-merge verb at all.ApproveandMergeCASarethe only write verbs and
MergeCASissues an immediatePUT /merge?sha=. A grep formerge_when/auto_merge/when_pipelineacrossinternal/+cmd/returns nothingbut
AutoMergeEligible— a doctor-report field never consulted for arming.Thread.Resolvedis read only insideinternal/forge's thread-idempotence logic, never byinternal/core. Thread resolution is not evidence and cannot move a decision.Measured through
runRunagainstfakeGitLab, replaying the walkthrough's own narrative:The MR never merges. Rerun idempotence holds — no duplicate thread; the summary comment is
updated in place.
Direction of the error, which differs from the
--armone: this over-statesautomation, so its risk direction is fail-safe — no unintended write can follow from it.
It is nonetheless false rather than merely optimistic: the mechanism is absent from the
codebase, and the failure mode is someone waiting indefinitely for a merge that will never
arrive with no diagnostic saying why. Same defect class as D-134 (a false claim about tool
behaviour in docs that ship with the release), so tag-blocking on the same reasoning.
Step 6's prose now states the measured behaviour, its
> **Shipped**banner (kept for theDOC-09 pin) names the unimplemented arming as an explicit exception, and a What this means
in practice callout closes the operator loop.
ADR-0009 — annotated, NOT decided. The ADR ships with the docs site (
mkdocs.ymlnavcarries
ADRs: adr/README.md, whose index links every ADR page), so it was a publisheddocument specifying a mechanism the tool does not have. Its Amendment now carries an
Implementation status note recording that point 1's deferred forge auto-merge is
unimplemented, citing D-135, with the three code facts and the measurement — and naming the
two honest resolutions as open options without picking one: (a) build the deferred arming
(GitLab C11), or (b) retract the amendment and re-derive the challenge-resolution story around
the immediate merge that exists. The amendment's normative text is untouched and the
decision stays open — recording that a specified mechanism is unbuilt is a verifiable fact
(AGENTS.md rule 6); choosing what to do about it is the architecture decision and remains the
operator's. D-135 and the ADR note link to each other in both directions.
Placed in the amendment body, not the Status row — by choice, not constraint. Probed
empirically: appending to Status and re-running
truthlag_pins_test.shstill givesPASS ADR index: 20 ADR status rows agree with their files, because the pin compares only thefirst whitespace token of the Status cell. Status is nonetheless the wrong home — it is the
ADR lifecycle field the index renders, an implementation gap is a different axis, and trailing
free-text there would never surface on the index page a reader scans.
Third-surface sweep
Because the D-135 fix makes the word "armed" load-bearing, I swept every user-facing
surface for a deferred-arming promise:
merge when/when pipeline/pipeline succeeds/
forge merges/GitLab itself/when every threadoverdocs/,README.md,cmd/.None found. Every product-doc and CLI use of "arm"/"auto-merge" maps to assent's own
ArmEligible— permission to perform the immediate approve+merge, which does exist.assent doctorprintsarming precondition MET — forge-probed, auto-merge may be armed/advisory-only — auto-merge NOT armed; README,docs/index.mdanddocs/vision.mduse"auto-merge" as the product category. The only surviving deferred-arming text is ADR-0009's
amendment (left to its own amendment) and the two
docs/planning/forge-dossier-*.mdresearchnotes, which are out of the mkdocs nav per GUIDELINES and correctly describe what the forge
can do (GitLab C11).
No further defect — but one sentence was added to
cli.md'sassent doctorsection, asurface this lane already edited, saying which arming is meant. The doctor synopsis line
is deliberately untouched: it is pinned verbatim by
TestCLIDocCoversSubcommandsagainstmain.go's dispatch table, so rewording it would be a CLI change, not a docs change.Review round 2 — REQUEST CHANGES addressed
ARM-08 (P1, introduced by my own diff). The "stay advisory" advice I added recommended
phase: observe. False in the unsafe direction, twice. Both re-measured myself:(i) Structural exclusion removes the findings that were withholding approval.
CoverWithPhaseCeilingover an enforcing BLOCK rule:enforceobserveoffrequire: [signal]require:)Only an
enforce-phase rule marks a required obligation covered (coverage.go:150), so thebinding's
require:list is the only thing holding the line — andrequireis optional inthe frozen schema ("Absent or empty ⇒ vacuously covered"), which is exactly the first-pack
rollout state the advice targeted.
(ii)
spec.phaseis inert unless--packis passed (run.go:247). Against a fake servingno pack route: without
--pack→exit=0 approvals=1 merges=1(proving no pack is read);with
--pack→ the read happens and fails loudly. Edit the manifest, change nothing else,stay fully enforcing — ARM-01's shape, reintroduced by ARM-01's fix.
Replaced with the measured truth, published as a matrix rather than a summary: no dry-run
mode exists (
assent run --dry-runexits2,flag provided but not defined: -dry-run,verified on the built binary), no phase is a safety switch (
offapproves too), and the onlyreliable lever is leaving one of the three arming preconditions unmet.
SURF-07 (P2). ADR-0009's mode table specifies six entrypoints; only
runexists —explain,serve,scan,statsall exit2against the shipped dispatch table. Annotatedwith the same restraint as the amendment (fact recorded, normative text untouched, options
named, neither picked), following the walkthrough's "Planned —
assent explaindoes not exist"precedent. This is ARM-08's root cause: the sanctioned advisory mechanism was never built,
so the docs substituted one, and the substitute was unsafe.
ADR9-ST (P3). Status row extended to
Substantially unimplemented as of 2026-08-09so anindex reader sees the gap. Free, as proved earlier — the pin reads only the first token.
SURF-09 (P3). The p4-e1-s11 evidence README keeps its historical text verbatim and
gains a note that
--armsemantics changed atc05cde0, pointing at D-134.D135-CAL (P3). Corrected my own overstatement: D-135 claimed the walkthrough defect was
"tag-blocking on the same reasoning" as D-134. Same class, not same reasoning — ARM-01 blocked
because it ran unsafe on the primary surface; this one over-states automation and fails
loudly and recoverably. Standing alone it is P2, and the row now says so.
SURF-08 → OQ-29 (recorded, NOT fixed).
PolicyProfile.spec.writes: falseis documented asan architectural invariant (
writes:false"Never callsReconcile") with zero runtimeenforcement —
cmd/assent/run.gocontains the stringProfilezero times and evaluates viaCoverWithPhaseCeiling;ResolveProfile/WriteAllowedhave non-test consumers only ininternal/lint;LoadProfileonly incompare.go. Meanwhilewritesis a requiredfrozen-schema field and the
single-writer-profilelint hard error compels adopters to authorit. Logged as OQ-29 for an operator ruling; schema and lint rule untouched.
Merge of
origin/main(#36 landed). OnlyCHANGELOG.mdconflicted. Both append-onlytables auto-merged with nothing lost —
decisions.md129 rows on each parent → 131 merged,no D-id missing from either side; same for OQ. The merge commit's subject is
:wrench: chore(release):on purpose:cliff.tomlskips exactly that, and anything else fallsthrough to the
.*catch-all and renders a merge subject into the changelog (D-125). Verifiedthis lane's merge subject does not appear in
CHANGELOG.md. Pre-existing, not mine: twounskipped merge subjects from
lane/fix-provider-symlink-containmentdid land atCHANGELOG.md:184-185via #36, alongside older ones — same D-125 trap, flagged for thecoordinator.
Review round 3 — ADV-01
The advisory section opened with "There is no dry-run mode today" but never said the run
still comments on the merge request. Measured through
runRunwith the C3 gate absent — thestate the section recommends:
effect: block).assent/**self-edit)forge.Reconcileposts the summary comment before the arming branch is reached, so it landson every path except the self-edit guard.
The suggested parenthetical — "(and, on REVIEW/BLOCK, one resolvable thread)" — holds for
REVIEW and for an ordinary block effect, but not for the
.assent/**self-edit BLOCK,which writes nothing at all. Every run-path BLOCK fixture in the suite happens to be that
guard, so an ordinary
effect: blockpolicy was authored to measure the other half honestly.The exception is named rather than omitted — this page has already shipped two false safety
instructions, so a claim with a known counterexample is not worth the brevity.
Landed, three sentences, in the advisory section:
Scope held: the advisory definition is not restated beyond the clause itself, the phase matrix
is untouched, and
cliff.tomlis left alone forlane/cliff-skip-merge-commits(CL-01).Disclosure
Every commit on this lane used
git -c core.hooksPath=/dev/null. Per D-125 the changelogmust be the last commit, so
changelog-verify(stage 9 of 14) is red by construction onevery intermediate commit and a per-commit
task checkis unreachable. The gate matrix wasrun green on the final tree seven times, most recently after the ADV-01 clause, with both
GOLANGCI_LINT_CACHEandGOCACHEisolated.