Skip to content

Wire CliWireResponse into gunbc run: SCM log/status reach an operator - #9864

Merged
briansrls merged 30 commits into
mainfrom
scm-cli
Sep 2, 2026
Merged

briansrls merged 30 commits into
mainfrom
scm-cli

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

The SCM answer reaches an operator

gunbc.scm.render has produced CliWireResponse since it was written, and outside its witness
test nothing consumed it. A function returning one hit classify_exit's NotProcessExit arm
and the run refused with "wrap the result in ExitSuccess / ExitFailure", so every SCM read
answer was computed and discarded — the unwired-renderer state gunbc.cli_dispatch_surface
already records.

run_verb now tries cli_wire_outcome first and falls through to classify_exit unchanged for
every other value. Binding it in the outcome seam rather than under a new scm subcommand
keeps dispatch peripheral (§3): one binding serves every wire-returning entry instead of the host
growing an arm per verb.

The slice is exactly that: CliWireResponse is bound once in the gunbc run outcome seam,
making SCM log and status observable. init is not in this PR — see below.

Proven by execution

$ gunbc run --entry dag/gunbc/scm/cli.dag --function scm_log --arg path=<fixture>
no commits yet                                         rc=0

$ ... --function scm_status --arg path=<fixture>
nothing checked out
0 commits
  nothing staged

$ ... --function scm_log --arg path=/tmp/no-such-repo
cannot read repository at /tmp/no-such-repo
  No such file or directory (os error 2)
repository unavailable                                 rc=1

The last one is load-bearing: bytes printed and a nonzero exit — the case an absorbing
implementation turns into either silence or a spurious 0.

init is cut from this PR, and that is a deferral rather than a fix

An earlier revision of this branch shipped scm_init. Review 5085479276 found a destructive
TOCTOU
in it: initialize_repository established an absence from a directory listing and then
called save_repository, which writes unconditionally, so an actor creating the file in between
made init truncate exactly the bytes it advertises that it refuses to touch. The decision was
honest; the write was simply not conditioned on it.

That is not repairable by observing more carefully — check-then-write is two acts with a gap, and
the existence test and the creation have to be one act. The fix needs a new file-transport verb
in src/v1/05_emit.dag, a load-bearing pipeline stage, which a review finding about a CLI verb is
not authority to extend. It therefore lands in #10026 with the primitive, where the emit-stage
change is judged on its own merits.

Removed with it, because they had no other consumer: the FilesystemEntryName admission carrier,
the init decision witnesses, and the save-rendering helpers that only scm_init_cli_response
reached. gunbc.cli_dispatch_surface and the seed-growth receipt were corrected to stop asserting
init is reachable — that row had previously been wrong in the other direction, still claiming
scm was unreachable after the host had been wired.

Also dissolves a fork this PR would otherwise have created

main.rs's exit_status_for carried its own copy of the verdict map; once the wire path needed
the same decision, that copy was two places free to drift. The body moved to
cli_run::exit_status_for_class, where an executing test can reach it — rust-unit-tests runs
cargo test -p v1-compiler --lib, and main.rs is a bin whose tests clippy compiles and
nobody runs.

A malformed wire response is not "some other type"

classify_cli_wire first returned NotCliWire for two materially different situations: a value of
another type, and a value that names CliWireResponse without inhabiting it. The second fell
through to classify_exit and was reported as "not a ProcessExit" — true, useless, and about a
type the value never claimed. MalformedCliWire is its own arm and refuses with nothing on
stdout
, exit 2; NotCliWire keeps its fall-through so every pre-existing entry point is
untouched.

Review 58518 then found the remaining hole: a present but invalid exit was accepted, so the
host printed bytes before returning the shape error. Only a real exit keeps a response printable.

Evidence, and its boundary

  • cli_wire_outcome_tests (5, executing in CI): bytes with the response's own exit, a rendered
    answer that still fails, a renderer refusal not reading as empty success, the non-wire
    fall-through, and the inherited ExitFailure { code: 0 } refusal.
  • cli_wire_classify_tests (9, executing in CI): the classifier boundary, which previously had
    zero coverage — every earlier test started from an already-classified CliWireClass. A
    positive control, the non-wire fall-through control, the malformed shapes, and the assertion that
    a refusal reaches the host with stdout: None.
  • test.claim.scm.scm_cli_witness (floor-executed): pins the plain-terminal bytes and the
    capability row. scm_log_response is split from scm_log precisely so these are authorable —
    the verbs read a file and this witness family is SubstrateInputsOnly.
  • Not enrolled: the end-to-end runs above are a manual receipt. An integration test would live
    in src/v1/tests/, which clippy compiles and no CI step runs. So the e2e path is
    mitigatable, and its next-rung trigger is a CI step that runs an integration target.

Mutation receipts

claim mutant fixed
a_malformed_wire_response_refuses_rather_than_falling_through FAIL PASS
a_printable_response_whose_exit_is_not_a_process_exit_is_malformed_and_prints_nothing FAIL PASS

Each mutation restores the exact prior behaviour rather than an invented one, and takes only its
own claim red.

Named, not hidden

  • add/commit are not here. They need one modeled ScmWriteOutcome (each composes two outcome
    types), and add has no staging authority to persist to. Both are written up in
    docs/plans/scm-demo-cli-rebuild.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

gunbc-ci-auto-heal and others added 5 commits September 1, 2026 01:42
A working 'gunbc scm' CLI existed and was lost with an uncommitted /tmp worktree, along
with the plan doc describing it. This commits the survey so the rebuild does not start by
re-reading every scm module, and so the next context loss costs nothing.

The useful finding from re-surveying: the gap is smaller and more specific than 'build a
CLI'. gunbc.scm.render ALREADY reaches CliWireResponse via scm_log_cli_response and
scm_status_cli_response -- they simply have no consumer outside
dag/test/claim/scm/scm_render_witness_test.dag, which is the unwired-renderer state
gunbc.cli_dispatch_surface already records.

It also states why the corpus's idiomatic instrument shape cannot substitute for the host
binding: 'fn check(...) -> ProcessExit' can only emit text on FAILURE (exit_failure's
reason), and log/status must print on success. That is the argument for the main.rs work
rather than a workaround, and it is the thing I would otherwise have had to re-derive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
gunbc.scm.render already produces CliWireResponse (scm_log_cli_response,
scm_status_cli_response) and nothing outside its witness test consumes it -- the answer is
computed and discarded, which is the unwired-renderer state gunbc.cli_dispatch_surface
records. This adds the two host-side pieces that binding needs.

classify_cli_wire sits beside classify_exit as the single authority for reading the
variant shape, so the driver seam cannot fork it. A missing or wrongly-shaped field is
NotCliWire rather than a default: defaulting bytes to "" would print nothing and exit 0,
which is a fabricated plausible output.

cli_wire_outcome is the total, pure map from that class to what the host does -- bytes,
status, message. Pure for the reason exit_status_for records about itself: the inlined
version of that decision dropped a case and reported success for every failure. It lives
in the LIB, not beside the driver, because main.rs is a BIN target and the rust-unit-tests
job runs 'cargo test -p v1-compiler --lib' -- a test written next to the driver is compiled
by clippy and executed by nobody. exit_status_for_class is shared by both paths so the wire
path and the plain ProcessExit path cannot disagree about what a verdict means.

NotCliWire yields None rather than a failure, so the driver falls through to classify_exit
and every existing ProcessExit entry behaves exactly as before; a test pins that.

5 executing witnesses: bytes written with the response's own exit, a rendered answer that
still fails, the renderer's refusal not reading as an empty success, the fall-through, and
the inherited ExitFailure{code:0} refusal.

NOT YET WIRED into run_verb -- that is the next commit, and until it lands this reader has
no production consumer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
gunbc.scm.render has produced CliWireResponse since it was written, and outside its
witness test nothing consumed it. A function returning one hit classify_exit's
NotProcessExit arm and the run refused with 'wrap the result in ExitSuccess /
ExitFailure', so every gunbc scm answer was computed and discarded -- the unwired-renderer
state gunbc.cli_dispatch_surface records.

run_verb now tries cli_wire_outcome first and falls through to classify_exit unchanged for
every other value. Binding it in the OUTCOME SEAM rather than under a new 'scm' subcommand
keeps dispatch peripheral (§3): one binding serves every wire-returning entry instead of
the host growing an arm per verb.

dag/gunbc/scm/cli.dag is the entry the host invokes -- scm_log and scm_status, composing
the read side onto a declared plain-terminal capability. The capability is declared, not
detected: a host that learns to report a real one passes it in.

PROVEN BY EXECUTION, not by typecheck:

  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_log --arg path=/tmp/no-such-repo
  cannot read repository at /tmp/no-such-repo
    No such file or directory (os error 2)
  repository unavailable
  EXIT=1

That is the renderer's own document on stdout AND a nonzero exit -- the
'printable response carrying a failing exit' case, which is the one an absorbing
implementation would have turned into either silence or a spurious 0.

Also dissolves a fork I had just created: main.rs's exit_status_for carried its own copy of
the verdict map, and once the wire path needed the same decision that copy was two places
free to drift. The body now lives in cli_run::exit_status_for_class, where an executing
test can reach it -- rust-unit-tests runs 'cargo test --lib', and main.rs is a bin whose
tests are compiled by clippy and run by nobody.

clippy --all-targets -D warnings: clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
Receipts for the three executed cases, including the load-bearing one (bytes printed AND a
nonzero exit), so the next session does not re-derive them.

States the rung honestly rather than implying the e2e is covered: the five cli_wire_outcome
tests execute in CI, and the end-to-end runs are a MANUAL receipt. An integration test
would live in src/v1/tests/, which clippy compiles and no CI step runs; a .dag witness
cannot substitute because scm_log reads a file and the SCM witnesses are SubstrateInputsOnly.
So the e2e path is mitigatable and its next-rung trigger is a CI step that runs an
integration target -- not another test file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 1, 2026 02:22
gunbc-ci-auto-heal and others added 4 commits September 1, 2026 02:42
gunbc.scm.render is already witnessed against PlainTerminal and an ASCII no-colour
capability. Nothing held gunbc.scm.cli to passing THOSE: setting color: true on
plain_terminal_capability would have turned no claim red, which is the inert-check shape
DESIGN calls worse than absent.

The composition is split from the read -- scm_log_response / scm_status_response take the
read's RESULT and are pure, while scm_log / scm_status supply it from a path. That split is
what makes the claims authorable at all: the verbs read a file and this witness family is
SubstrateInputsOnly, so a claim can never call them.

Five enrolled claims: the unavailable-repository bytes (pinned to the same expected string
the render-level witness uses, so an equal answer is evidence the PLAIN target and
capability were passed, not merely that something was), its failing exit, the empty-log
answer with a succeeding exit, the same for status, and the capability row itself so a
reader editing it learns it is a contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_init --arg path=/tmp/demo-repo.json
  initialized repository at /tmp/demo-repo.json        rc=0, file created

  ... --function scm_log --arg path=/tmp/demo-repo.json
  no commits yet

init writes the document, log reads it back. That settles the open question the plan doc
recorded: a host WRITE is permitted from gunbc run, so the remaining write verbs are a
modeling question rather than a permissions one.

render.dag gains the write-answer renderer. A write verb answers with what the write DID,
and its four outcomes are not one line with a flag -- each names a different failure and a
different next action. RepositoryWriteByteCountUnrepresentable EXITS FAILURE even though
bytes reached the disk: a write whose reported size is not a magnitude has not been
confirmed, and reporting success for it is the fabricated plausible output §5 forbids.

TWO LOSSES NAMED IN THE SOURCE RATHER THAN HIDDEN:

- The codec refusal carries a typed RepositoryEncodeRefusal with six arms (uncontained
  target, root not in store, checkout not in commits, duplicated reference, reference
  outside allocator, parent not in commits) and this renderer does not decompose it, so an
  operator learns THAT encoding refused and not WHICH invariant failed. Rendering it needs
  a per-arm function over ObjectId and RepositoryCommitRef.
- init does NOT refuse an existing repository. save_repository writes unconditionally, so
  init over a populated path overwrites it. Refusing needs a read-before-write that is not
  expressible as one outcome here. That is why this verb is not offered as a safe default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
Settles by execution what was open: a host write IS permitted from gunbc run, content
enters via store_node, a Node is built with node_synthetic (the test modules' 'atom' is a
LOCAL helper, not an authority), and mint_repository_commit requires store_contains(root),
so add must precede commit -- and because identity is content-derived, commit can re-derive
an added object's ObjectId by rebuilding the same node.

Names the design step rather than sketching it: add composes store_node's 2 arms with
save_repository's 4, and commit composes mint's 4 with the same save. A renderer taking two
outcome values would encode 'which one failed' positionally and the arms multiply. One
modeled ScmWriteOutcome is the increment's real content. I deliberately did not improvise
it into cli.dag at the end of a long session, because a write-verb outcome invented at a
call site is the anemic modeling this repository keeps paying for.

Also records that 'add' has no staging authority to persist to -- repository_status takes
pending as a PARAMETER because what is staged is not a fact the document carries -- so a
literal stage-now-commit-later add needs a staging authority to exist first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
@gunbai-bot gunbai-bot Bot changed the title SCM MVP-N Wire CliWireResponse into the host: gunbc scm log/status/init reach an operator Sep 1, 2026
gunbc-ci-auto-heal and others added 2 commits September 1, 2026 04:19
…ake it fail-closed

Review 58044 was right and this was the worst thing in the PR. save_repository writes
unconditionally -- correctly, it is persistence and not policy -- so a verb offering
initialization has to decide for itself whether there is anything at the path it would
destroy. The previous revision skipped that decision, called save directly, and NAMED the
overwrite in a comment. DESIGN §5's review bar is that a diff landing a non-fail-closed
failure arm is a hard reject regardless of what else it delivers; an annotation is not a
refusal.

gunbc.scm.init owns the decision, because whether there is anything to destroy is a fact
about the repository and deciding it in the CLI module would put policy in the realization
layer. ScmInitOutcome has three arms and no 'initialized: Bool' beside a save outcome -- a
value whose flag disagreed with its shape would have no spelling -- and the two refusals are
distinct because the operator's next action differs: a path that already IS a repository is
not the same as a path holding someone else's bytes.

MEASURED, including the case that proves the destruction is actually closed:

  fresh path        -> initialized repository at /tmp/d2.json
  same path again   -> refusing to initialize /tmp/d2.json
                         a repository is already there, and init would replace its whole history
  foreign file      -> refusing to initialize /tmp/foreign.json
                         a file is already there that is not a repository, and init would destroy it
  foreign file after-> 'not a repo'   (intact -- read back, not assumed)

THE RESIDUAL IS STATED IN THE MODULE RATHER THAN IMPLIED. RepositoryFileUnreadable fuses
'absent' with 'present but unreadable' and carries the host's error STRING. This module
refuses to branch on that text, because deciding a destructive question by matching an errno
spelling is the stringly reasoning the substrate exists to remove. So the proceed arm is
'unreadable', not 'absent'. Closing it needs a modeled path-existence observation distinct
from a read failure, which does not exist yet and is the module's next-rung trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
…he wire host Rust

Three blocking findings from review 58060 on #9864.

1. `init` was fail-open on `RepositoryFileUnreadable`. Proceeding there is a guess
   about the host's permission model, not an observation -- a file can be unreadable
   and perfectly truncatable, so the proceed arm could destroy the bytes it existed to
   protect. The residual was NAMED in the module header, and a named residual does not
   refuse. `initialize_repository` now takes a DIRECTORY and a NAME and routes through
   `extdeps.filesystem.filesystem_io` `filesystem_file_observation`: absence is
   established from a listing that succeeded and did not name the entry, and only that
   arm reaches `save_repository`. Present bytes are classified (repository vs foreign)
   for the operator's benefit but both refuse; indeterminate and disagree refuse as
   `ScmInitRefusedPathUnobserved`, carrying the host's cause.

   Executed, four cases: fresh directory writes 166 bytes; an existing repository
   refuses; a foreign file refuses and reads back byte-intact; a directory with mode 000
   refuses naming `Permission denied (os error 13)` rather than widening to "nothing is
   there". Three hermetic claims enrolled beside them in
   test.claim.scm.scm_cli_witness -- no refusal renders as a success exit, the three
   refusals render differently, and the unobserved arm names what could not be observed.

2. The hand-authored Rust had no seed-growth receipt. `gunbc.cli_wire_host_admission`
   enumerates all 11 declarations at identity grain, states why the host process and the
   interpreter Value both lack a .dag denotation today, records that
   `exit_status_for_class` is a relocation rather than new behaviour, and names two
   capability-grain triggers. It does NOT admit the growth; the disposition is Terminal
   and fails closed without an operator ruling. Wired into
   `gunbc.seed_growth_admission`.

3. `gunbc.cli_dispatch_surface` still asserted there is NO route a user can invoke for
   the scm family, which this branch made false. The row now names the real route and
   says what changed, and its stage0 mirror is regenerated from the emitter (the
   candidate tree differed from the committed mirror by exactly this one string).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT
@gunbai-bot

gunbai-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

All three findings from review 58060 are addressed in 0259205.

dag/gunbc/scm/init.dag — every non-absent observation now refuses. You were right that
proceeding on RepositoryFileUnreadable is fail-open: it is a guess about the host's permission
model, and a file can be unreadable and perfectly truncatable. The fix is not a better read — it is a
different observation. initialize_repository now takes a directory and a name (presence is a
fact about a listing, so its subject has to be an argument, not something recovered by splitting a
string) and routes through extdeps.filesystem.filesystem_io filesystem_file_observation. Absence
is established from a listing that succeeded and did not name the entry; only that arm reaches
save_repository. A new ScmInitRefusedPathUnobserved { path, cause } carries the indeterminate and
disagree arms.

Executed, one per arm:

fresh directory       [file] write /tmp/scminit/fresh/repo.json (166 bytes)
                      initialized repository at /tmp/scminit/fresh/repo.json
repository present    refusing to initialize … a repository is already there
foreign file present  refusing to initialize … and the file reads back byte-intact afterwards
directory mode 000    refusing to initialize … the path could not be observed, so nothing about
                      it is established -- the directory /tmp/scminit/nodir could not be listed,
                      so the absence of repo.json is not established -- Permission denied (os error 13)

Three hermetic claims are enrolled beside them in test.claim.scm.scm_cli_witness: no init refusal
renders as a success exit, the three refusals render differently, and the unobserved arm names what
could not be observed. All three evaluate true.

The hand-Rust receipt. dag/gunbc/cli_wire_host_admission.dag enumerates all 11 declarations at
identity grain (5 production, 6 test), reproducible from the merge base rather than the moving tip.
It states why the boundary is real — the host process's stdout/exit and the interpreter's Value
representation both lack a .dag denotation, so the fold between them cannot be written in the
substrate it is bridging into — records that exit_status_for_class is a relocation of
main.rs's exit_status_for body rather than new behaviour, and names two capability-grain
triggers. It does not admit the growth: the disposition is Terminal and fails closed without an
operator ruling. Wired into gunbc.seed_growth_admission.

The dispatch-surface fork. Correct and now closed at both ends: cli_dispatch_surface.dag names
the real route and says what changed, and the stage0 mirror is regenerated from the emitter rather
than hand-edited. claim_executor --required-regen produced a candidate tree whose
gunbc_cli_dispatch_surface.rs differed from the committed mirror by exactly this one string, which
is the evidence that taking it is a regeneration and not a patch.

— sent from deep-carp-676

gunbc-ci-auto-heal and others added 2 commits September 1, 2026 15:30
seed_growth_admission conflicted on the justification roster: two lanes
appended one row each — cli_wire_host_seed_growth_justification from this
branch, required_lane_judgment_seed_growth_justification from main. The rows
are independent, so both are kept, and each name was re-checked against its
import edge rather than read off the diff: this carrier is exactly the shape
merge_region_excludes_shared_tail describes, and its rows being one-liners is
what makes the additive resolution safe here rather than something to assume.

The generated projections are regenerated from the merged authorities via the
sanctioned actuator, not resolved by picking a side.

Verified locally on the merged tree with a claim_executor rebuilt from it:
build lane regen first_generation_equal=true, generated-artifact 35/35 matched
0 drifted; witnesses lane 3 phases 0 failures, 4484 files parse-clean,
namespace-wave-admission ADMITTED.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
# Conflicts:
#	src/v1/stage0/src/cli_run.rs

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head review — 0230151dc4821b8c9ddb9c05a4dfb5c1a3af15ba

REQUEST_CHANGES

I reviewed the current tree, the host binding, the init model, the evidence, and the cli_run.rs merge resolution.

The cli_run.rs conflict resolution is accepted

The merge commit has the authored branch as its first parent and exact current main@c26de912077a702bd7d3a12c2da9fcc4fe21b8ca as its second. Relative to that current main, cli_run.rs is exactly the intended +255/-0 wire block. The block closes completely before main's ambiguity-ban declarations begin; I found no brace rotation, lost shared tail, duplicated declaration, or interleaving of the two changes. rust-unit-tests also completed green on the exact head. Do not redo this resolution.

Four substantive blockers remain.

1. init observes absence and then performs an unconditional write

initialize_repository performs List and Read, derives FilesystemFileAbsent, and only then calls save_repository. save_repository uses ordinary Filesystem.Write, which this code explicitly describes as unconditional.

That closes the fail-open cases during the observation, but not the interval after it. Another actor can create or replace the target between the observation and the write; the ordinary write can then truncate bytes that init claims it refuses to replace. The preflight is useful for the repository-vs-foreign diagnostic, but it cannot authorize a destructive actuation.

The final actuation must be atomic create-if-absent: O_CREAT|O_EXCL / create_new(true) or a modeled equivalent. Do not reuse WriteOwnerOnly merely because its current realization happens to carry create_new; owner-only mode and create-only existence policy are separate facts. Add a distinct filesystem operation/outcome, preserve an already-existing file byte-for-byte, and return a named occupied/moved-at-actuation refusal rather than generic unwritable.

Required evidence:

  • absent target → one new repository document;
  • existing target → exact original bytes remain and the create-only cause is returned;
  • the observation-to-actuation moved case cannot reach ScmInitialized.

2. name: String does not identify one entry of directory

The listing asks whether it contains the supplied name, while the read/write subject is constructed independently as directory + "/" + name. A value such as ../victim, subdir/repo.json, the empty string, or a string containing a newline is not one directory entry. It can escape the listed directory or corrupt the line-delimited membership predicate, so the observation and actuation no longer necessarily concern one subject.

Admit an entry-name carrier before either operation: nonempty; not . or ..; no path separator; no NUL; and no newline while Filesystem.List uses newline-delimited names. Construct both the listing query and the final path from that admitted component. Add the rejecting cases above and an ordinary repo.json control.

3. A malformed nominal CliWireResponse falls through as “not ProcessExit”

classify_cli_wire correctly recognizes the nominal CliWireResponse type first. But a missing/wrong bytes, missing exit, empty/absent cause, or unknown wire variant is returned as CliWireClass::NotCliWire. cli_wire_outcome(NotCliWire) returns None, and run_verb then falls through to classify_exit.

So a value that is nominally a CliWireResponse and is malformed as one is reported as merely not being ProcessExit. The owner and remedy are lost. NotCliWire must be reserved for a genuinely different return type. Add a MalformedCliWire { cause }/equivalent arm that produces a nonzero wire-boundary refusal and never reaches the ProcessExit classifier.

The present five tests start after classification by constructing CliWireClass directly, so they cannot detect this. Execute the classifier for the malformed field/variant cases and retain the genuine non-wire fall-through control.

4. The init safety decision is not independently executed, and the authority text contradicts the live interface

test.claim.scm.scm_cli_witness renders preconstructed ScmInitOutcome values. It does not execute the mapping from FilesystemFileObservation to write/refusal. Consequently, mutating either FilesystemFileIndeterminate or FilesystemFileObservationsDisagree to the save arm leaves the enrolled init claims green. The manual receipt does not include the disagreement case.

Factor the observation-to-decision step into a pure function/carrier, execute all four observation arms one fact apart, and let only an established-absence decision reach the atomic create-only actuation from item 1.

Reconcile the live narrative at the same time:

  • there is no gunbc scm subcommand; gunbc.cli_dispatch_surface still marks it AbsentFromEmitMainRs and names gunbc run --entry ... as the reachable route;
  • the PR's init receipt passes --arg path=..., but scm_init now requires directory and name;
  • the PR body says init does not refuse an existing repository, the opposite of the current module;
  • the body says the SCM witness has six tests; it has eight;
  • cli_wire_host_admission likewise says gunbc scm log/status/init reach the operator rather than accurately naming the gunbc run host seam.

The architecture choice to bind CliWireResponse once in run_verb is accepted. The current title and prose must describe that real interface rather than the future subcommand.

Exact-head CI

Workflow 33586792719 is terminal red on this exact head: required-witnesses-build failed, while the floor, Rust unit, and fabric jobs succeeded. Fresh terminal exact-head green is required after the repairs.

Re-review bar

  1. Atomic create-only init actuation with preserved-existing-byte evidence.
  2. An admitted one-entry filename carrier joining observation and actuation.
  3. A distinct malformed-wire classification, executed before the outcome map.
  4. Executed observation-to-init-decision coverage for absent, present, indeterminate, and disagreement.
  5. Truthful PR/source/plan narrative for the actual gunbc run interface and current signatures/counts.
  6. Terminal exact-head CI.

The merge-conflict resolution itself is approved; no blocker asks you to redo it.

gunbc-ci-auto-heal and others added 7 commits September 2, 2026 04:32
main carries an updated `proof` row in gunbc.rust_source_type_bindings whose
committed projection was never regenerated, so origin/main is itself in a
drifted state and every branch that merges it inherits a failing regen phase.
Measured here rather than assumed: both the .dag authority and the .rs
projection on this branch are byte-identical to origin/main, and regen still
reports first_generation_equal=false for this one file.

The fix is the projection, not the authority: the generated file now carries
what the row says. The build lane is green at this head -- regen
first_generation_equal=true, generated-artifact 35/35 matched, 0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
cli_wire_host_relocation_note was a `data ... : String` whose only purpose was
commentary, with no consumer -- the neighbouring SeedGrowthJustification did not
reference the symbol, it referenced the NAME inside its own prose. That is the
state DESIGN §4c names: an ordinary String declaration carrying commentary is
mechanically indistinguishable from program data, and `//` is the quarantine
boundary that keeps the two apart.

It is authored rationale about why the roster counts a relocation, so it becomes
an annotation on the declaration it explains. The FACT it carried does not
disappear with it: that exit_status_for_class is a relocation rather than new
behaviour is now stated inside the justification's own `reason`, where a
consumer reading the receipt sees it, rather than in a sibling row a consumer
would have to know to look up.

Found by review 58455 on gunbc#9864.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Two of the four findings in review 5085479276 on gunbc#9864.

THE NAME WAS USED TWICE AND GUARANTEED ONCE. initialize_repository asked the
listing about `name` and separately joined directory + "/" + name into a path,
with nothing making those the same subject. `subdir/repo.json` is asked about as
a child of the listed directory while the join reaches past it, `../victim`
leaves it entirely, `.`/`..`/`""` name no child at all, and -- the case that
belongs to the filesystem module rather than to any caller -- a name carrying a
newline can make the membership test answer yes for an entry that is not there,
because newline is Filesystem.List's own delimiter.

FilesystemEntryName is sole_constructor, so admission is unavoidable rather than
advisory, and the refusal names which rule rejected the spelling. Its scope is
stated honestly: it is adopted at this consumer, filesystem_entry_presence still
takes a String, and widening that is a replacement migration over the whole
population with its own next-rung trigger -- not smuggled into the change that
introduces the carrier.

THE DECISION WAS NOT WITNESSED, ONLY ITS RENDERING. Every enrolled init claim
built a refusal outcome and checked how it printed, so nothing executed the step
that CHOOSES an outcome from an observation; a mutation routing indeterminate to
the write arm left the family green. scm_init_decision is that step with the two
operations lifted out, and the four arms are now driven one fact apart through
the REAL fold -- FilesystemEstablishedAbsence being sole_constructor means a
witness cannot hand in a fabricated absence, which forces the evidence to cover
filesystem_file_observation and the decision together.

Executed: routing indeterminate to a present-refusal takes
an_unobservable_path_does_not_authorize_a_write red; routing a real absence away
from the write arm takes only_an_established_absence_may_create red. The third
mutation -- routing DISAGREEMENT to the write arm -- is unwritable, because
ScmInitMayCreate carries the sole_constructor absence and no arm can manufacture
one. The rung is split on that boundary rather than averaged.

The refusal vocabulary is one type (ScmInitRefusal) consumed by both the decision
and the outcome, so a refusal has one spelling rather than two, and the outcome
never carries an arm that is only ever intermediate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Third of the four findings in review 5085479276 on gunbc#9864.

classify_cli_wire returned NotCliWire for two materially different situations:
a value of some other type, and a value that NAMES CliWireResponse but does not
inhabit it. cli_wire_outcome answers None for NotCliWire so the caller falls
through to classify_exit, which meant a malformed wire response was reported as

  error: function `f` returned `...`, not `ProcessExit`

-- true, useless, and about a type the value never claimed. It hides that the
value claimed a type it does not inhabit, and it sends the caller to the wrong
remedy: wrap this in ExitSuccess, when the actual defect is a missing `bytes`.

MalformedCliWire is its own arm and REFUSES rather than falling through, exiting
2 like the other shape refusal. NotCliWire keeps its fall-through unchanged,
which is what preserves every pre-existing ProcessExit entry point.

THE TESTS THE REVIEW SAID DID NOT EXIST. All five original tests started from an
already-classified CliWireClass, so classify_cli_wire had no coverage at all --
the boundary carrying the defect was the one nothing executed. Seven tests now
build real interpreter values: a positive control that a well-formed printable
still classifies as printable, a control that a plain ProcessExit and a bare
string still fall through (the case that must NOT become malformed, or every
existing entry point breaks), the four malformed shapes, and one that the
refusal reaches the host with status 2 and no stdout.

Executed: restoring the fall-through for MalformedCliWire takes
a_malformed_wire_response_refuses_rather_than_falling_through red and leaves the
other twelve green, so the arm is load-bearing for exactly the reported case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Adding cli_wire_classify_tests put thirteen hand-authored declarations into
cli_run.rs that the receipt did not name, while it went on asserting "+11 FROM
THE MERGE BASE, ENUMERATED ABOVE". A roster whose subject is hand-authored
declarations, silently missing thirteen of them, is the defect it exists to
prevent -- and it was introduced by the change that fixed a different one.

Enumerated at 25 and recomputed: 449 insertions in cli_run.rs, 84 changed lines
in main.rs, re-derived against origin/main rather than a stale local `main` ref.

The five test HELPERS are counted, not just the seven test fns. The roster's
subject is declarations, and a helper is one; waving them through as "just
tests" is the same netting this receipt already refuses for the relocated
exit_status_for_class.

Two further .rs files differ and the row says WHY they are outside the subject
rather than omitting them: gunbc_cli_dispatch_surface.rs and
gunbc_rust_source_type_bindings.rs are generated projections regenerated from
their .dag authority, so they are excluded by construction, not by exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
TWO CHANGES, BOTH SUBTRACTIONS OF A CLAIM THIS BRANCH COULD NOT HONESTLY MAKE.

1. scm.init is removed, on the operator's ruling. Its remaining defect is a
destructive TOCTOU: initialize_repository establishes an absence and then calls
save_repository, which writes UNCONDITIONALLY, so an actor creating the file in
between makes init truncate the bytes it advertises that it refuses to touch.
The honest fix is an atomic create-only write, which needs a new file-transport
verb in src/v1/05_emit.dag and 05_emit_rust.dag -- a load-bearing pipeline stage
that a review finding about a CLI verb is not authority to extend. So init and
the primitive land together in their own PR, where the emit-stage change can be
judged on its own merits, rather than riding in on a CLI-wiring branch.

What goes with it, because it would otherwise be an artifact with no consumer:
the FilesystemEntryName admission carrier and the init decision witnesses. They
are preserved on scm-init-create-only, not discarded.

Two rows asserted the thing that is no longer true and are corrected rather than
left to rot: cli_dispatch_surface said scm_init was reachable, and the seed
growth receipt said "log, status and init reach an operator". Both now say init
is deliberately absent and why. A surface that keeps asserting a reachability
the host does not provide is the §3 fork the first of those rows exists to
prevent -- it was wrong in the other direction two changes ago.

2. classify_cli_wire accepted any PRESENT `exit` field, including one that
classify_exit answers NotProcessExit for. That value reached Printable, so
cli_wire_outcome set stdout: Some(bytes) and then exited 2 -- the host PRINTED
bytes carried by a value that does not inhabit the declared wire shape, and a
consumer reading stdout got partial output from a response that was refused.
Present is not the same as valid. Only a real exit keeps a response printable.

Executed: restoring the accept-any-present-exit form takes
a_printable_response_whose_exit_is_not_a_process_exit_is_malformed_and_prints_nothing
red and leaves the other thirteen green. The assertion that carries the finding
is `stdout.is_none()` -- a classification left as Printable would still refuse,
but only after emitting the untrusted bytes.

Found by review 58518 on gunbc#9864.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
The row that stopped claiming scm_init is reachable is an authority with a
committed projection, so the projection moves with it. Build lane green at this
head: regen first_generation_equal=true, generated-artifact 35/35, 0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head re-review — 82f8312ea22a9c9b5a5a051ce17c953fa8601f56

REQUEST_CHANGES

The operator's deferral is acceptable. Do not restore scm.init to this PR. Removing an unsafe claimed capability is a valid way to close this slice; the atomic create-only file-transport primitive remains a hard predecessor of the later init PR, not a blocker to a wire-binding PR that contains no init route.

The malformed-wire repair is also accepted. A nominal CliWireResponse whose exit field is present but classifies as NotProcessExit now becomes MalformedCliWire; the outcome refuses with status 2 and stdout: None. The new test asserts the load-bearing consequence—untrusted bytes are not emitted—and the reported mutant distinguishes exactly that case. The earlier cli_run.rs conflict resolution remains accepted.

Four bounded blockers remain.

1. The init subtraction is incomplete

dag/gunbc/scm/init.dag and the init decision machinery are gone from this PR, which is correct. But branch-only artifacts that existed solely for init remain:

  • gunbc.scm.render: repository_save_lines, scm_save_exit, scm_save_document, and scm_save_cli_response, plus their RepositorySave imports and write-verb commentary. No remaining log/status entry consumes them.
  • test.claim.scm.scm_cli_witness: wire_refuses and wire_bytes remain beneath the stale heading “SHARED BY THE INIT CLAIMS BELOW”, but there are no init claims below; the file ends after those helpers.

Move those artifacts with init or delete them here. Cutting the consumer while leaving its private projection and test helpers is not a complete scope subtraction.

2. The hand-Rust receipt no longer matches the implementation

cli_wire_host_seed_growth_justification omits the newly added test

a_printable_response_whose_exit_is_not_a_process_exit_is_malformed_and_prints_nothing.

The exact implementation population is therefore 26 declarations, not 25:

  • 5 production declarations;
  • cli_wire_outcome_tests: 1 module + 5 test functions = 6;
  • cli_wire_classify_tests: 1 module + 5 helpers + 9 test functions = 15.

The receipt currently says the classifier module has seven test functions, while its roster actually lists eight and the implementation now has nine. It also assigns every helper/test child the parent path v1_compiler.cli_run; those children belong under v1_compiler.cli_run.cli_wire_classify_tests, just as the older outcome-test children use v1_compiler.cli_run.cli_wire_outcome_tests. Correct the identities, total, and the stale “three declarations below” wording.

3. The public narrative still claims the cut capability

The dispatch-surface authority and generated projection now correctly say only log/status are reachable through gunbc run, and that init is deferred pending atomic create-only write. The PR metadata and plan do not agree:

  • the title still says gunbc scm log/status/init reach an operator;
  • the PR body still contains the scm_init execution receipt, says init proves host write, says init overwrites existing repositories, and reports the old witness count;
  • docs/plans/scm-demo-cli-rebuild.md still labels init as landed, carries its execution/witness receipts, and derives later conclusions from that run;
  • gunbc.scm.cli still describes “every gunbc scm answer” although this PR lands no gunbc scm subcommand—the real interface is the gunbc run --entry ... outcome seam.

Rewrite these as the log/status-only wire-binding slice. Historical init work may be named as deferred and preserved on its branch, but it may not remain presented as landed behavior.

4. Current-main composition and exact-head CI are not green

Current main is ecda0710810f6fca89b39a3fc808f42d8a9717fe. This head still has merge base c26de912077a702bd7d3a12c2da9fcc4fe21b8ca; it is behind current main, whose intervening work overlaps src/v1/stage0/src/cli_run.rs and the generated source-type-binding projection. Merge current main, preserve the accepted wire block, regenerate declared projections from the composed authority, and obtain fresh exact-head CI.

Workflow 33603696869 is already red at this head: required-witnesses-build succeeded, but rust-unit-tests failed. The remaining jobs cannot turn that run green.

Re-review bar

  1. Complete the init subtraction by removing/moving the unconsumed write-render and witness helpers.
  2. Correct the seed-growth roster to the exact 26-declaration population and nested module identities.
  3. Reconcile the title, PR body, plan, and source wording to log/status through gunbc run; init is deferred, not landed.
  4. Compose current main, regenerate, and obtain terminal exact-head green CI.

The deferral itself is approved. No finding asks you to put init back into #9864.

@gunbai-bot gunbai-bot Bot changed the title Wire CliWireResponse into the host: gunbc scm log/status/init reach an operator Wire CliWireResponse into the host: gunbc scm log/status reach an operator Sep 2, 2026
gunbc-ci-auto-heal and others added 3 commits September 2, 2026 08:35
Three findings from the SCM reviewer, all of them leftovers from cutting init.

DEAD ARTIFACTS THE CUT LEFT BEHIND. Removing scm_init_cli_response orphaned the
whole save-rendering cluster -- repository_save_lines, scm_save_exit,
scm_save_document, scm_save_cli_response -- which had no consumer outside
render.dag once init was gone, plus the RepositorySave imports that fed them.
The witness kept wire_refuses and wire_bytes under a header reading "SHARED BY
THE INIT CLAIMS BELOW" with no init claims below. Measured before deleting: the
two helpers appeared exactly twice in the file, which was their own definitions.
They return in #10026 with scm_init_lines, their actual consumer.

THE RECEIPT UNDERCOUNTED FOR THE SECOND TIME, and the repair is the derivation
rather than the number. It said +11 while a test module added thirteen unlisted
items; corrected to +25, it then missed
a_printable_response_whose_exit_is_not_a_process_exit_is_malformed_and_prints_nothing
-- the test that closed review 58518, added after the row was written. Both
times the receipt was edited as a step separate from the code it admits, so
anything added afterwards fell silently outside its subject. The row now states
how to re-derive the population instead of asserting a total: per test module,
the module itself plus every fn carrying #[test] plus every fn that does not.
5 + (1 + 0 + 5) + (1 + 5 + 9) = 26.

Child items also carry their NESTED module path now
(v1_compiler.cli_run.cli_wire_classify_tests), because that is where the
declaration lives and a roster whose identities do not resolve cannot be checked
against the code. The outcome module's five were flattened onto the parent when
first written and are corrected here rather than left as a second convention.

THE PR NARRATIVE claimed init landed -- title, execution receipt, and a
paragraph describing the unsafe behaviour as a known limitation. Rewritten as
the slice this actually is: CliWireResponse bound once in the gunbc run outcome
seam, making log and status observable, with init's removal stated as a
deferral and its defect described rather than softened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
…g care

exit_status_for_class's doc said "kept byte-for-byte equivalent to the driver's
own exit_status_for" while the relocation had silently dropped the trailing
`status: refused -- printing the value and exiting 0 would report success for a
run whose outcome is unknown.` sentence from the NotProcessExit message. The
comment asserted the exact property it violated, so the doc was the thing that
lied rather than the code. Found by review 58567 on gunbc#9864.

The sentence is restored rather than the claim weakened, because it carries the
operator-facing reason the refusal exists: an unknown outcome reported as
success is the fabricated plausible output §5 forbids, and that is the half a
reader needs in order to not "fix" the refusal away.

The doc is also corrected to state the STRONGER and true property. It claimed an
equivalence held by care; main.rs holds no match of its own and delegates
entirely, so there is ONE implementation and the two paths cannot disagree by
construction. Saying "kept equivalent" understated it and simultaneously
invited the drift it failed to prevent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
Review 58591 on #9864 found it. `scm_init` was the sole reference; removing init
left the import standing. Verified: zero occurrences of `empty_repository` remain
in the module. `empty_proposal` from the neighbouring import IS still used, by
scm_status_response, so only the one line goes.

Same class as the reviewer's own item 1 on this PR -- private artifacts that
should have left with the capability they served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
@gunbai-bot gunbai-bot Bot changed the title Wire CliWireResponse into the host: gunbc scm log/status reach an operator Wire CliWireResponse into gunbc run: SCM log/status reach an operator Sep 2, 2026
The SCM reviewer's item 3, and they were right that I reported it as done when
the pushed tree still said otherwise. Three separate false statements, each
verified against the tree before changing it:

1. docs/plans/scm-demo-cli-rebuild.md carried an `### init -- the write verb`
   section under the landed material, with four execution receipts, reading as
   behaviour this PR ships. It is now explicitly headed DEFERRED, states that
   nothing in it may be read as evidence for this change, and says why init was
   cut rather than fixed in place (the observe-then-write TOCTOU, and that
   closing it needs a new modeled file-transport verb in a load-bearing stage).

2. The same doc claimed the init refusal claims were "hermetically enrolled" in
   test.claim.scm.scm_cli_witness. They are not: that witness has ZERO init
   claims on this branch -- they left with init. Corrected to say so, and to say
   where they went.

3. The "next increment" section derived "a host WRITE is permitted" from
   `scm_init` created a file, presented as a settled fact of this PR. The fact
   survives -- it is a fact about the HOST, not about init's safety -- but it is
   now carried as prototype evidence rather than as something landing here.

Also two bounded text corrections the reviewer named:

- cli_wire_host_admission said "the three declarations below are the route".
  Five production declarations sit below. Verified from the roster's own
  decl_name rows: CliWireClass and CliWireOutcome are carriers, classify_cli_wire
  / cli_wire_outcome / exit_status_for_class are the three functions. The text now
  makes that split explicit instead of undercounting to match the route.

- gunbc.scm.cli said "every `gunbc scm` answer was computed and discarded".
  There is no `gunbc scm` subcommand -- the annotation named a route that does
  not exist two lines before naming the one that does. Now "every SCM read
  answer".

The PR title had the same defect and is changed to name `gunbc run`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head re-review — 01ebacbf3b1741433ff05f874e2ba8c6b4b8f957

REQUEST_CHANGES

The named 594d6db repairs are substantially correct:

  • the init-only render and witness residue is gone;
  • the unused empty_repository import is gone while empty_proposal remains correctly consumed;
  • the seed-growth roster now derives and enumerates the exact 26 declarations, with correct nested module identities;
  • the five-production-declaration / three-route-function / two-carrier split is truthful;
  • the title and gunbc.scm.cli now name the real gunbc run seam;
  • the deferred-init section now explicitly denies that its receipts or claims are evidence for this PR.

The projection-drift hypothesis is also closed by falsification. The main-wide red was not an SCM defect. Main commit 2f6f7aa0950df8edd7897902f1b7a51eb1630e26 / #10025 independently confirms the actual contract: an unmodeled shell output key produces a typed, located emit refusal and no emitted file. The old test required src/probe.rs to exist and thereby asserted the fail-open shape it existed to forbid. Do not repair or waive that test inside #9864; compose the main-owned correction.

Three bounded blockers remain.

1. The PR body still names a nonexistent command and retains an init-only bullet

The opening paragraph still says:

so every `gunbc scm` answer was computed and discarded

There is no gunbc scm subcommand. Use the same truthful wording now present in source—every SCM read answer—or name the gunbc run entry route directly.

The final “Named, not hidden” section still says:

The write renderer does not decompose RepositoryEncodeRefusal's arms

This PR contains no write renderer after the init subtraction. Move that fact with #10026/future write work or remove it here.

2. The plan still mixes baseline, landed, and future states without marking the transitions

docs/plans/scm-demo-cli-rebuild.md opens with:

It is a plan, not a receipt: nothing here claims to be built.

but later contains ## LANDED plus execution receipts. Make the document's mixed role truthful—for example, a plan and historical ledger whose explicitly marked landed sections carry receipts.

It also still states in the present tense that scm_log_cli_response and scm_status_cli_response have NO consumer, although this branch adds gunbc.scm.cli as that consumer. Mark that section as the baseline gap before this change and use past tense.

Finally, the numbered host-shape section still presents Commands::Scm plus scm_verb as the required host binding. This PR chose and implemented the generic run_verb outcome seam instead. Mark the direct subcommand as the original/future ergonomic shape, not as an unfinished requirement for the binding this PR claims landed.

The deferred init subsection itself is accepted, including its explicit statement that the init claims are absent here and travel with scm-init-create-only.

3. Current-main composition and terminal CI

Current main is 583ffd661c0037c0f3a10a8410d4cbeb5b4c5c07. This head's merge base is still 7f71ee34094d9879ea06a69d25ac9f6186c3acb5; it is six main commits behind. Those six include #10025, the exact main-owned correction for the inherited unit-test red.

Merge current main (or a newer tip), regenerate every declared projection from the composed authority, and obtain terminal exact-head CI. The pending workflow on 01ebacb is tied to the obsolete base and cannot be the qualifying receipt even if its required aggregate reports green.

Exact remaining bar

  1. Correct the two residual PR-body statements.
  2. Mark the plan's baseline, landed, and future sections truthfully.
  3. Compose current main including #10025, regenerate, and obtain terminal exact-head green CI.

No source-code, carrier, witness, or mutation redesign is requested. No approval carries on 01ebacb.

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head native ruling — f2ff7ae344fe814399b152c2dadcbc6f6488e90d

REQUEST_CHANGES

The implementation bar remains closed: the generic gunbc run CliWireResponse binding, malformed nominal-wire classification, no-output refusal for an invalid exit, five-plus-twenty-one seed declaration census, init subtraction, and the #10025 main-owned shell-refusal correction are accepted. The first floor attempt's single BUDGET-REFUSED row is not a semantic failure: it produced zero failed verdicts and one undecided result after exceeding the row's 500 ms CPU ceiling. One failed-job rerun is the correct response; do not patch SCM semantics or the witness to make that event disappear.

Three bounded blockers remain.

1. One PR-body statement still belongs to the removed init/write slice

The final “Named, not hidden” section still says the write renderer does not decompose RepositoryEncodeRefusal arms. This PR contains no write renderer after init moved to #10026. Remove that bullet or move it to the write/init PR.

2. The plan still states mutually incompatible time slices

docs/plans/scm-demo-cli-rebuild.md opens by saying it is only a plan and that nothing claims to be built, then contains a LANDED section with execution receipts. Describe it as a plan and historical ledger, or otherwise scope that opening claim.

The same document still states in present tense that scm_log_cli_response and scm_status_cli_response have no consumer, although this branch adds gunbc.scm.cli; mark that as the baseline gap before this change. It also still presents Commands::Scm plus scm_verb as the required host binding, although this PR deliberately chose the generic run_verb outcome seam. Mark the direct subcommand as a future ergonomic surface rather than unfinished work required for the binding landed here.

The deferred-init subsection itself is accepted.

3. Recompose current main and obtain a terminal exact-head receipt

This head composed main@583ffd661c0037c0f3a10a8410d4cbeb5b4c5c07. Current main is now 1ad016a2174c9c6fdb30def5ee03b6441c15c395. Compose the current tip, regenerate every declared projection from the composed authority, and obtain terminal exact-head green CI. The rerun on f2ff7ae may classify the one budget interruption, but it cannot qualify a head that no longer contains current main.

No source-code, carrier, witness, mutation, or init redesign is requested. No approval carries on f2ff7ae.

gunbc-ci-auto-heal and others added 3 commits September 2, 2026 12:28
…o write renderer

Reviewer's native 5089156132 items 1 and 2 on #9864.

1. The PR body's "Named, not hidden" section still named a defect in the write
   renderer's handling of RepositoryEncodeRefusal arms. There is no write
   renderer in this PR -- it left with init for #10026. Bullet removed.

2. Three incompatible time claims in docs/plans/scm-demo-cli-rebuild.md:

   - It opened "It is a plan, not a receipt: nothing here claims to be built"
     and then carried a LANDED section full of execution receipts. Now states
     that it is a plan AND a historical ledger, separated by section, and that
     a present-tense statement in the plan half describes the BASELINE rather
     than the tree today.

   - It said in the present tense that scm_log_cli_response and
     scm_status_cli_response have NO consumer. This branch adds that consumer.
     Marked as the baseline gap this change closes, pointing at the receipts.

   - It presented `Commands::Scm` + `scm_verb` as the required host binding.
     That is NOT what landed, deliberately: binding CliWireResponse once in the
     generic run_verb outcome seam makes EVERY wire-returning entry reachable
     instead of only the scm family -- one binding rather than one per verb. Now
     marked as a future ergonomic surface rather than unfinished work required
     by the binding that shipped.

The deferred-init subsection the reviewer accepted is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head re-review — 91550f772c734a631741b4ad9c851140dfb13deb

REQUEST_CHANGES

The four bounded items from native review 5089156132 are discharged.

  • The removed write-renderer bullet is gone.
  • docs/plans/scm-demo-cli-rebuild.md now truthfully separates its baseline plan from its landed ledger.
  • The old no-consumer statement is scoped to the baseline this change closes.
  • Commands::Scm / scm_verb is correctly marked as a future ergonomic surface rather than unfinished work required by this PR.
  • The current body and source name the real gunbc run outcome seam.

The implementation bar remains accepted: the one generic CliWireResponse binding, malformed nominal-wire refusal before ProcessExit, no stdout for a malformed exit, the exact 26-declaration seed census, init subtraction, and the main-owned #10025 correction.

The earlier floor event is also closed as classified. One row produced no verdict after the 500 ms CPU ceiling, with failed=0; the single rerun returned green. That is a transient threshold crossing at the available evidence, not an SCM semantic regression. Do not patch or repeatedly rerun around it.

Two blockers remain on this exact head.

1. The checked-in add/commit plan now contradicts the object model that this head composed

The Next increment section still labels the following as “settled by execution”:

  • source content enters through store_node(store, n: Node);
  • a source file is represented by a synthetic semantic Node;
  • commit can recover the staged identity by rebuilding that node;
  • immediate content-addressed storage with no staging authority is the honest add model.

That is the pre-#9891 model. This head contains merged #9891, under which authored bytes enter through the authored-source object arm, and the accepted remaining Finding 6 is that add/commit need a CorpusManifestObject joining path, semantic root, and authored-source identity, plus a staging authority. The same document's final section already names CorpusManifestObject, so it presently publishes both designs at once.

Do not design Finding 6 inside this CLI-binding PR. Remove the obsolete recipe, or mark it explicitly as superseded historical reasoning and state the actual block: CorpusManifestObject plus staging authority must be designed before production add/commit. The phrase “settled by execution” cannot remain over a route the composed object model has invalidated.

2. Current-main composition and terminal exact-head CI

This head's second parent is main@a0f03e41c992c9c1f4d404bda099804494b2629b. Current main is now 533264517ca822002519b96cc670c7832dcbc87f. Compose the current tip or a later one, regenerate every declared projection from the composed authority, establish the required fixed point, and obtain terminal exact-head green CI. The workflow on 91550f7 is still nonterminal and cannot authorize landing in any event.

Exact remaining bar

  1. Make the plan carry one truthful post-#9891 add/commit model boundary.
  2. Compose current main, regenerate to fixed point, and obtain terminal exact-head green CI.

No source-code, carrier, classifier, witness, mutation, or init redesign is requested. No approval carries on 91550f7.

gunbai-bot Bot added a commit that referenced this pull request Sep 2, 2026
* Record the SCM demo CLI rebuild plan and its API inventory

A working 'gunbc scm' CLI existed and was lost with an uncommitted /tmp worktree, along
with the plan doc describing it. This commits the survey so the rebuild does not start by
re-reading every scm module, and so the next context loss costs nothing.

The useful finding from re-surveying: the gap is smaller and more specific than 'build a
CLI'. gunbc.scm.render ALREADY reaches CliWireResponse via scm_log_cli_response and
scm_status_cli_response -- they simply have no consumer outside
dag/test/claim/scm/scm_render_witness_test.dag, which is the unwired-renderer state
gunbc.cli_dispatch_surface already records.

It also states why the corpus's idiomatic instrument shape cannot substitute for the host
binding: 'fn check(...) -> ProcessExit' can only emit text on FAILURE (exit_failure's
reason), and log/status must print on success. That is the argument for the main.rs work
rather than a workaround, and it is the thing I would otherwise have had to re-derive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Host reader and pure outcome map for CliWireResponse

gunbc.scm.render already produces CliWireResponse (scm_log_cli_response,
scm_status_cli_response) and nothing outside its witness test consumes it -- the answer is
computed and discarded, which is the unwired-renderer state gunbc.cli_dispatch_surface
records. This adds the two host-side pieces that binding needs.

classify_cli_wire sits beside classify_exit as the single authority for reading the
variant shape, so the driver seam cannot fork it. A missing or wrongly-shaped field is
NotCliWire rather than a default: defaulting bytes to "" would print nothing and exit 0,
which is a fabricated plausible output.

cli_wire_outcome is the total, pure map from that class to what the host does -- bytes,
status, message. Pure for the reason exit_status_for records about itself: the inlined
version of that decision dropped a case and reported success for every failure. It lives
in the LIB, not beside the driver, because main.rs is a BIN target and the rust-unit-tests
job runs 'cargo test -p v1-compiler --lib' -- a test written next to the driver is compiled
by clippy and executed by nobody. exit_status_for_class is shared by both paths so the wire
path and the plain ProcessExit path cannot disagree about what a verdict means.

NotCliWire yields None rather than a failure, so the driver falls through to classify_exit
and every existing ProcessExit entry behaves exactly as before; a test pins that.

5 executing witnesses: bytes written with the response's own exit, a rendered answer that
still fails, the renderer's refusal not reading as an empty success, the fall-through, and
the inherited ExitFailure{code:0} refusal.

NOT YET WIRED into run_verb -- that is the next commit, and until it lands this reader has
no production consumer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Wire CliWireResponse into run_verb: the SCM answer reaches an operator

gunbc.scm.render has produced CliWireResponse since it was written, and outside its
witness test nothing consumed it. A function returning one hit classify_exit's
NotProcessExit arm and the run refused with 'wrap the result in ExitSuccess /
ExitFailure', so every gunbc scm answer was computed and discarded -- the unwired-renderer
state gunbc.cli_dispatch_surface records.

run_verb now tries cli_wire_outcome first and falls through to classify_exit unchanged for
every other value. Binding it in the OUTCOME SEAM rather than under a new 'scm' subcommand
keeps dispatch peripheral (§3): one binding serves every wire-returning entry instead of
the host growing an arm per verb.

dag/gunbc/scm/cli.dag is the entry the host invokes -- scm_log and scm_status, composing
the read side onto a declared plain-terminal capability. The capability is declared, not
detected: a host that learns to report a real one passes it in.

PROVEN BY EXECUTION, not by typecheck:

  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_log --arg path=/tmp/no-such-repo
  cannot read repository at /tmp/no-such-repo
    No such file or directory (os error 2)
  repository unavailable
  EXIT=1

That is the renderer's own document on stdout AND a nonzero exit -- the
'printable response carrying a failing exit' case, which is the one an absorbing
implementation would have turned into either silence or a spurious 0.

Also dissolves a fork I had just created: main.rs's exit_status_for carried its own copy of
the verdict map, and once the wire path needed the same decision that copy was two places
free to drift. The body now lives in cli_run::exit_status_for_class, where an executing
test can reach it -- rust-unit-tests runs 'cargo test --lib', and main.rs is a bin whose
tests are compiled by clippy and run by nobody.

clippy --all-targets -D warnings: clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Record the working read surface and state its evidence boundary

Receipts for the three executed cases, including the load-bearing one (bytes printed AND a
nonzero exit), so the next session does not re-derive them.

States the rung honestly rather than implying the e2e is covered: the five cli_wire_outcome
tests execute in CI, and the end-to-end runs are a MANUAL receipt. An integration test
would live in src/v1/tests/, which clippy compiles and no CI step runs; a .dag witness
cannot substitute because scm_log reads a file and the SCM witnesses are SubstrateInputsOnly.
So the e2e path is mitigatable and its next-rung trigger is a CI step that runs an
integration target -- not another test file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Make gunbc.scm.cli's own decisions witnessable, and enroll them

gunbc.scm.render is already witnessed against PlainTerminal and an ASCII no-colour
capability. Nothing held gunbc.scm.cli to passing THOSE: setting color: true on
plain_terminal_capability would have turned no claim red, which is the inert-check shape
DESIGN calls worse than absent.

The composition is split from the read -- scm_log_response / scm_status_response take the
read's RESULT and are pure, while scm_log / scm_status supply it from a path. That split is
what makes the claims authorable at all: the verbs read a file and this witness family is
SubstrateInputsOnly, so a claim can never call them.

Five enrolled claims: the unavailable-repository bytes (pinned to the same expected string
the render-level witness uses, so an equal answer is evidence the PLAIN target and
capability were passed, not merely that something was), its failing exit, the empty-log
answer with a succeeding exit, the same for status, and the capability row itself so a
reader editing it learns it is a contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* scm init: the first write verb, and a real round trip

  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_init --arg path=/tmp/demo-repo.json
  initialized repository at /tmp/demo-repo.json        rc=0, file created

  ... --function scm_log --arg path=/tmp/demo-repo.json
  no commits yet

init writes the document, log reads it back. That settles the open question the plan doc
recorded: a host WRITE is permitted from gunbc run, so the remaining write verbs are a
modeling question rather than a permissions one.

render.dag gains the write-answer renderer. A write verb answers with what the write DID,
and its four outcomes are not one line with a flag -- each names a different failure and a
different next action. RepositoryWriteByteCountUnrepresentable EXITS FAILURE even though
bytes reached the disk: a write whose reported size is not a magnitude has not been
confirmed, and reporting success for it is the fabricated plausible output §5 forbids.

TWO LOSSES NAMED IN THE SOURCE RATHER THAN HIDDEN:

- The codec refusal carries a typed RepositoryEncodeRefusal with six arms (uncontained
  target, root not in store, checkout not in commits, duplicated reference, reference
  outside allocator, parent not in commits) and this renderer does not decompose it, so an
  operator learns THAT encoding refused and not WHICH invariant failed. Rendering it needs
  a per-arm function over ObjectId and RepositoryCommitRef.
- init does NOT refuse an existing repository. save_repository writes unconditionally, so
  init over a populated path overwrites it. Refusing needs a read-before-write that is not
  expressible as one outcome here. That is why this verb is not offered as a safe default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Record what add and commit need, including the one design step

Settles by execution what was open: a host write IS permitted from gunbc run, content
enters via store_node, a Node is built with node_synthetic (the test modules' 'atom' is a
LOCAL helper, not an authority), and mint_repository_commit requires store_contains(root),
so add must precede commit -- and because identity is content-derived, commit can re-derive
an added object's ObjectId by rebuilding the same node.

Names the design step rather than sketching it: add composes store_node's 2 arms with
save_repository's 4, and commit composes mint's 4 with the same save. A renderer taking two
outcome values would encode 'which one failed' positionally and the arms multiply. One
modeled ScmWriteOutcome is the increment's real content. I deliberately did not improvise
it into cli.dag at the end of a long session, because a write-verb outcome invented at a
call site is the anemic modeling this repository keeps paying for.

Also records that 'add' has no staging authority to persist to -- repository_status takes
pending as a PARAMETER because what is staged is not a fact the document carries -- so a
literal stage-now-commit-later add needs a staging authority to exist first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* init refuses an occupied path: naming a destructive default did not make it fail-closed

Review 58044 was right and this was the worst thing in the PR. save_repository writes
unconditionally -- correctly, it is persistence and not policy -- so a verb offering
initialization has to decide for itself whether there is anything at the path it would
destroy. The previous revision skipped that decision, called save directly, and NAMED the
overwrite in a comment. DESIGN §5's review bar is that a diff landing a non-fail-closed
failure arm is a hard reject regardless of what else it delivers; an annotation is not a
refusal.

gunbc.scm.init owns the decision, because whether there is anything to destroy is a fact
about the repository and deciding it in the CLI module would put policy in the realization
layer. ScmInitOutcome has three arms and no 'initialized: Bool' beside a save outcome -- a
value whose flag disagreed with its shape would have no spelling -- and the two refusals are
distinct because the operator's next action differs: a path that already IS a repository is
not the same as a path holding someone else's bytes.

MEASURED, including the case that proves the destruction is actually closed:

  fresh path        -> initialized repository at /tmp/d2.json
  same path again   -> refusing to initialize /tmp/d2.json
                         a repository is already there, and init would replace its whole history
  foreign file      -> refusing to initialize /tmp/foreign.json
                         a file is already there that is not a repository, and init would destroy it
  foreign file after-> 'not a repo'   (intact -- read back, not assumed)

THE RESIDUAL IS STATED IN THE MODULE RATHER THAN IMPLIED. RepositoryFileUnreadable fuses
'absent' with 'present but unreadable' and carries the host's error STRING. This module
refuses to branch on that text, because deciding a destructive question by matching an errno
spelling is the stringly reasoning the substrate exists to remove. So the proceed arm is
'unreadable', not 'absent'. Closing it needs a modeled path-existence observation distinct
from a read failure, which does not exist yet and is the module's next-rung trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Make scm init proceed only from an established absence, and receipt the wire host Rust

Three blocking findings from review 58060 on #9864.

1. `init` was fail-open on `RepositoryFileUnreadable`. Proceeding there is a guess
   about the host's permission model, not an observation -- a file can be unreadable
   and perfectly truncatable, so the proceed arm could destroy the bytes it existed to
   protect. The residual was NAMED in the module header, and a named residual does not
   refuse. `initialize_repository` now takes a DIRECTORY and a NAME and routes through
   `extdeps.filesystem.filesystem_io` `filesystem_file_observation`: absence is
   established from a listing that succeeded and did not name the entry, and only that
   arm reaches `save_repository`. Present bytes are classified (repository vs foreign)
   for the operator's benefit but both refuse; indeterminate and disagree refuse as
   `ScmInitRefusedPathUnobserved`, carrying the host's cause.

   Executed, four cases: fresh directory writes 166 bytes; an existing repository
   refuses; a foreign file refuses and reads back byte-intact; a directory with mode 000
   refuses naming `Permission denied (os error 13)` rather than widening to "nothing is
   there". Three hermetic claims enrolled beside them in
   test.claim.scm.scm_cli_witness -- no refusal renders as a success exit, the three
   refusals render differently, and the unobserved arm names what could not be observed.

2. The hand-authored Rust had no seed-growth receipt. `gunbc.cli_wire_host_admission`
   enumerates all 11 declarations at identity grain, states why the host process and the
   interpreter Value both lack a .dag denotation today, records that
   `exit_status_for_class` is a relocation rather than new behaviour, and names two
   capability-grain triggers. It does NOT admit the growth; the disposition is Terminal
   and fails closed without an operator ruling. Wired into
   `gunbc.seed_growth_admission`.

3. `gunbc.cli_dispatch_surface` still asserted there is NO route a user can invoke for
   the scm family, which this branch made false. The row now names the real route and
   says what changed, and its stage0 mirror is regenerated from the emitter (the
   candidate tree differed from the committed mirror by exactly this one string).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Regenerate the Rust source-type bindings from their authority

main carries an updated `proof` row in gunbc.rust_source_type_bindings whose
committed projection was never regenerated, so origin/main is itself in a
drifted state and every branch that merges it inherits a failing regen phase.
Measured here rather than assumed: both the .dag authority and the .rs
projection on this branch are byte-identical to origin/main, and regen still
reports first_generation_equal=false for this one file.

The fix is the projection, not the authority: the generated file now carries
what the row says. The build lane is green at this head -- regen
first_generation_equal=true, generated-artifact 35/35 matched, 0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* The relocation note was prose in a String row, which §4c quarantines

cli_wire_host_relocation_note was a `data ... : String` whose only purpose was
commentary, with no consumer -- the neighbouring SeedGrowthJustification did not
reference the symbol, it referenced the NAME inside its own prose. That is the
state DESIGN §4c names: an ordinary String declaration carrying commentary is
mechanically indistinguishable from program data, and `//` is the quarantine
boundary that keeps the two apart.

It is authored rationale about why the roster counts a relocation, so it becomes
an annotation on the declaration it explains. The FACT it carried does not
disappear with it: that exit_status_for_class is a relocation rather than new
behaviour is now stated inside the justification's own `reason`, where a
consumer reading the receipt sees it, rather than in a sibling row a consumer
would have to know to look up.

Found by review 58455 on gunbc#9864.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Admit the entry name, and make the init decision witnessable

Two of the four findings in review 5085479276 on gunbc#9864.

THE NAME WAS USED TWICE AND GUARANTEED ONCE. initialize_repository asked the
listing about `name` and separately joined directory + "/" + name into a path,
with nothing making those the same subject. `subdir/repo.json` is asked about as
a child of the listed directory while the join reaches past it, `../victim`
leaves it entirely, `.`/`..`/`""` name no child at all, and -- the case that
belongs to the filesystem module rather than to any caller -- a name carrying a
newline can make the membership test answer yes for an entry that is not there,
because newline is Filesystem.List's own delimiter.

FilesystemEntryName is sole_constructor, so admission is unavoidable rather than
advisory, and the refusal names which rule rejected the spelling. Its scope is
stated honestly: it is adopted at this consumer, filesystem_entry_presence still
takes a String, and widening that is a replacement migration over the whole
population with its own next-rung trigger -- not smuggled into the change that
introduces the carrier.

THE DECISION WAS NOT WITNESSED, ONLY ITS RENDERING. Every enrolled init claim
built a refusal outcome and checked how it printed, so nothing executed the step
that CHOOSES an outcome from an observation; a mutation routing indeterminate to
the write arm left the family green. scm_init_decision is that step with the two
operations lifted out, and the four arms are now driven one fact apart through
the REAL fold -- FilesystemEstablishedAbsence being sole_constructor means a
witness cannot hand in a fabricated absence, which forces the evidence to cover
filesystem_file_observation and the decision together.

Executed: routing indeterminate to a present-refusal takes
an_unobservable_path_does_not_authorize_a_write red; routing a real absence away
from the write arm takes only_an_established_absence_may_create red. The third
mutation -- routing DISAGREEMENT to the write arm -- is unwritable, because
ScmInitMayCreate carries the sole_constructor absence and no arm can manufacture
one. The rung is split on that boundary rather than averaged.

The refusal vocabulary is one type (ScmInitRefusal) consumed by both the decision
and the outcome, so a refusal has one spelling rather than two, and the outcome
never carries an arm that is only ever intermediate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* A malformed wire response is not "some other type"

Third of the four findings in review 5085479276 on gunbc#9864.

classify_cli_wire returned NotCliWire for two materially different situations:
a value of some other type, and a value that NAMES CliWireResponse but does not
inhabit it. cli_wire_outcome answers None for NotCliWire so the caller falls
through to classify_exit, which meant a malformed wire response was reported as

  error: function `f` returned `...`, not `ProcessExit`

-- true, useless, and about a type the value never claimed. It hides that the
value claimed a type it does not inhabit, and it sends the caller to the wrong
remedy: wrap this in ExitSuccess, when the actual defect is a missing `bytes`.

MalformedCliWire is its own arm and REFUSES rather than falling through, exiting
2 like the other shape refusal. NotCliWire keeps its fall-through unchanged,
which is what preserves every pre-existing ProcessExit entry point.

THE TESTS THE REVIEW SAID DID NOT EXIST. All five original tests started from an
already-classified CliWireClass, so classify_cli_wire had no coverage at all --
the boundary carrying the defect was the one nothing executed. Seven tests now
build real interpreter values: a positive control that a well-formed printable
still classifies as printable, a control that a plain ProcessExit and a bare
string still fall through (the case that must NOT become malformed, or every
existing entry point breaks), the four malformed shapes, and one that the
refusal reaches the host with status 2 and no stdout.

Executed: restoring the fall-through for MalformedCliWire takes
a_malformed_wire_response_refuses_rather_than_falling_through red and leaves the
other twelve green, so the arm is load-bearing for exactly the reported case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* The seed-growth receipt undercounted the module I had just added

Adding cli_wire_classify_tests put thirteen hand-authored declarations into
cli_run.rs that the receipt did not name, while it went on asserting "+11 FROM
THE MERGE BASE, ENUMERATED ABOVE". A roster whose subject is hand-authored
declarations, silently missing thirteen of them, is the defect it exists to
prevent -- and it was introduced by the change that fixed a different one.

Enumerated at 25 and recomputed: 449 insertions in cli_run.rs, 84 changed lines
in main.rs, re-derived against origin/main rather than a stale local `main` ref.

The five test HELPERS are counted, not just the seven test fns. The roster's
subject is declarations, and a helper is one; waving them through as "just
tests" is the same netting this receipt already refuses for the relocated
exit_status_for_class.

Two further .rs files differ and the row says WHY they are outside the subject
rather than omitting them: gunbc_cli_dispatch_surface.rs and
gunbc_rust_source_type_bindings.rs are generated projections regenerated from
their .dag authority, so they are excluded by construction, not by exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Close the init TOCTOU with a create-only write, not a better check

Review 5085479276 on gunbc#9864, finding 1: initialize_repository established an
absence from a directory listing and then called save_repository, which writes
UNCONDITIONALLY. The decision was honest and the write was simply not
conditioned on it, so an actor creating the file between the two made init
truncate exactly the bytes it advertises that it refuses to touch.

THIS IS NOT REPAIRABLE BY OBSERVING MORE CAREFULLY. Check-then-write is two acts
with a gap, and re-observing only makes the gap smaller. The existence test and
the creation have to BE one act, which is a property of open(2) with
O_CREAT|O_EXCL and not something a fold over two modeled operations can express.
Construction over validation at a boundary where validation structurally cannot
win.

So the substrate gains the operation rather than the caller gaining a check:
extdeps.filesystem.filesystem_io WriteCreateNew, a FileWriteCreateNew verb in
the emit stage, and one hand-authored realization. gunbc.scm.repository_save
gains create_repository beside save_repository -- persisting a repository that
exists and creating one that must not exist are different subjects, and the
unconditional write stays correct for the first.

NOT WriteOwnerOnly, WHICH ALREADY CALLS create_new. Its O_EXCL is incidental to
setting a mode at creation -- meaningless on a path that already exists -- and
is not a contract. Owner-only MODE and create-only EXISTENCE are independent
facts; a caller taking create-only from it would silently also take 0600 and
would break the day owner-only stopped needing O_EXCL. Reusing a realization
detail in place of a modeled fact is the inversion §3 names, and the review
rejected it explicitly before this landed.

THE REFUSAL DOES NOT CLASSIFY ITSELF, and that is deliberate. It carries the
host's error verbatim and does not report whether the cause was "already
existed" or "permission denied": the transport's channels cannot separate them,
and deciding it by matching the error TEXT would be a heuristic standing in for
an observation. The caller learns what it needs in order to refuse and does not
learn a classification nothing measured. That gap has its own next-rung trigger.

Executed, and the mutation is the original defect rather than an invented one:
replacing create_new with create+truncate -- what the code did before -- takes
create_new_refuses_a_path_that_already_exists_and_leaves_its_bytes red while the
positive control stays green. The load-bearing assertion is that the EXISTING
BYTES SURVIVE, not that an error is returned: a write that truncated and then
reported failure would satisfy a weaker test and still have destroyed the file.

Seed growth is receipted in gunbc.filesystem_create_new_admission and rostered,
with a trigger naming the capability (creation exclusivity declarable as a
property of a write, with the realization deriving the flags) rather than an
artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Move the create-only rationale to module-item grain, and fix its imports

Two defects my own build lane caught rather than a reviewer.

The WriteCreateNew annotation sat INSIDE the service body, which §4c refuses:
only module-item grain is modeled, and an operation lives inside a service's
declaration. It now sits above the service that contains it, and says why it is
there rather than on the operation it describes -- so the next author does not
repeat the move.

The seed-growth row imported DeclarationRef and WholeDeclaration from
gunbc.seed_growth, which does not export them; std.decl_ref does.

Build lane green: regen first_generation_equal=true, generated-artifact 35/35,
0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head composition ruling — 91550f772c734a631741b4ad9c851140dfb13deb

REQUEST_CHANGES

The two content/text blockers from review 5089156132 are closed.

  • The PR body no longer attributes a RepositoryEncodeRefusal-arm defect to a write renderer that left with init.
  • docs/plans/scm-demo-cli-rebuild.md now tells the truth about its two time slices: plan/baseline above LANDED, historical receipt below it; the no-consumer statement is explicitly the baseline gap this PR closes; and Commands::Scm/scm_verb is correctly a future ergonomic surface rather than a prerequisite for the generic run_verb binding.

No source-code, carrier, witness, mutation, init, or narrative redesign remains.

Binding ruling on the moving-main question

The literal rule I previously stated — “compose the current tip and then obtain terminal CI while it is still the current tip” — is not an honest gate on a main branch advancing faster than the run. It does not converge. I withdraw that literal formulation.

The binding rule is a pinned green composition plus an explicit forward-delta admission:

  1. Exact head H composes a named main checkpoint B, regenerates every declared projection from that composed authority, reaches a fixed point, and obtains terminal exact-head CI.
  2. At merge authorization, observe main at T and adjudicate the entire forward delta B..T.
  3. If that delta is disjoint from the PR's authored authority, generated-output dependency closure, and the compiler/witness/CI machinery that qualified the PR, the green receipt remains valid. Authorization is bound to (H, T). If main moves again, adjudicate only T..T'; do not restart CI merely because time passed.
  4. If the delta overlaps any of those populations, or its effect is unclassified, compose it, regenerate, and obtain a new exact-head receipt. If overlapping changes keep arriving, wait for a quiet admission window or use a merge queue/frozen merge slot; do not burn runs chasing every unrelated tip.

Therefore dashboard ready=true is necessary but not sufficient until the dashboard carries this forward-composition standing. A selection over the criteria it currently models cannot define the full admission population. Native APPROVE likewise will not be used as a content-only signal going forward; content-only acceptance will be a comment, and APPROVE will mean the exact head is merge-admissible under this rule.

Application to 91550f7

This forward delta is not disjoint.

91550f7 composes main@a0f03e41c992c9c1f4d404bda099804494b2629b. Main has since taken #10033 (f6d872e09b252e13c1a8c3e4100792712b7a3a5f), which changes the same hand-authored host seam, src/v1/stage0/src/cli_run.rs, and installs a crate-wide reachability gate whose subject includes declarations and tests this PR adds to that file. Exact-head CI on 91550f7 cannot have executed that newly landed gate over the combined tree. A clean textual merge would not prove the semantic join.

Let the present run reach its terminal verdict; it remains useful exact-head evidence and makes any later composition failure attributable. Then compose a checkpoint containing #10033 (normally the observed main tip), regenerate all declared projections, and run exact-head CI once. After that run, later main movement is handled by the bounded forward-delta rule above, not by automatic recomposition.

The earlier 500 ms BUDGET-REFUSED attempt is closed as non-semantic by the successful floor on this head. The only remaining hold is composed-tree qualification.

…s PR's fixes

#10026 merged before #9864 while carrying an older copy of THIS PR's CLI slice, so
main briefly regressed four reviewer-mandated fixes. That is the predecessor-
ordering hazard review 5089156132 named in its own finding 6, realized in the
opposite direction from the one it anticipated: it warned #10026 must not repair
the slice independently, and instead #10026's stale copy landed first.

Resolved per file on merits, not by picking a side:

  scm/cli.dag              main's init wiring (init is REAL now) + my two fixes
                           re-applied: "every SCM read answer" (there is no
                           `gunbc scm` subcommand) and dropping the unused
                           empty_repository import, which is still unused because
                           init calls empty_repository() inside init.dag.
  scm_cli_witness_test.dag main. The init claims were removed from this PR
                           correctly when init was not in it; init has landed, so
                           they belong again.
  cli_dispatch_surface.dag main. Its successor row says init IS reachable, which
                           is now true. The row must describe the tree, not the PR.
  seed_growth_admission    main. Adds the filesystem_create_new roster entry.
  cli_wire_host_admission  MINE. The 26-declaration derivation with nested module
                           identities is what review 5089156132 required; main's
                           copy reverted it to a flat path and an undercount.
  scm-demo-cli-rebuild.md  NEITHER side was right. Kept my three scoping fixes
                           (plan-vs-ledger, the baseline-gap framing, Commands::Scm
                           as future ergonomics) and reframed the init subsection
                           from DEFERRED to LANDED-with-open-findings, naming the
                           two model defects review found in #10026.
  cli_run.rs               Four conflicted hunks resolved to mine (the review-58518
                           malformed-wire refusal and the review-58567 doc repair).
                           Everything outside those hunks is main's, which MATTERS:
                           main added a cwd-mutation reachability module that a
                           wholesale ours-resolution would have deleted. Verified
                           present after resolution.
  gunbc_cli_dispatch_surface.rs
                           Generated projection, not hand-merged. Took main's bytes
                           and re-derived: required-regen reports
                           first_generation_equal=true, so those bytes ARE the
                           projection of the composed authority.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
gunbai-bot Bot pushed a commit that referenced this pull request Sep 2, 2026
#10026 landed with these open. Each was verified against the code before being
accepted, and each carries a control that goes red on the defect.

1. ScmInitialized could contain a FAILED creation. It carried the whole
   RepositorySave, so `ScmInitialized { save: RepositoryFileUnwritable { .. } }`
   was spellable and told every consumer matching the outer arm that
   initialization happened. The renderer noticed and exited nonzero, which
   repaired the message and not the carrier. ScmInitialized now carries only
   { path, written } from a successful create; init_outcome_of_save is a total
   fold over all four save arms and every other arm reaches ScmInitWriteRefused.

2. The established-absence authority was discarded before actuation. The code
   matched ScmInitMayCreate(_) and wrote to an independently supplied path, so an
   absence established for one subject could sit beside a create against another
   and no mutation to the write target went red. The token carries
   { directory, name }, so create_repository_at_absence now DERIVES the target
   from it -- there is no second target to disagree with.

   The rung prose is corrected rather than softened. Four external reviews read
   the sole_constructor and endorsed a rung-4 authorization claim; the designated
   reviewer read the MATCH and found the token was never consumed. A capability
   carried past its actuation authorizes nothing. The annotation now splits the
   rung honestly and says plainly that no preflight closes a TOCTOU -- O_EXCL
   does -- so the observation decides what the operator is told and which subject
   may be written, not whether the race exists.

3. WriteCreateNew erased the post-create write-failure state. open-then-write
   leaves a created, zero-or-partial file if write_all fails, while the model
   reports the create did not happen. That is worse than the overwrite race this
   operation closed: the race could destroy someone else's bytes, this
   FABRICATES a repository nobody wrote. Fixed by construction rather than by
   modeling two dispositions -- content is staged to an O_EXCL sibling and the
   target name is claimed with hard_link, which fails if the target exists, so
   exclusivity moves to the publish step and every earlier failure leaves the
   target absent.

   THE FIRST CONTROL I WROTE FOR THIS WAS A DECORATION, and the dead attempt is
   documented in the test module so it is not rebuilt. It put a DIRECTORY at the
   target; measured against the old construction it PASSED, because create_new
   fails at the OPEN when the name exists so nothing was ever created. The
   replacement injects RLIMIT_FSIZE in a forked child so the create succeeds and
   the write fails -- the one ordering that separates the constructions. Proven
   both ways: green on the fix, RED on the pre-fix construction with "a write
   that failed after creation must leave NO target behind".

4. FilesystemEntryName rejected `/` but admitted backslash and NUL while
   WriteCreateNew is implemented on non-Unix. `..\victim` is a parent traversal
   there, and a NUL makes the syscall see a PREFIX of the name the listing was
   asked about. The grammar is the UNION over supported targets, not the one the
   author happens to run on.

Still open from that review: finding 5 (the emitted realization in
05_emit_rust.dag is independently mutable, plus two weak assertions) and finding
6 (compose #9864's final authority, which must land first).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9
@briansrls
briansrls merged commit 5a91f31 into main Sep 2, 2026
6 checks passed
@briansrls
briansrls deleted the scm-cli branch September 2, 2026 15:56

Copy link
Copy Markdown
Contributor

Post-merge exact-tree ruling

I re-anchored the final PR head and the actual squash result rather than treating the dashboard tally as the ruling.

Accepted

Exact PR head af506ef2d264ec995e32292f0710712d7baa4fdc completed workflow 33640744335 green across required-witnesses-build, required-witnesses-floor, rust-unit-tests, fabric-evidence, and the aggregate witnesses job. The generic gunbc run CliWireResponse binding, malformed nominal-wire refusal, no-output invalid-exit behavior, exact seed census, and main composition are accepted.

The actual squash result is 5a91f3183e50a959c0f26264a9265391fab0c76e. Its push workflow 33651809509 also completed green across every required lane, including Rust unit tests and fabric evidence. The combined landed implementation is therefore qualified ex post; no rollback or code repair is requested for the wire binding.

The merge-time gate was nevertheless not satisfied

af506ef composed main@de531c35496f2cdd3a223a1d5c5f048f782a3226. The squash landed on parent 4e6a597de07450f3dbde2096e78b09e1fadf7bbd. That eight-commit forward delta was not disjoint: it changed src/v1/stage0/src/cli_run.rs, .github/workflows/witnesses.yml, the witness-floor authority, seed-growth admission, compiler tests, and generated compiler projections. Under the pinned-green-composition-plus-forward-delta rule, that overlap required composed-tree qualification before merge. GitHub CLEAN and dashboard ready=true did not supply it.

The successful push run closes the resulting uncertainty after the fact; it does not turn the pre-merge decision into an admitted one.

One authority-text correction remains on main

docs/plans/scm-demo-cli-rebuild.md still labels the pre-#9891 add/commit recipe “settled by execution” and says authored content enters through store_node, a file is rebuilt as a synthetic semantic node, mint_repository_commit takes ObjectId and checks store_contains, and immediate object-store insertion is the honest substitute for staging. The current object model instead has an authored-source object arm; mint_repository_commit takes SemanticNodeTarget; and the accepted production boundary is CorpusManifestObject plus staging authority before add/commit.

This is a text-only correction on main: mark that recipe as superseded historical reasoning and state the current model block. It is not a request to redesign or revert #9864.

Process ruling

Dashboard ready=true remains necessary but insufficient until it includes forward-composition standing. Native APPROVE will mean merge-admissible, not content-approved with a hidden composition hold. Because this PR was already merged before this re-review completed, I am leaving the historical CHANGES_REQUESTED review intact and recording the retrospective acceptance and residual here rather than manufacturing a late pre-merge approval.

briansrls pushed a commit that referenced this pull request Sep 2, 2026
* Record the SCM demo CLI rebuild plan and its API inventory

A working 'gunbc scm' CLI existed and was lost with an uncommitted /tmp worktree, along
with the plan doc describing it. This commits the survey so the rebuild does not start by
re-reading every scm module, and so the next context loss costs nothing.

The useful finding from re-surveying: the gap is smaller and more specific than 'build a
CLI'. gunbc.scm.render ALREADY reaches CliWireResponse via scm_log_cli_response and
scm_status_cli_response -- they simply have no consumer outside
dag/test/claim/scm/scm_render_witness_test.dag, which is the unwired-renderer state
gunbc.cli_dispatch_surface already records.

It also states why the corpus's idiomatic instrument shape cannot substitute for the host
binding: 'fn check(...) -> ProcessExit' can only emit text on FAILURE (exit_failure's
reason), and log/status must print on success. That is the argument for the main.rs work
rather than a workaround, and it is the thing I would otherwise have had to re-derive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Host reader and pure outcome map for CliWireResponse

gunbc.scm.render already produces CliWireResponse (scm_log_cli_response,
scm_status_cli_response) and nothing outside its witness test consumes it -- the answer is
computed and discarded, which is the unwired-renderer state gunbc.cli_dispatch_surface
records. This adds the two host-side pieces that binding needs.

classify_cli_wire sits beside classify_exit as the single authority for reading the
variant shape, so the driver seam cannot fork it. A missing or wrongly-shaped field is
NotCliWire rather than a default: defaulting bytes to "" would print nothing and exit 0,
which is a fabricated plausible output.

cli_wire_outcome is the total, pure map from that class to what the host does -- bytes,
status, message. Pure for the reason exit_status_for records about itself: the inlined
version of that decision dropped a case and reported success for every failure. It lives
in the LIB, not beside the driver, because main.rs is a BIN target and the rust-unit-tests
job runs 'cargo test -p v1-compiler --lib' -- a test written next to the driver is compiled
by clippy and executed by nobody. exit_status_for_class is shared by both paths so the wire
path and the plain ProcessExit path cannot disagree about what a verdict means.

NotCliWire yields None rather than a failure, so the driver falls through to classify_exit
and every existing ProcessExit entry behaves exactly as before; a test pins that.

5 executing witnesses: bytes written with the response's own exit, a rendered answer that
still fails, the renderer's refusal not reading as an empty success, the fall-through, and
the inherited ExitFailure{code:0} refusal.

NOT YET WIRED into run_verb -- that is the next commit, and until it lands this reader has
no production consumer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Wire CliWireResponse into run_verb: the SCM answer reaches an operator

gunbc.scm.render has produced CliWireResponse since it was written, and outside its
witness test nothing consumed it. A function returning one hit classify_exit's
NotProcessExit arm and the run refused with 'wrap the result in ExitSuccess /
ExitFailure', so every gunbc scm answer was computed and discarded -- the unwired-renderer
state gunbc.cli_dispatch_surface records.

run_verb now tries cli_wire_outcome first and falls through to classify_exit unchanged for
every other value. Binding it in the OUTCOME SEAM rather than under a new 'scm' subcommand
keeps dispatch peripheral (§3): one binding serves every wire-returning entry instead of
the host growing an arm per verb.

dag/gunbc/scm/cli.dag is the entry the host invokes -- scm_log and scm_status, composing
the read side onto a declared plain-terminal capability. The capability is declared, not
detected: a host that learns to report a real one passes it in.

PROVEN BY EXECUTION, not by typecheck:

  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_log --arg path=/tmp/no-such-repo
  cannot read repository at /tmp/no-such-repo
    No such file or directory (os error 2)
  repository unavailable
  EXIT=1

That is the renderer's own document on stdout AND a nonzero exit -- the
'printable response carrying a failing exit' case, which is the one an absorbing
implementation would have turned into either silence or a spurious 0.

Also dissolves a fork I had just created: main.rs's exit_status_for carried its own copy of
the verdict map, and once the wire path needed the same decision that copy was two places
free to drift. The body now lives in cli_run::exit_status_for_class, where an executing
test can reach it -- rust-unit-tests runs 'cargo test --lib', and main.rs is a bin whose
tests are compiled by clippy and run by nobody.

clippy --all-targets -D warnings: clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Record the working read surface and state its evidence boundary

Receipts for the three executed cases, including the load-bearing one (bytes printed AND a
nonzero exit), so the next session does not re-derive them.

States the rung honestly rather than implying the e2e is covered: the five cli_wire_outcome
tests execute in CI, and the end-to-end runs are a MANUAL receipt. An integration test
would live in src/v1/tests/, which clippy compiles and no CI step runs; a .dag witness
cannot substitute because scm_log reads a file and the SCM witnesses are SubstrateInputsOnly.
So the e2e path is mitigatable and its next-rung trigger is a CI step that runs an
integration target -- not another test file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Make gunbc.scm.cli's own decisions witnessable, and enroll them

gunbc.scm.render is already witnessed against PlainTerminal and an ASCII no-colour
capability. Nothing held gunbc.scm.cli to passing THOSE: setting color: true on
plain_terminal_capability would have turned no claim red, which is the inert-check shape
DESIGN calls worse than absent.

The composition is split from the read -- scm_log_response / scm_status_response take the
read's RESULT and are pure, while scm_log / scm_status supply it from a path. That split is
what makes the claims authorable at all: the verbs read a file and this witness family is
SubstrateInputsOnly, so a claim can never call them.

Five enrolled claims: the unavailable-repository bytes (pinned to the same expected string
the render-level witness uses, so an equal answer is evidence the PLAIN target and
capability were passed, not merely that something was), its failing exit, the empty-log
answer with a succeeding exit, the same for status, and the capability row itself so a
reader editing it learns it is a contract.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* scm init: the first write verb, and a real round trip

  gunbc run --entry dag/gunbc/scm/cli.dag --function scm_init --arg path=/tmp/demo-repo.json
  initialized repository at /tmp/demo-repo.json        rc=0, file created

  ... --function scm_log --arg path=/tmp/demo-repo.json
  no commits yet

init writes the document, log reads it back. That settles the open question the plan doc
recorded: a host WRITE is permitted from gunbc run, so the remaining write verbs are a
modeling question rather than a permissions one.

render.dag gains the write-answer renderer. A write verb answers with what the write DID,
and its four outcomes are not one line with a flag -- each names a different failure and a
different next action. RepositoryWriteByteCountUnrepresentable EXITS FAILURE even though
bytes reached the disk: a write whose reported size is not a magnitude has not been
confirmed, and reporting success for it is the fabricated plausible output §5 forbids.

TWO LOSSES NAMED IN THE SOURCE RATHER THAN HIDDEN:

- The codec refusal carries a typed RepositoryEncodeRefusal with six arms (uncontained
  target, root not in store, checkout not in commits, duplicated reference, reference
  outside allocator, parent not in commits) and this renderer does not decompose it, so an
  operator learns THAT encoding refused and not WHICH invariant failed. Rendering it needs
  a per-arm function over ObjectId and RepositoryCommitRef.
- init does NOT refuse an existing repository. save_repository writes unconditionally, so
  init over a populated path overwrites it. Refusing needs a read-before-write that is not
  expressible as one outcome here. That is why this verb is not offered as a safe default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Record what add and commit need, including the one design step

Settles by execution what was open: a host write IS permitted from gunbc run, content
enters via store_node, a Node is built with node_synthetic (the test modules' 'atom' is a
LOCAL helper, not an authority), and mint_repository_commit requires store_contains(root),
so add must precede commit -- and because identity is content-derived, commit can re-derive
an added object's ObjectId by rebuilding the same node.

Names the design step rather than sketching it: add composes store_node's 2 arms with
save_repository's 4, and commit composes mint's 4 with the same save. A renderer taking two
outcome values would encode 'which one failed' positionally and the arms multiply. One
modeled ScmWriteOutcome is the increment's real content. I deliberately did not improvise
it into cli.dag at the end of a long session, because a write-verb outcome invented at a
call site is the anemic modeling this repository keeps paying for.

Also records that 'add' has no staging authority to persist to -- repository_status takes
pending as a PARAMETER because what is staged is not a fact the document carries -- so a
literal stage-now-commit-later add needs a staging authority to exist first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* init refuses an occupied path: naming a destructive default did not make it fail-closed

Review 58044 was right and this was the worst thing in the PR. save_repository writes
unconditionally -- correctly, it is persistence and not policy -- so a verb offering
initialization has to decide for itself whether there is anything at the path it would
destroy. The previous revision skipped that decision, called save directly, and NAMED the
overwrite in a comment. DESIGN §5's review bar is that a diff landing a non-fail-closed
failure arm is a hard reject regardless of what else it delivers; an annotation is not a
refusal.

gunbc.scm.init owns the decision, because whether there is anything to destroy is a fact
about the repository and deciding it in the CLI module would put policy in the realization
layer. ScmInitOutcome has three arms and no 'initialized: Bool' beside a save outcome -- a
value whose flag disagreed with its shape would have no spelling -- and the two refusals are
distinct because the operator's next action differs: a path that already IS a repository is
not the same as a path holding someone else's bytes.

MEASURED, including the case that proves the destruction is actually closed:

  fresh path        -> initialized repository at /tmp/d2.json
  same path again   -> refusing to initialize /tmp/d2.json
                         a repository is already there, and init would replace its whole history
  foreign file      -> refusing to initialize /tmp/foreign.json
                         a file is already there that is not a repository, and init would destroy it
  foreign file after-> 'not a repo'   (intact -- read back, not assumed)

THE RESIDUAL IS STATED IN THE MODULE RATHER THAN IMPLIED. RepositoryFileUnreadable fuses
'absent' with 'present but unreadable' and carries the host's error STRING. This module
refuses to branch on that text, because deciding a destructive question by matching an errno
spelling is the stringly reasoning the substrate exists to remove. So the proceed arm is
'unreadable', not 'absent'. Closing it needs a modeled path-existence observation distinct
from a read failure, which does not exist yet and is the module's next-rung trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Make scm init proceed only from an established absence, and receipt the wire host Rust

Three blocking findings from review 58060 on #9864.

1. `init` was fail-open on `RepositoryFileUnreadable`. Proceeding there is a guess
   about the host's permission model, not an observation -- a file can be unreadable
   and perfectly truncatable, so the proceed arm could destroy the bytes it existed to
   protect. The residual was NAMED in the module header, and a named residual does not
   refuse. `initialize_repository` now takes a DIRECTORY and a NAME and routes through
   `extdeps.filesystem.filesystem_io` `filesystem_file_observation`: absence is
   established from a listing that succeeded and did not name the entry, and only that
   arm reaches `save_repository`. Present bytes are classified (repository vs foreign)
   for the operator's benefit but both refuse; indeterminate and disagree refuse as
   `ScmInitRefusedPathUnobserved`, carrying the host's cause.

   Executed, four cases: fresh directory writes 166 bytes; an existing repository
   refuses; a foreign file refuses and reads back byte-intact; a directory with mode 000
   refuses naming `Permission denied (os error 13)` rather than widening to "nothing is
   there". Three hermetic claims enrolled beside them in
   test.claim.scm.scm_cli_witness -- no refusal renders as a success exit, the three
   refusals render differently, and the unobserved arm names what could not be observed.

2. The hand-authored Rust had no seed-growth receipt. `gunbc.cli_wire_host_admission`
   enumerates all 11 declarations at identity grain, states why the host process and the
   interpreter Value both lack a .dag denotation today, records that
   `exit_status_for_class` is a relocation rather than new behaviour, and names two
   capability-grain triggers. It does NOT admit the growth; the disposition is Terminal
   and fails closed without an operator ruling. Wired into
   `gunbc.seed_growth_admission`.

3. `gunbc.cli_dispatch_surface` still asserted there is NO route a user can invoke for
   the scm family, which this branch made false. The row now names the real route and
   says what changed, and its stage0 mirror is regenerated from the emitter (the
   candidate tree differed from the committed mirror by exactly this one string).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wy8wRfzTK2kvFzj9nAEbyT

* Regenerate the Rust source-type bindings from their authority

main carries an updated `proof` row in gunbc.rust_source_type_bindings whose
committed projection was never regenerated, so origin/main is itself in a
drifted state and every branch that merges it inherits a failing regen phase.
Measured here rather than assumed: both the .dag authority and the .rs
projection on this branch are byte-identical to origin/main, and regen still
reports first_generation_equal=false for this one file.

The fix is the projection, not the authority: the generated file now carries
what the row says. The build lane is green at this head -- regen
first_generation_equal=true, generated-artifact 35/35 matched, 0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* The relocation note was prose in a String row, which §4c quarantines

cli_wire_host_relocation_note was a `data ... : String` whose only purpose was
commentary, with no consumer -- the neighbouring SeedGrowthJustification did not
reference the symbol, it referenced the NAME inside its own prose. That is the
state DESIGN §4c names: an ordinary String declaration carrying commentary is
mechanically indistinguishable from program data, and `//` is the quarantine
boundary that keeps the two apart.

It is authored rationale about why the roster counts a relocation, so it becomes
an annotation on the declaration it explains. The FACT it carried does not
disappear with it: that exit_status_for_class is a relocation rather than new
behaviour is now stated inside the justification's own `reason`, where a
consumer reading the receipt sees it, rather than in a sibling row a consumer
would have to know to look up.

Found by review 58455 on gunbc#9864.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Admit the entry name, and make the init decision witnessable

Two of the four findings in review 5085479276 on gunbc#9864.

THE NAME WAS USED TWICE AND GUARANTEED ONCE. initialize_repository asked the
listing about `name` and separately joined directory + "/" + name into a path,
with nothing making those the same subject. `subdir/repo.json` is asked about as
a child of the listed directory while the join reaches past it, `../victim`
leaves it entirely, `.`/`..`/`""` name no child at all, and -- the case that
belongs to the filesystem module rather than to any caller -- a name carrying a
newline can make the membership test answer yes for an entry that is not there,
because newline is Filesystem.List's own delimiter.

FilesystemEntryName is sole_constructor, so admission is unavoidable rather than
advisory, and the refusal names which rule rejected the spelling. Its scope is
stated honestly: it is adopted at this consumer, filesystem_entry_presence still
takes a String, and widening that is a replacement migration over the whole
population with its own next-rung trigger -- not smuggled into the change that
introduces the carrier.

THE DECISION WAS NOT WITNESSED, ONLY ITS RENDERING. Every enrolled init claim
built a refusal outcome and checked how it printed, so nothing executed the step
that CHOOSES an outcome from an observation; a mutation routing indeterminate to
the write arm left the family green. scm_init_decision is that step with the two
operations lifted out, and the four arms are now driven one fact apart through
the REAL fold -- FilesystemEstablishedAbsence being sole_constructor means a
witness cannot hand in a fabricated absence, which forces the evidence to cover
filesystem_file_observation and the decision together.

Executed: routing indeterminate to a present-refusal takes
an_unobservable_path_does_not_authorize_a_write red; routing a real absence away
from the write arm takes only_an_established_absence_may_create red. The third
mutation -- routing DISAGREEMENT to the write arm -- is unwritable, because
ScmInitMayCreate carries the sole_constructor absence and no arm can manufacture
one. The rung is split on that boundary rather than averaged.

The refusal vocabulary is one type (ScmInitRefusal) consumed by both the decision
and the outcome, so a refusal has one spelling rather than two, and the outcome
never carries an arm that is only ever intermediate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* A malformed wire response is not "some other type"

Third of the four findings in review 5085479276 on gunbc#9864.

classify_cli_wire returned NotCliWire for two materially different situations:
a value of some other type, and a value that NAMES CliWireResponse but does not
inhabit it. cli_wire_outcome answers None for NotCliWire so the caller falls
through to classify_exit, which meant a malformed wire response was reported as

  error: function `f` returned `...`, not `ProcessExit`

-- true, useless, and about a type the value never claimed. It hides that the
value claimed a type it does not inhabit, and it sends the caller to the wrong
remedy: wrap this in ExitSuccess, when the actual defect is a missing `bytes`.

MalformedCliWire is its own arm and REFUSES rather than falling through, exiting
2 like the other shape refusal. NotCliWire keeps its fall-through unchanged,
which is what preserves every pre-existing ProcessExit entry point.

THE TESTS THE REVIEW SAID DID NOT EXIST. All five original tests started from an
already-classified CliWireClass, so classify_cli_wire had no coverage at all --
the boundary carrying the defect was the one nothing executed. Seven tests now
build real interpreter values: a positive control that a well-formed printable
still classifies as printable, a control that a plain ProcessExit and a bare
string still fall through (the case that must NOT become malformed, or every
existing entry point breaks), the four malformed shapes, and one that the
refusal reaches the host with status 2 and no stdout.

Executed: restoring the fall-through for MalformedCliWire takes
a_malformed_wire_response_refuses_rather_than_falling_through red and leaves the
other twelve green, so the arm is load-bearing for exactly the reported case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* The seed-growth receipt undercounted the module I had just added

Adding cli_wire_classify_tests put thirteen hand-authored declarations into
cli_run.rs that the receipt did not name, while it went on asserting "+11 FROM
THE MERGE BASE, ENUMERATED ABOVE". A roster whose subject is hand-authored
declarations, silently missing thirteen of them, is the defect it exists to
prevent -- and it was introduced by the change that fixed a different one.

Enumerated at 25 and recomputed: 449 insertions in cli_run.rs, 84 changed lines
in main.rs, re-derived against origin/main rather than a stale local `main` ref.

The five test HELPERS are counted, not just the seven test fns. The roster's
subject is declarations, and a helper is one; waving them through as "just
tests" is the same netting this receipt already refuses for the relocated
exit_status_for_class.

Two further .rs files differ and the row says WHY they are outside the subject
rather than omitting them: gunbc_cli_dispatch_surface.rs and
gunbc_rust_source_type_bindings.rs are generated projections regenerated from
their .dag authority, so they are excluded by construction, not by exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Close the init TOCTOU with a create-only write, not a better check

Review 5085479276 on gunbc#9864, finding 1: initialize_repository established an
absence from a directory listing and then called save_repository, which writes
UNCONDITIONALLY. The decision was honest and the write was simply not
conditioned on it, so an actor creating the file between the two made init
truncate exactly the bytes it advertises that it refuses to touch.

THIS IS NOT REPAIRABLE BY OBSERVING MORE CAREFULLY. Check-then-write is two acts
with a gap, and re-observing only makes the gap smaller. The existence test and
the creation have to BE one act, which is a property of open(2) with
O_CREAT|O_EXCL and not something a fold over two modeled operations can express.
Construction over validation at a boundary where validation structurally cannot
win.

So the substrate gains the operation rather than the caller gaining a check:
extdeps.filesystem.filesystem_io WriteCreateNew, a FileWriteCreateNew verb in
the emit stage, and one hand-authored realization. gunbc.scm.repository_save
gains create_repository beside save_repository -- persisting a repository that
exists and creating one that must not exist are different subjects, and the
unconditional write stays correct for the first.

NOT WriteOwnerOnly, WHICH ALREADY CALLS create_new. Its O_EXCL is incidental to
setting a mode at creation -- meaningless on a path that already exists -- and
is not a contract. Owner-only MODE and create-only EXISTENCE are independent
facts; a caller taking create-only from it would silently also take 0600 and
would break the day owner-only stopped needing O_EXCL. Reusing a realization
detail in place of a modeled fact is the inversion §3 names, and the review
rejected it explicitly before this landed.

THE REFUSAL DOES NOT CLASSIFY ITSELF, and that is deliberate. It carries the
host's error verbatim and does not report whether the cause was "already
existed" or "permission denied": the transport's channels cannot separate them,
and deciding it by matching the error TEXT would be a heuristic standing in for
an observation. The caller learns what it needs in order to refuse and does not
learn a classification nothing measured. That gap has its own next-rung trigger.

Executed, and the mutation is the original defect rather than an invented one:
replacing create_new with create+truncate -- what the code did before -- takes
create_new_refuses_a_path_that_already_exists_and_leaves_its_bytes red while the
positive control stays green. The load-bearing assertion is that the EXISTING
BYTES SURVIVE, not that an error is returned: a write that truncated and then
reported failure would satisfy a weaker test and still have destroyed the file.

Seed growth is receipted in gunbc.filesystem_create_new_admission and rostered,
with a trigger naming the capability (creation exclusivity declarable as a
property of a write, with the realization deriving the flags) rather than an
artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Move the create-only rationale to module-item grain, and fix its imports

Two defects my own build lane caught rather than a reviewer.

The WriteCreateNew annotation sat INSIDE the service body, which §4c refuses:
only module-item grain is modeled, and an operation lives inside a service's
declaration. It now sits above the service that contains it, and says why it is
there rather than on the operation it describes -- so the next author does not
repeat the move.

The seed-growth row imported DeclarationRef and WholeDeclaration from
gunbc.seed_growth, which does not export them; std.decl_ref does.

Build lane green: regen first_generation_equal=true, generated-artifact 35/35,
0 drifted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Four of review 5089156132's six findings on the init model

#10026 landed with these open. Each was verified against the code before being
accepted, and each carries a control that goes red on the defect.

1. ScmInitialized could contain a FAILED creation. It carried the whole
   RepositorySave, so `ScmInitialized { save: RepositoryFileUnwritable { .. } }`
   was spellable and told every consumer matching the outer arm that
   initialization happened. The renderer noticed and exited nonzero, which
   repaired the message and not the carrier. ScmInitialized now carries only
   { path, written } from a successful create; init_outcome_of_save is a total
   fold over all four save arms and every other arm reaches ScmInitWriteRefused.

2. The established-absence authority was discarded before actuation. The code
   matched ScmInitMayCreate(_) and wrote to an independently supplied path, so an
   absence established for one subject could sit beside a create against another
   and no mutation to the write target went red. The token carries
   { directory, name }, so create_repository_at_absence now DERIVES the target
   from it -- there is no second target to disagree with.

   The rung prose is corrected rather than softened. Four external reviews read
   the sole_constructor and endorsed a rung-4 authorization claim; the designated
   reviewer read the MATCH and found the token was never consumed. A capability
   carried past its actuation authorizes nothing. The annotation now splits the
   rung honestly and says plainly that no preflight closes a TOCTOU -- O_EXCL
   does -- so the observation decides what the operator is told and which subject
   may be written, not whether the race exists.

3. WriteCreateNew erased the post-create write-failure state. open-then-write
   leaves a created, zero-or-partial file if write_all fails, while the model
   reports the create did not happen. That is worse than the overwrite race this
   operation closed: the race could destroy someone else's bytes, this
   FABRICATES a repository nobody wrote. Fixed by construction rather than by
   modeling two dispositions -- content is staged to an O_EXCL sibling and the
   target name is claimed with hard_link, which fails if the target exists, so
   exclusivity moves to the publish step and every earlier failure leaves the
   target absent.

   THE FIRST CONTROL I WROTE FOR THIS WAS A DECORATION, and the dead attempt is
   documented in the test module so it is not rebuilt. It put a DIRECTORY at the
   target; measured against the old construction it PASSED, because create_new
   fails at the OPEN when the name exists so nothing was ever created. The
   replacement injects RLIMIT_FSIZE in a forked child so the create succeeds and
   the write fails -- the one ordering that separates the constructions. Proven
   both ways: green on the fix, RED on the pre-fix construction with "a write
   that failed after creation must leave NO target behind".

4. FilesystemEntryName rejected `/` but admitted backslash and NUL while
   WriteCreateNew is implemented on non-Unix. `..\victim` is a parent traversal
   there, and a NUL makes the syscall see a PREFIX of the name the listing was
   asked about. The grammar is the UNION over supported targets, not the one the
   author happens to run on.

Still open from that review: finding 5 (the emitted realization in
05_emit_rust.dag is independently mutable, plus two weak assertions) and finding
6 (compose #9864's final authority, which must land first).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* The other two parts of finding 5: pin the valid repository, and enrol the fourth refusal

5.2 -- present_bytes_do_not_authorize_a_write_and_are_classified asserted only
`!= may_create` for a DECODABLE repository. That is satisfied by mapping every
present file, valid repository included, to ScmInitForeignFilePresent -- which
would tell an operator a foreign file is in the way of their own repository, and
the claim would have stayed green. The two present arms are one fact apart (same
listing and read, differing only in whether the content decodes), so each is now
pinned to its own arm. That is the difference between classification evidence and
a not-the-write-arm check.

5.3 -- ScmInitNameNotAnEntry became a fourth refusal arm while both renderer
claims still enumerated three. The direct admission test proved the name refuses
EARLY; nothing proved it reaches an operator as its own answer with a failing
exit. Both claims now cover four arms, and the claim is RENAMED to
the_four_init_refusals_render_differently so its name moves with its population --
a claim that names a population and does not move silently covers a shrinking
fraction of its subject.

Not done, and deliberately not guessed at: 5.1, the emitted realization in
05_emit_rust.dag being independently mutable to create/truncate while the Rust
unit tests stay green. That is the same class as the decoration I shipped and
withdrew earlier in this branch, one layer out, and it wants either an executing
emitted-program probe or a construction that prevents the two realizations from
drifting. I have asked the reviewer which shape they want rather than build the
wrong one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Finding 5.1: the emitted realization kept the defect the interpreter fix removed

Review 58797 on gunbc#10069 is correct, and it is a sharper statement than my own
framing of the same row. I had called this "the emitted realization is
independently mutable" -- a testing gap. It is not: the DEFECT ITSELF was still
present. v1_interpreter::write_file_create_new was repaired to stage-then-link
while src/v1/05_emit_rust.dag's file_write_create_new_expr still spelled
open-then-write against the target, so a failed emitted write could still leave a
partial repository while reporting failure. The fabricated-repository failure
survived, in the realization my tests do not execute.

That is one fact with two authorities (DESIGN sections 2 and 3), where repairing
the reachable one leaves the other lying; and it is section 4b rung honesty,
because a class's rung is the MINIMUM across its paths and a fix on the
interpreted path does not raise the emitted one.

The emitted expression now uses the same construction: stage to an O_EXCL
sibling, publish by hard_link (which fails if the target exists), remove the
staging file on every path. Projection regenerated.

EVIDENCE, over the EMITTED BYTES rather than the interpreter. The expression is
decoded out of 05_emit_rust.dag, compiled as a standalone program, and executed:
an absent path is created with the right content; an existing path REFUSES and
its bytes survive; no staging temporary is left behind. Proven discriminating by
the mutation the review itself named -- replacing the body with create/truncate
turns the probe RED at "an existing path must refuse".

WHAT THIS DOES NOT CLOSE, stated rather than left to be rediscovered: these are
still two spellings. An emitted standalone program cannot call the seed's helper,
so nothing but review holds them in step, and the drift this review caught can
recur. The note in 05_emit_rust.dag carries a next-rung trigger naming the
CAPABILITY -- one file-transport realization authority that both the seed and the
emitted program consume -- rather than an artifact that would merely contribute
to one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Derive the valid-repository fixture from the writer, not a hand-authored literal

The floor red on 78a5220 was my own tightened claim: I had pinned the
valid-repository arm of present_bytes_do_not_authorize_a_write_and_are_classified
from `!= "may_create"` to `== "repository_present"`, and the hand-authored
payload `{"format":"gunbc-scm-repository-v2"}` does not decode as a v2
repository, so the classifier answered `foreign_present`.

Reverting the pin would re-weaken the claim to one that cannot tell a
repository from a foreign file, so the fixture is what changes. A literal here
is a second authority for the repository wire format (DESIGN section 3): it
agrees with the codec only while someone keeps retyping it, and the version it
drifts to is silently classified as a foreign file -- the classifier reporting
a repository this program just wrote as not one. The fixture is now
`serialize_json` of `encode_repository_checked(empty_repository())`, the same
two calls repository_save makes, so writer and classifier are held to agreeing
BY EXECUTION.

The encode-refused arm yields "", which decodes as foreign rather than as a
repository, so a refusal fails this claim instead of vacuously satisfying it.

Mutation-proven discriminating: substituting the old literal back into
saved_repository_bytes turns the claim RED; the derived bytes turn it green.
All 20 claims in test.claim.scm.scm_cli_witness pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

* Spell the staging open with `?`, which the emitted realization already did

The composed head's rust-unit-tests job failed on clippy::question_mark (the repo
clippy command runs with -D warnings): write_file_create_new opened its staging file
through `match { Ok(f) => f, Err(e) => return Err(e) }` where `?` is exact.

Clippy is right and this is not a lint to allow. That arm creates nothing, so there is
no staging file to clean up -- which is precisely why it is the one failure path in this
function that does NOT remove_file, and the comment now says so rather than leaving the
`?` looking careless next to three neighbours that do clean up.

It also removes a difference that was never semantic: file_write_create_new_expr already
spelled this `?`, so the two realizations disagreed on the spelling of one line while
agreeing on its meaning. One less thing for the commissioned single-authority cut to
reconcile.

Verified with the exact CI command rather than by inspection:
  cargo clippy --all-targets -- -D warnings   clean
  cargo test --release -p v1-compiler --lib write_file_create_new
    create_new_writes_when_nothing_is_there                       ok
    create_new_refuses_a_path_that_already_exists_and_leaves_its_bytes  ok
    a_write_failure_after_creation_leaves_no_target_behind        ok

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ4iThiZ1B9LPB9ePr8qa9

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 6, 2026
…te wall.

The projector cannot be scraped for markdown; ProcessExit regen is the repair shape, and review 58567 on #9864 is a prior specimen of the same class.

Co-authored-by: Cursor <cursoragent@cursor.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 7, 2026
…te wall.

The projector cannot be scraped for markdown; ProcessExit regen is the repair shape, and review 58567 on #9864 is a prior specimen of the same class.

Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls pushed a commit that referenced this pull request Sep 7, 2026
…ish (#10701)

* Give docs/*.md ledger projections a ProcessExit actuator that can finish.

Required-regen never owned those files, and main_wet dies inside the whole-registry emit graph, so rostered failure-mode rows could sit stale with every completing instrument green. A docs-only gate plus a required docs-projections population refuse that split.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Correct the docs-projection failure-mode row: identity join is membership, not content.

The specimen is three edited rows that stayed named in the projection; a name-set join would have called the file complete. The projector returns String so gunbc run exits 2 without a ProcessExit actuator.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record that NotProcessExit interpolates type_name only, as a deliberate wall.

The projector cannot be scraped for markdown; ProcessExit regen is the repair shape, and review 58567 on #9864 is a prior specimen of the same class.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retract the type-name-only claim: NotProcessExit.type_name carries format_value on the non-variant arm.

Docs-projection content is extractable only by scraping that unversioned diagnostic. The field-name fork is a separate row.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Regenerate docs/design-failure-modes.md so the new docs-projection gate is green on this tree.

The roster rows were unprojected (review 61515); receipts also drop interpolating type_name so the regen entry resolves.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use artifact_path as the docs-ledger location authority and record the executed stderr scrape.

Deletes the duplicated path literals and the join test that policed them. Regenerates the failure-mode projection after the false type-name-only assertion is replaced with the EXIT=2 empty-stdout receipt.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record that the NotProcessExit control never calls classify_exit, then regenerate the projection.

The enrolled test hand-builds type_name Bool, so its RED cannot see the rendered-value arm. docs_projection_gate main exited 0 on that regenerated tree.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Lift exit_ok to std.process so the three gate copies are one constructor.

docs_projection_gate, ci_gates, and generated_artifact_gate now consume it; floor_effect_gate_witness follows.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Give the docs-projection regen recipe one home in the agreement module.

The host no longer reprints a second copy of the same command on the required-ci refusal.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Ground the two-ledger heal population as policy, not a copied count.

Restores the rationale that only the unbounded roster projections heal, without citing the deleted path-join test.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record heron's unparseable-authority incident as the detector-gap specimen.

The quoting injury stays a neighbouring class; this row only claims the missing completing gate. Regen attaches comments to the declarations they describe so the projector parses.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Eliminate std.process Bool exit_ok; join seed-growth trigger to the roster.

Call sites match ProcessExit. Heal registration-without-completion goes in the failure-mode row. The seed-growth trigger names hand_authored_declarations instead of a transcribed 20.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retract the heal-dies mechanism from the docs-projection row.

heal main_wet completed on another branch; the 137 was claim_executor. Comments now mark tools.ci_gates.exit_ok as deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop false main_wet-cannot-complete claims; untranscribe the registry fold.

required-regen ownership motivates the docs actuator. Ordering still defends against an observed whole-registry stall; the fold is not a 35.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore exhaustive std.process exit_ok and drop stale residue citations.

Claim Bool collapse has one home; sequencers still match ProcessExit. Residue rows named the deleted instrument copies.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Point the generated-artifact loudness comment at std.process.exit_ok.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names
Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator
Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value
Ledger-Repair-Judged: docs/design-rung-drops.md

* Cite the existing claim-surface stall as exit_ok's standing disposition.

The Bool collapse is not an untracked residue: sequencers already match ProcessExit, and tools.emit_host_gate emit_host_verdict_narration_dissolution_trigger names when the claim path can carry typed reasons.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Say exit_ok is not rostered; the Bool claim gap is analysed on the narration trigger.

That trigger deletes the scaffold, not this predicate. Both retire when the claim path carries typed reasons.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Admit the seven exit_ok TargetChanged bindings and drop consumed #10676 rows.

The floor_effect_gate_witness _passes functions now bind exit_ok to std.process; the nine scm builder-rehome admissions were consumed on main and came due on this roster touch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names
Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator
Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value
Ledger-Repair-Judged: docs/design-rung-drops.md

* Cite std.process.exit_ok in the scope-placement loudness comment.

The other two sequencers already named the rehomed predicate; this copy still pointed at tools.ci_gates after that symbol was deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
briansrls pushed a commit that referenced this pull request Sep 7, 2026
…ish (#10701)

* Give docs/*.md ledger projections a ProcessExit actuator that can finish.

Required-regen never owned those files, and main_wet dies inside the whole-registry emit graph, so rostered failure-mode rows could sit stale with every completing instrument green. A docs-only gate plus a required docs-projections population refuse that split.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Correct the docs-projection failure-mode row: identity join is membership, not content.

The specimen is three edited rows that stayed named in the projection; a name-set join would have called the file complete. The projector returns String so gunbc run exits 2 without a ProcessExit actuator.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record that NotProcessExit interpolates type_name only, as a deliberate wall.

The projector cannot be scraped for markdown; ProcessExit regen is the repair shape, and review 58567 on #9864 is a prior specimen of the same class.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retract the type-name-only claim: NotProcessExit.type_name carries format_value on the non-variant arm.

Docs-projection content is extractable only by scraping that unversioned diagnostic. The field-name fork is a separate row.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Regenerate docs/design-failure-modes.md so the new docs-projection gate is green on this tree.

The roster rows were unprojected (review 61515); receipts also drop interpolating type_name so the regen entry resolves.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use artifact_path as the docs-ledger location authority and record the executed stderr scrape.

Deletes the duplicated path literals and the join test that policed them. Regenerates the failure-mode projection after the false type-name-only assertion is replaced with the EXIT=2 empty-stdout receipt.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record that the NotProcessExit control never calls classify_exit, then regenerate the projection.

The enrolled test hand-builds type_name Bool, so its RED cannot see the rendered-value arm. docs_projection_gate main exited 0 on that regenerated tree.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Lift exit_ok to std.process so the three gate copies are one constructor.

docs_projection_gate, ci_gates, and generated_artifact_gate now consume it; floor_effect_gate_witness follows.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Give the docs-projection regen recipe one home in the agreement module.

The host no longer reprints a second copy of the same command on the required-ci refusal.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Ground the two-ledger heal population as policy, not a copied count.

Restores the rationale that only the unbounded roster projections heal, without citing the deleted path-join test.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Record heron's unparseable-authority incident as the detector-gap specimen.

The quoting injury stays a neighbouring class; this row only claims the missing completing gate. Regen attaches comments to the declarations they describe so the projector parses.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Eliminate std.process Bool exit_ok; join seed-growth trigger to the roster.

Call sites match ProcessExit. Heal registration-without-completion goes in the failure-mode row. The seed-growth trigger names hand_authored_declarations instead of a transcribed 20.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retract the heal-dies mechanism from the docs-projection row.

heal main_wet completed on another branch; the 137 was claim_executor. Comments now mark tools.ci_gates.exit_ok as deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop false main_wet-cannot-complete claims; untranscribe the registry fold.

required-regen ownership motivates the docs actuator. Ordering still defends against an observed whole-registry stall; the fold is not a 35.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Restore exhaustive std.process exit_ok and drop stale residue citations.

Claim Bool collapse has one home; sequencers still match ProcessExit. Residue rows named the deleted instrument copies.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Point the generated-artifact loudness comment at std.process.exit_ok.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names
Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator
Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value
Ledger-Repair-Judged: docs/design-rung-drops.md

* Cite the existing claim-surface stall as exit_ok's standing disposition.

The Bool collapse is not an untracked residue: sequencers already match ProcessExit, and tools.emit_host_gate emit_host_verdict_narration_dissolution_trigger names when the claim path can carry typed reasons.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Say exit_ok is not rostered; the Bool claim gap is analysed on the narration trigger.

That trigger deletes the scaffold, not this predicate. Both retire when the claim path carries typed reasons.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Admit the seven exit_ok TargetChanged bindings and drop consumed #10676 rows.

The floor_effect_gate_witness _passes functions now bind exit_ok to std.process; the nine scm builder-rehome admissions were consumed on main and came due on this roster touch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: regenerate drifted generated artifacts (ci auto-heal)

Ledger-Repair-Judged: docs/design-failure-modes.md
Ledger-Rows-Repaired: docs/design-failure-modes.md excluded_witness_outlives_the_subject_it_names
Ledger-Rows-Repaired: docs/design-failure-modes.md docs_markdown_projections_have_no_completing_actuator
Ledger-Rows-Repaired: docs/design-failure-modes.md type_name_field_carries_rendered_value
Ledger-Repair-Judged: docs/design-rung-drops.md

* Cite std.process.exit_ok in the scope-placement loudness comment.

The other two sequencers already named the rehomed predicate; this copy still pointed at tools.ci_gates after that symbol was deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant