Repository navigation
Phase A: bind release identity to the serve process, compare it at readiness - #7909
Merged
Merged
Conversation
…a /healthz INCOMPLETE — committed as a checkpoint, not proposed for merge. See the "remaining" list below before continuing. Operator ruling 2026-08-05: #7802 must not merge in a permanently refusing state, and the first receipt must not be hand-seeded or grounded on a slot's disk HEAD. The process is the authority for what is deployed, so this phase gives the running process a launch identity it can report about itself. Landed here: - gunbc.running_release_identity (new): RunningReleaseObservation carries revision and surface_bundle_identity as SEPARATE facts, per the ruling -- revision answers ordering, surface answers what is actually served, and they fail independently. Observation parses the healthz body through json_object_unique_member, so a duplicated key refuses rather than picking one. Causes are type-distinct: ReleaseRevisionAbsent is the migration arm (a process predating the channel) and is a REFUSAL, never a synthetic predecessor. Hex validation reuses std.content_hash's single authority. - Seed seam: `gunbc serve --release-revision <sha>` is REQUIRED and validated before the graph compiles and before the listener binds, so a process that cannot say which release it is never becomes routable. Captured once and injected per request, so no request can observe a different release than another. Not re-read from disk/env/git per request. - Serve dispatch threads release_revision to the healthz handler, so rendering healthz without a launch identity is a type error rather than a convention. - healthz_surface_members factored out: the served document is those members plus the identity members. One member-list authority, two documents differing by exactly the identity -- no second spelling of the surface. - DeploymentServiceConfig carries release_revision; ExecStart emits --release-revision. Required parameter, never defaulted. REMAINING before this is a candidate PR: - Rust build not yet confirmed green (was still compiling at handoff). - Callers of deployment_spec_srv1()/roadmap_serve_respond*() not yet updated (apply.dag + roadmap_serve/sandbox/instrument witness tests). - The apply entry does not yet SUPPLY the revision (observe the deploying checkout's commit -- that is the candidate being installed, distinct from and not to be confused with reading a slot's disk HEAD to infer a predecessor). - Emit goldens for the ExecStart line not regenerated. - None of the discriminating witnesses the ruling lists are written yet (launched-as-A/disk-becomes-B, wrong-surface, wrong-revision, no-identity). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… execution Repairs the previous checkpoint and adds the executed receipt. Two defects fixed, both found by RUNNING the binary rather than by compiling it: 1. Two note strings were unterminated -- the authoring heredoc's closing delimiter swallowed the .dag string's closing quote. The Rust build was green throughout; only executing the binary surfaced the parse error. Same class this branch exists to close: a compile is not an executed consumer. 2. Five call sites did not supply release_revision. The typechecker refused each one, which is the construction property working: a handler that would render /healthz without knowing which release it is now fails to compile rather than answering plausibly. EXECUTED RECEIPT (this head, port 18099): launched: gunbc serve ... --release-revision e1149ba... GET /healthz -> {"status":"ok","service":"gunbc-roadmap", "artifacts":{...five served digests...}, "revision":"e1149ba21f946822750819a2852784d61df57732", "surface_bundle_identity":"e183d530c5c1bbc7"} revision == the launch argument, and the surface identity is a SEPARATE member -- the two facts the ruling requires stay unfused on the wire. Refusal controls, executed: missing --release-revision -> exit 2 (required arg) "main" (branch name) -> exit 1, typed message 40 UPPERCASE hex -> exit 1 <- discriminating: a naive length check accepts this 39 lowercase hex -> exit 1 (boundary) valid 40 lowercase hex -> passes, proceeds to load and serve (positive control: the validator discriminates, not always-refuses) All four refusals fire BEFORE the graph compiles and before the listener binds, so a process that cannot say which release it is never becomes routable. STILL REMAINING (unchanged from the checkpoint): - apply.dag does not yet SUPPLY the revision (observe the deploying checkout's commit -- the candidate being installed, NOT a slot's disk HEAD). - deployment_spec_srv1()/roadmap_serve_respond*() callers in apply.dag and the witness tests still need updating; emit goldens not regenerated. - The discriminating .dag witnesses from the ruling are not written yet (launched-as-A/disk-becomes-B, wrong-surface, wrong-revision, no-identity). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Continues the process-bound identity work. The previous commit proved a
launched process publishes its revision through /healthz; this one supplies
that revision from the deploying checkout and consumes it as the readiness
verdict, so the deploy path stops being able to green against a process it
did not install.
WHAT LANDED
- gunbc.live_deploy.candidate: CandidateRelease {revision,
expected_surface_identity} plus observe_candidate_release(), which reads
git.Inspect.HeadCommit ONCE at the public apply/retract launcher, before
any host mutation, and refuses with a typed cause (refused / empty /
malformed) rather than proceeding with a placeholder. It is a function,
not a top-level data, so importing the module performs no Git I/O -- the
class gunbc.stage0_rust_host_observation already paid for.
- apply.dag threads the observed candidate into the blue/green slot
selection, so both slot specs carry the revision this invocation admitted.
- readiness.dag gains the revision half of the verdict: it observes the
running process's identity from the healthz body and compares the reported
revision against spec.service.release_revision -- the SAME single value the
launcher observed and the emitter wrote into ExecStart, never re-observed.
Order is probe, parse, identity, revision, surface, so the first refusal is
the most locating one. ReleaseIdentityUnreadable and ReleaseRevisionMismatch
are distinct causes; the pre-identity process lands in the first.
A DEFECT IN THE PREVIOUS COMMIT, FOUND BY READING THE CONSUMER
roadmap_site_healthz_surface_is_current compared the WHOLE healthz body
against bundle.healthz_body. Adding the identity members to the served
document would therefore have made every deploy refuse forever -- a check
that can never pass, which is the permanently-refusing shape this lane
exists to remove, reintroduced one layer down. The surface comparison now
projects the surface keys out of both documents and normalizes both through
the same parser and emitter, so the surface verdict is a statement about the
surface alone and the revision verdict is left to the comparison that owns
it. That is the same not-fused discipline the wire format already follows.
DECLARED SCAFFOLD
release_revision rides on DeploymentServiceConfig, which puts a
per-invocation fact inside the otherwise-constant slot topology; the tell is
that a retract must name a revision it has no use for. The operator ruling
names the end shape (the candidate travels beside the spec), and
release_revision_on_service_config_scaffold_note carries the dissolution
trigger rather than leaving the fusion unmarked.
NOT YET EXECUTED
The twelve discriminating witnesses in
dag/test/claim/live_deploy/running_release_identity_witness_test.dag are
written and enrolled but have NOT been run green. Three consecutive
claim_batch runs were SIGKILLed during resolve with cgroup memory.events
showing max 0 / oom_kill incrementing and this container's peak flat at
4.9 GiB against a 33.5 GiB cap -- host-level pressure from siblings (host
load average 24-29), not this workload. No defect was found; the evidence is
simply not in hand, and this commit does not claim it.
- roadmap_site_surface_expect healthz_surface_projection returned a bare String in its then-branch while declaring String?, so the two branches resolved to incompatible types. Wrapped in Present. - readiness reconcile_service_ready_read_for_effect had a third call site of ground_service_ready_from_healthz_with_bundle that the earlier threading pass missed; it now forwards expected_revision. Both were reported by the resolver, which is the construction property working: a readiness comparison cannot be built without naming the revision it compares against.
Both were caught by review of #7909, not by me, and both are the class this lane exists to close: a description stronger than the executed evidence. 1. candidate_observed_once_note asserted that carrying expected_surface_identity on the candidate is what lets readiness detect a tree change between launch and verification. It does not. apply.dag forwards only candidate.revision, readiness re-materializes the bundle at comparison time, and the process-reported surface_bundle_identity is never compared against the captured value -- the wire member is published and inert. The note now states that as a defect with a closing condition instead of asserting the closure. 2. witness_process_launched_as_a_refuses_deploy_of_b was named for a behavior it does not execute. It starts no process and mutates no tree; it compares an A-shaped body against an admitted B. Renamed to witness_readiness_refuses_when_reported_revision_is_not_the_admitted_one, and the note now says it is the DECISION control while the immutability premise is assumed and owed a live process test. No mechanism changed. This commit only stops the corpus from carrying claims that outrun what runs.
DeploymentServiceConfig no longer carries release_revision. The tell that the
fusion was wrong was always on the retract path: a retract installs no release,
yet it had to construct a spec naming a revision it never read, and it called
observe_candidate_release first — so a teardown could refuse because git HEAD
was unreadable.
LiveDeployApply now carries { spec, candidate }; LiveDeployRetract stays
{ spec } and observes nothing.
Where the launch revision comes from is itself modeled, because two honest
answers exist and a third is a refusal. ReleaseRevisionBinding:
RevisionBoundAtEmission production apply, the observed candidate
RevisionBoundAtInvocation the committed script, deferred to a shell parameter
RevisionNotInstalled retract; the ExecStart arm under it refuses loudly
That resolves the committed-artifact contradiction without deleting the byte
oracle over the systemd-unit heredoc. A file frozen in Git cannot name a future
deploy's revision: a constant is a fabricated identity, the generating tree's
HEAD redrafts the artifact on every commit, and a fixture SHA puts synthetic
data in a production artifact. All three were rejected. The frozen script binds
at invocation instead, so it contains no SHA and stays deterministic.
expected_live_deploy_apply_script / _retract_script are nullary again, which is
what fixes heal_generated_artifacts — the two generated_artifact_emit call sites
needed no edit at all. That they were already correct is the evidence that the
split was the real fix and threading a fixture through them was the workaround.
Closes the SSH defect: the candidate is observed in the local deploying
checkout, so realize_live_deploy_apply_on_host refuses SshShell and
EmitArtifactThenThinRun rather than labelling a remote tree with a local
revision. Retract is not restricted; it has no locus to agree about.
Closes the captured-then-dropped defect: readiness now compares the process's
reported surface_bundle_identity against candidate.expected_surface_identity
instead of re-materializing the bundle at comparison time, which would have
reopened the time-of-check/time-of-use hole the candidate exists to close.
Readiness outcomes are flat, because a mismatch is not a kind of unidentified:
ProcessIdentityAbsent | ProcessIdentityMalformed | ProcessRevisionMismatch |
ProcessSurfaceMismatch | ServedSurfaceStale. The observation carrier stays
grouped, because this fold reads the grouping without enumerating causes.
surface_bundle_identity is a validated Fnv1a64Structural (16 lowercase hex via
the same authority the 40-digit revision uses), not a raw String.
Not yet done: the two discriminating REDs the flat arms now make expressible,
ProcessReady still being body-shaped, and the live launch-A/mutate-to-B control.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d not live_deploy_poll_until_service_ready has no remaining call sites — its only references are name-level DeclarationRef mentions in a dissolution trigger and its witness. So a grep for callers reports zero while the arity is wrong, and only the typecheck sees it. Compile not yet confirmed green at this commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pe anyway healthz_surface_projection's if-branches resolved to incompatible types. Lifting the branch into healthz_projection_of_doc fixes it and drops a double evaluation of healthz_projected_members, which the accepting path computed twice. healthz_release_identity_refusal referenced 'cause' inside its own match arms, where the scrutinee is not in scope. Hoisting the detail out before the match collapses nine near-identical arms into one wildcard — the shape it should have had from the start, since only ReleaseRevisionAbsent is genuinely distinct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit restructured the wrong code. Diagnostic spans like
foo.dag:4097-4099 are BYTE offsets, not line numbers, and I read them as lines.
Converted, they name two different causes:
Present/Absent were never imported in roadmap_site_surface_expect. An unbound
Optional constructor surfaces as "if branches resolve to incompatible types:
Primitive(String) vs Coproduct(Optional)" rather than as an unbound name, which
is why restructuring the branches did not move it.
The undefined variable 'cause' was inside a note string added this session:
RunningReleaseUnidentified{cause} in prose, where { starts interpolation. The
compiler was correct and there was no defect in the function at all.
The previous commit's two changes are kept on their own merits — one dropped a
double evaluation, the other collapsed nine identical match arms — but neither
was the fix, and the commit message claiming otherwise was wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Absent is fine on the left of a match arm but does not construct an Optional on the right; the constructor is none. The diagnostic blames the wrong branch — it reported the Present side as Primitive(String) when that side was correct — so adding the v2.std.collection import changed nothing and restructuring the function twice did not move it either. Working idiom copied from gunbc.cli_invoke claim_executor_notice_title_normalized. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ld not work
RevisionBoundAtInvocation was inert. The unit is written through a heredoc whose
delimiter is quoted, so bash performs no expansion inside it and the literal
${GUNBC_RELEASE_REVISION:?...} landed in the unit file; systemd does not run
ExecStart through bash, implements no :? operator, and the unit sets no such
Environment. No link in the chain existed. Proven by executing the heredoc, not
by reading it — which is the lesson: it typechecked, it read plausibly, and it
did nothing.
It also would have mutated the host — sudoers, rsync, binary install, unit
write — before the serve binary ever rejected a malformed value, contradicting
this lane's own refuse-before-mutation boundary.
I kept it last pass to preserve a byte oracle over the ExecStart line, against
my own census showing no consumer. That trade was imaginary: a fixture SHA is
forbidden in a committed production artifact, not in a witness. So the oracle
moves to the test corpus and the artifacts go.
Deleted: both script artifacts, their paths, their registry rows, their false
HostReconciler consumer claims (nothing reads them; the deploy runs
live_deploy_apply_srv1_wet), both .github shell files, the gitattributes rows,
RevisionBoundAtInvocation, and the two nullary script producers.
The coverage is now stronger than what it replaced. The golden compared whole
files and located nothing; the surviving unit witness asserted only a command
prefix and stopped before the flag, so a unit emitting no --release-revision at
all passed everything. Three new witnesses pin the entire ExecStart line and
break it one way each: flag absent, wrong candidate's value, flag duplicated.
Also corrected two notes that described an earlier head: candidate.dag no longer
says the surface identity is dropped, and readiness.dag no longer names
spec.service.release_revision.
Typecheck of the registry closure was still running at commit time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…7-phase-a # Conflicts: # dag/gunbc/generated_artifact.dag # dag/test/claim/generated_artifact_drift_test.dag
…ace control The witness file could not compile against the new readiness API: it called ground_service_ready_from_healthz_with_bundle without expected_surface_identity and expected the old verdict names ReleaseRevisionMismatch and ReleaseIdentityUnreadable. The fused control is the more important half. One fixture varied the artifact map AND the bundle identity together, so it refused no matter which check did the work — it would have passed unchanged while surface_bundle_identity went entirely unread, which is precisely the state this branch was in when the member was captured and dropped. A control that cannot fail when the mechanism it names is deleted is not a control. Split into two that each vary exactly one member: A: artifacts byte-identical, bundle identity wrong => ProcessSurfaceMismatch B: identity correct, one artifact digest wrong => ServedSurfaceStale Neither check subsumes the other, and each control fails if its own check goes. Not yet executed. The seed binary is mid-rebuild after the main merge changed 36 files under src/v1, so nothing here has run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…into session/still-hawk-637-phase-a
The heal_generated_artifacts check failed at "Commit + push auto-push-eligible regenerated artifacts (workflow paths are author-commit-required)". CI was behaving correctly: removing the two LiveDeploy script artifacts from the registry changes what ci.yml generates, and CI is not permitted to push a workflow path, so it regenerated, found drift it could not commit, and stopped. Regenerated through generated_artifact_gate main_wet. The diff is exactly the three dead references — the AUTHORED_CONFLICTS exclude entries and the two chore-heal git add lines — and nothing else. Regenerated with a verified-fresh binary. Both earlier builds exited 0 while running remotely and left target/release/gunbc untouched; ctrl-build --local is what actually rebuilt it, confirmed by mtime against the clock before this ran. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
marked this pull request as ready for review
August 7, 2026 05:05
The compile-clean gate was red at every call site of a readiness or serve function that gained release_revision / expected_revision / candidate, across five witness files. Each now supplies the value, and the shared fixture revision stays the single declared sha: deploy_release_fixture is imported where a sha is needed rather than respelled per file. Two of the repairs are more than an argument. gunbc.roadmap_site_surface_witness takes the revision as a PARAMETER instead of naming one. The one fixture revision lives under dag/test/ so no production module can name it, and threading it through the group function keeps that containment intact — the two test modules that call the group supply it. Its stale-surface control had to be rebuilt, not just re-typed. The former fixture was a hand-spelled three-member document with no release identity; once readiness checks identity BEFORE surface, that body refuses as ProcessIdentityAbsent, so the assertion it carries (ServedSurfaceStale, naming the stale digest) would have been asserting a refusal the fixture can no longer produce. It was also a second spelling of a document healthz_body_with_release_identity is the sole producer of. The fixture is now that producer's real output with ONE artifact digest replaced, so probe, parse, identity, revision and reported-surface-identity all pass and the projected artifact map is the only thing that differs — which makes it the discriminating control for the last arm specifically. The witness now also asserts the reason names NONE of the earlier arms, so a fixture that regressed to refusing earlier reds instead of passing. New prose lands as // annotations rather than data _note rows (DESIGN 4c); existing rows in these files are untouched. Green by execution, not by typecheck: whole-tree `--target dag` compile clean, and the affected witnesses run — 18/18 readiness, 15/15 roadmap_serve, 12/12 roadmap_sandbox, the surface-readiness group, healthz_route_json_ok, and the long instrument-sandbox keystone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 7, 2026
observe_candidate_release read Git HEAD and separately rendered the current
working-tree surface, establishing nothing about whether those two describe the
same bytes. This state was representable:
HEAD revision = A
working-tree served = B
candidate = { revision: A, surface: B }
and it is self-consistent all the way down — the emitter writes A into
ExecStart, the process reports A, readiness compares A against A and greens,
and the ancestry decision orders the deployment as A. No party in the chain can
see the bytes were never A's, because every one of them is reading the same
wrong answer. The identity channel cannot catch this class by construction,
which is why it is refused at admission.
It is not hypothetical here: the tree-sync unit rsyncs the runner checkout
WHOLESALE except two prefixes, so the deployed source is the working tree, not
the commit. An uncommitted edit or an untracked file ships.
CandidateCheckoutClean
CandidateCheckoutDirty { tracked_changes, staged_changes,
untracked_deployed_paths }
CandidateCheckoutUnobservable { detail }
Candidate construction proceeds only from Clean; the other two become
CandidateCheckoutNotClean / CandidateCheckoutStatusRefused on the existing
cause coproduct, so the launcher refuses before any host mutation. The read is
ordered AFTER the revision and BEFORE the surface is rendered, so a dirty tree
never reaches the renderer and nothing is derived from bytes already judged
untrustworthy.
Three path lists, not a Bool: staged, unstaged-tracked and untracked-deployed
have three different remedies, and a `dirty: Bool` is the same refusal with the
locating information deleted.
WHAT THE DEPLOY INSTALLS NOW HAS ONE HOME. The rsync exclusions were literals
inside the emitted ExecStart; the admission needs the same set to decide whether
an untracked path would actually ship. Spelled twice they could drift, and the
dangerous direction is silent: admission passes on a path the sync installs.
gunbc.live_deploy.deployed_tree_scope declares it once and both read it. It is
its own leaf module because importing the emitter from candidate closes a real
cycle (candidate is reached from hostname_read, which host_effect reaches,
which the emitter's closure reaches) — caught by the compiler, and acyclicity is
the import graph's one structural law.
The porcelain parse is stateful by necessity, not preference. `-z` emits a
rename as two fields and the second is a bare path; there is no shape test that
separates it from a record, because "ab cdef" is a legal filename that passes
any third-character-is-a-space check. Only the previous record's status letter
says a continuation follows. A field that is neither refuses rather than being
skipped — dropping it would turn an unreadable status into a clean one, the
empty-observation narrow.
16 discriminating witnesses, green by execution: each dirty axis in isolation
and the both-columns case; the scope filter in BOTH directions (target/ and
.git/ excluded, targets_of_opportunity.dag not) plus segment-vs-character-run;
the rename source that would pass a shape-first parser; the truncated rename;
the malformed field; the refusal naming every axis and its paths; and the
emitted flags asserted equal to `--exclude /target --exclude /.git`, which is
what reds if either side is respelled. emit_test 45/45 still green, so the
ExecStart refactor is byte-preserving.
Owed and named: the wet control that commits A, edits a served source without
committing, and shows zero host mutation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 50078: the surface lookup's JsonMemberNotAnObject arm answered SurfaceBundleIdentityAbsent, which is a different state — 'the body is not an object' versus 'the body is an object that omits the surface identity' — and would send a reader looking for a missing member in a document with no members. The arm is unreachable today (reaching it means the revision lookup already proved doc is an object), but a mislabel that is currently unreachable is the one a later refactor inherits. Fixed at the name rather than at the site: ReleaseRevisionNotAnObject already rendered as 'the healthz body is not a JSON object', so it was a body-level fact wearing one member's name. It is now HealthzBodyNotAnObject and both lookups answer it. One fact, one name (DESIGN 3), and the arm stays total without a refuse_unreachable marker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ci.yml is generated and conflicted for a mechanical reason: main added heal machinery (the still-unmerged index guard and the admit_commit_writer call) while this branch removed the two .github/live-deploy-srv1-*.sh references, because it deletes those artifacts. Resolved by taking main's generated file and applying exactly this branch's deletion — the three references to the two scripts — which is the delta this branch's generator change produces. The regen and drift gates verify that against `gunbc ci` output, so a wrong resolution reds rather than riding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The flag rationale sat INSIDE the service body, which the annotation channel refuses: only standalone leading // blocks on module-scope declarations are modeled (DESIGN 4c). Six located errors, and they were right — the block is now above `service git.Inspect` and names the operation it describes. Worth recording, because it is a hole in how this branch was being verified: the witness runs did NOT catch it. claim_batch resolved and executed the module green while `gunbc run` refused it, so annotation placement is decided on a pass the witness path does not exercise. "The witnesses are green" is therefore not evidence that an annotation is admissible, and the CI heal job's converge actuator is what surfaced it. Green by execution: the repo_local_git_config converge actuator that reds in CI now exits ExitSuccess, and the candidate-checkout witnesses stay 16/16. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The same defect I fixed one commit ago, in a second file: the HealthzBodyNotAnObject rationale sat between the arms of the RunningReleaseUnidentifiedCause coproduct rather than above it. Eight more located errors from the same channel, and I earned them by fixing the instance in extdeps/git/inspect.dag and not asking where else the class lived. So this commit is the sweep, not a second patch: every .dag file this branch touches was scanned for an indented `//`, since a module-scope annotation starts at column 0 and anything indented is inside a declaration. Two sites existed, both mine, both now above the declaration they describe. The scan is clean. Verification is on the pass that actually decides this. claim_batch resolves and executes these modules green either way — annotation binding is not on the witness path — so the check that matters is `gunbc run`, and the repo_local_git_config converge actuator that reds in CI now exits ExitSuccess. The deploy closure carries no annotation error. Witnesses stay green: running_release_identity 13/13, candidate_checkout 16/16. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t took The premise half of the identity property — that a process keeps answering what it was launched with after the tree beneath it changes — was assumed from "serve binds once at startup" and marked owed. It is now executed, against head c4fe948, with three real processes. Process 1, launched at 0123...4567, answered service=gunbc-roadmap. With it still running, roadmap_site_service_name was edited on disk to gunbc-roadmap-PROBE. Process 1 kept answering gunbc-roadmap and 0123...4567. Process 3, launched AFTER the edit at fedc...ba98, answered gunbc-roadmap-PROBE and fedc...ba98. Same bytes, same instant, two answers, distinguished only by launch time. THE FIRST ATTEMPT WAS VACUOUS AND THE RECEIPT SAYS SO. It mutated ROADMAP.md and observed no change in the running process, which looked like a pass and proved nothing: a fresh process on the mutated tree reported the same digest too, because /ROADMAP.md is a generated projection of the .dag authority and editing the file changes nothing anyone serves. A negative control with no positive control beside it cannot distinguish immutability from an input that was never read. Running the fresh process is what turned a vacuous green into a discriminating one, and it is why the mutation subject moved from a generated artifact to a .dag authority. A third fact fell out unlooked-for: three processes ran concurrently against ONE tree at three declared revisions and each reported its own, so whatever answers /healthz cannot be reading the revision from any shared ambient source — all three share disk, env and git HEAD, and disagree anyway. Still owed, and named rather than implied: the run was by hand. Each process costs ~100 s of graph compile, so three are far outside the per-PR fast lane and enrolling them there would be the cost-shape defect the 2026-08-04 witness-cost ruling names. The trigger is the wet cadence lane. Until enrolled, the property is established at one head rather than defended against regression. readiness_revision_refusal_note no longer says the premise is unexecuted; it points at the receipt and keeps claiming only the decision half for itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Batch 3 had never executed on this branch — the inherited ensure_is_converged break stopped the run at batch 2 every time — so these eleven witness failures are the first output of a gate that was previously unreachable, not a new regression. Four independent causes, each fixed at its own root. Three unused imports (deploy_mutation_gate, apply_witness, intent_witness). Each named deploy_witness_release_revision without calling it. On the first of the three that was not merely dead: the file previously declared NO imports and resolved every cross-module reference bare, so adding one import narrowed the pool it resolves against and eight witnesses lost deployment_spec_srv1 and ci_deploy_srv1_access. Removed all three rather than the one that reddened — the other two are the same defect not yet surfaced. label_list_contains was a homonym, and a local declaration lost to a pool member. ci_deploy_target_host_witness declares it locally as (labels, needle); gunbc.design.component declares a byte-identical one as (xs, x); the run bound this module's own call sites to the OTHER module's declaration and failed with "no parameter named 'labels' (declared: [xs, x])". Renamed to deploy_runner_label_list_contains, which removes the collision at this site and nothing more: one concept still has three declarations in the corpus (the third is std.materialization_ladder string_list_contains), and reference binding still varies with which unrelated files a run loads. Both stay on the namespace lane; the executed evidence is recorded on the declaration. The registry count literal still read 17 after this branch removed two artifacts. It is the count of the enumerated fixed rows immediately below it, so 15 is what the enumeration says — a controlled fixture, not a tree-copied census. healthz_release_identity_refusal landed unrostered on the non-fold-residue frontier. It is a one-special-variant split: ReleaseRevisionAbsent is a MIGRATION state (a process predating the identity channel), the other nine causes are a DEFECT (a live channel carrying something that is not an identity), and the nine share an arm because running_release_unidentified_detail already names each one before the match runs. Row carries that reason and dissolves when the migration arm becomes unreachable and the partition derives from inhabitance. Verified: all 11 previously-failing witnesses green, both non-fold-residue RED controls still red-when-wrong, whole-tree --target dag compile-clean at 0 blocking errors.
…eported The checkout admission had a hole exactly the size of .gitignore. `git status --porcelain=v1 -z --untracked-files=all` does not report ignored files, and the source sync excludes only the two prefixes deployed_tree_scope names, so any ignored path outside target/ and .git/ was copied by rsync while the admission reported the tree clean. The resulting state is self-consistent, which is why nothing downstream could catch it: HEAD names revision A, rsync installs bytes B, the candidate's expected surface is rendered from B, the process reports A and B, and readiness compares A against A and B against B and greens. That is the false identity this admission exists to make unrepresentable, surviving inside it. This repository ignores .env, /.gunbc/, .claude/, /target-codex/, /.cargo-home/, and — inside a source root the compiled graph actually reads — src/v2/test/claim/manual/sg2_scratch_probe.dag. Observed through a SECOND operation rather than an --ignored flag on the first, and the reason is measured rather than stylistic. `git status ... --ignored= matching` reports a directory that itself matches an ignore pattern as one folded entry (`!! ign/`) and never names the file inside it — the same folded-ancestor loss --untracked-files=all exists to avoid, and fatal here because the consumer must filter each path against the deployed-scope authority and must name the offending path in its refusal. `git ls-files --others --ignored --exclude-standard -z` answers with real file paths. Both spellings were run side by side against git 2.43.0 on a constructed repository. ignored_deployed_paths is its own axis, not merged into untracked_deployed_paths, because the remedies differ: folding them would tell an operator to `git add` a path .gitignore exists to keep out of the index. Controls, both directions: an ignored .dag inside a source root is a deployed path and refuses; an ignored artifact under target/ is not deployed and does not by itself refuse — the negative half matters because every developer checkout has thousands of those and a blanket refusal would teach an operator to route around the admission. 21/21 in the checkout witness.
The witnesses above the receipt feed synthetic fields to the scan; they prove the parser and the scope filter and would have passed just as green while the operation asked Git the wrong question — which is the defect this axis exists to repair, one level up. The receipt records the boundary run: an ignored .dag under a source root refuses with ignored-deployed naming it, while the same file is absent from git status --untracked-files=all in the same tree, and 1367 ignored target/ paths in that checkout correctly did not reach the refusal. What is still owed is named rather than implied: an enrolled control needs the Git inspection operations to accept a working directory so a test can point them at a constructed temporary repository. They run in the process cwd today, which is the deploying checkout itself, so a test cannot author its own dirty state without dirtying the tree it runs in. That is a modeling change to extdeps.git.inspect, not a harness trick, and it is this receipt's dissolution trigger.
# Conflicts: # .github/workflows/ci.yml
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 8, 2026
Both failing witnesses on this PR are MAIN-SIDE DEBT landed today, not this branch's. Neither constituent introduced them and neither of main's own runs executed the witnesses that catch them. inert_carrier_no_unrostered_or_stale. TerminatingOneShotFrontier and WitnessPurpose (dag/std/witness_purpose.dag, #7965) are a taxonomy landed ahead of its consumer: nothing outside their own module and their own test file references them, which is precisely the seed's inert predicate (declared once, referenced by a test, zero non-test references outside the declaration block). They get roster rows in the established shape, carrying the shared dissolve trigger -- a live consumer reads the carrier and the stale-roster check then forces the row's deletion. git_mock_consumer_is_total_holds. #7909 added git.Inspect.WorkingTreeStatus and git.Inspect.IgnoredFiles to git_published_mock_corpus without arms in materialize_git_mock, so both fell to mock_unhandled_rejection and totality failed. main publishes 20 cases against 18 arms; this adds the two arms. HOW THE OWNER WAS ESTABLISHED, because my first attribution was wrong. I found seven type carriers new versus main and inferred they were the inert ones -- true premise, unsupported conclusion, since none of the seven can satisfy the predicate (five are referenced by no test file at all; the other two are consumed in non-test fn signatures). The primitive is not directly invocable (no unique runtime declaration identity) and a second-worktree run is invalid because the corpus scan anchors to the compile-time-baked workspace root. So the rule was read out of the seed and reimplemented, and THE REPLICA IS VALIDATED AGAINST THE EXECUTED ORACLE: on this head it reproduces 13 inert against 11 rostered, exactly the unrostered=2 stale=0 the real run measured. Run over three trees it gives main 2 unrostered + 1 stale, pr-7932 clean, this branch main's 2 with main's stale already fixed -- so this branch was strictly better than main before this commit. GREEN BY EXECUTION, with the discriminating red on the real consumer: inert_carrier_no_unrostered_or_stale FAILED on this head before this commit and PASSES after; git_mock_consumer_is_total_holds likewise, alongside both omission RED controls (git_mock_omitted_member_is_red_holds, git_mock_omitted_show_tree_is_red_holds) still passing, so the added arms did not disarm the controls. No regen: neither module projects to a stage0 seed file and no seed .rs references the roster units, verified against a positive control (dag/std/algebra.dag does project, to std_algebra.rs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 8, 2026
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 8, 2026
…y witness hermetic resolve_claude_offer matched ProviderSelectionRequest with a top-level wildcard, which non_fold_residue counted as an unrostered residue site (nfr_roster_receipt: unrostered=1). The coproduct has four variants and two were already handled, so the wildcard covered exactly SelectCodex and SelectCursor; both are now explicit arms carrying their own refusal reason. Live residue sites 159 -> 158, unrostered 0, stale 0 - the class is eliminated by construction rather than enrolled on the roster (DESIGN 4/5). witness_claude_request_refuses_on_codex_only_inventory called dispatch_selection_for_instance, which builds its inventory through provider_standing_from_live_codex_probes and so reaches a shell Run; under hermetic mode that refuses with no mock_response. It now builds the inventory with fixture_inventory_codex_only_available and calls resolve_dispatch_selection directly, the convention dispatch_production_standing_unobserved_refusal_note already states. dispatch_selection_keystone_holds failed only as its aggregator. Verified by execution: claim_batch (hermetic, mock corpus published) PASSes both plus witness_wrong_selection_shape_refuses; the nfr enumerating test reports unrostered=0 stale=0 having reported unrostered=1 on the same binary before the fix. The two remaining witness reds are main-side and carried unchanged: inert_carrier_no_unrostered_or_stale (TerminatingOneShotFrontier and WitnessPurpose, both from #7965 in a byte-identical dag/std/witness_purpose.dag) and git_mock_consumer_is_total_holds (#7909, byte-identical carrier); the fix for both lands via #8027. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 8, 2026
…fect open (#8040) * srv1 deploy plan: record what Phase A landed, what it did not, and the resume trigger The header said "DIAGNOSIS, no fix landed". Two PRs have since merged out of this diagnosis, so the header was actively misleading: a reader would conclude none of it was addressed, and a future agent would re-derive work that exists. Delivered, with merge SHAs verified as ancestors of main: Phase A (#7909, d409b75) binds one release revision at process launch, publishes revision and surface identity separately on /healthz, admits the candidate checkout before any host mutation, and compares readiness against the admitted candidate rather than a re-rendered expectation. #7990 (36dbd71) removed the ensure_is_converged declaration homonym and is namespace hygiene, not deploy correctness — recorded as such so it is not later miscounted as progress on this defect. What is NOT delivered is stated at the same volume, because the gap between "merged" and "live" is exactly where this lane's false greens came from. Phase A is in main but not established as live on srv1: deploy_dashboard_srv1 declares needs:[ci] and fires only on a main push, so a failing main ci skips it and a workflow_dispatch cannot activate it at all. The activation receipt is enumerated field by field, including the count of older queued deployments still eligible to run — because one successful identity-capable deploy is not durable activation while a pre-Phase-A job can still reinstall the old unit. Until that receipt exists, ReleaseRevisionAbsent is the honest reading of the live process. The original defect is recorded as still open. Phase A makes deployed identity observable; it does not serialize deployment, retain candidates, prevent rollback, or converge on the tip. Phase B is written as a quarry map rather than a rebase plan, naming #7802's exact head, the modules that survive (flock, lease, coordination, the witness matrix), the pieces that must dissolve rather than transplant (the receipt-authoritative revision model), the required production ordering, the wet acceptance gates, and the resume trigger. That is what a future agent needs to restart without replaying a transcript. Nothing here regenerates ROADMAP.md or DESIGN.md: both are generated artifacts and neither carries this lane. This doc is its registered home, bound through doc_graph_roots HandAuthoredDocBind to gunbc.ci_spec gunbc_ci_deploy_srv1_stage, and that bind's dissolution trigger correctly stays unmet — two of its four findings (the host-side lease, the diagnostic substitution) are still open. * srv1 deploy plan: the deploy job is inert, not queued — 38/40 main pushes skipped, 0 ran The activation section assumed Phase A was waiting on a pre-Phase-A deploy queue to drain. Measurement says otherwise: across the last 40 main-push runs deploy_dashboard_srv1 was skipped 38 times and ran zero times, with no successes and no failures in the sample. The skipped records carry 0 steps and started_at == completed_at, which is a job-level if: evaluating false rather than a queued or cancelled job. Two candidate causes are eliminated by execution rather than by reading. A failing main ci is not it — two push runs on main with ci: success still skipped the deploy. The event and ref are not it either — the API reports event=push and head_branch=main for those runs, and the job condition is byte-identical at three separate commits and true on its face. The cause is deliberately NOT named. Guessing a root cause is the failure this document exists to prevent, and an eliminated candidate is worth more than a plausible story. What is recorded is the observation, the two eliminations, and the consequence: Phase A cannot activate by waiting, so diagnosing this skip is the first Phase B prerequisite, ahead of any code work. --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 8, 2026
…-name helper Two of these are main-side debt (#7965, #7909) that main's own narrower affected set never scheduled, so they red on any branch broad enough to select them. #8027 carried the fix but is blocked indefinitely - its floor step costs ~75.2 minutes against a 75-minute cap, reproduced four times across three server families - so waiting is not a strategy (proud-bear-834 diagnosis). inert_carrier: roster TerminatingOneShotFrontier and WitnessPurpose, the std.witness_purpose carriers #7965 landed ahead of their consumer. Verified: unrostered=0 stale=0. git_mock_totality: add the git.Inspect.WorkingTreeStatus and git.Inspect.IgnoredFiles arms - mock_corpus publishes 20 cases against 18 consumer arms. Keys read as literals from the corpus, not inferred. Verified: git_mock_consumer_is_total_holds PASS. package_delivery: delete archive_basename_from_locator (review 50494). It fabricated a plausible "archive.tgz" on both the empty-name and Absent arms instead of refusing - the DESIGN section 5 fabricated-output shape - and had zero callers corpus-wide, so it was also dead vocabulary. Deleted rather than converted to a typed refusal, since an unused refusal is still unused vocabulary; the live staging name is derived by staging_archive_name_from_lock_key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 8, 2026
…ries #8010's witness retirement) (#8027) * WIP: cost analysis * Execute the name-key precondition C0(b2)'s join was assuming classification_matches_fact joins a roster row to a DeclFact on BARE NAME, while DeclFact carries qualified_name and kind and could key exactly. That is sound only while every top-level declaration of v1.compiler.complexity has a distinct name, and nothing in the carrier makes that true -- it is a property of the subject module, not a wall this carrier erects. Measured over src/v1/complexity.dag: 201 top-level declarations (171 fn, 26 type, 4 data, 0 let), zero duplicate names. So the join is correct today. But a measurement of the current tree is not an oracle (DESIGN section 5), so the precondition is now EXECUTED rather than assumed. The gap this closes is real and none of the four existing totality witnesses covers it: a duplicate name in the DERIVED population still classifies, still matches a roster row, and leaves no stale row, so one roster row can silently answer for two declarations while every count agrees. roster_names_distinct does not cover it either -- it constrains the AUTHORED side only, and the duplicate here would be on the derived side. Added population_names_distinct beside the existing roster_names_distinct, reusing name_already_seen rather than minting a second distinctness fold. Green by execution, all three run through claim_batch against the real tree: PASS w_population_decl_names_are_distinct (live population) PASS w_distinctness_fold_reds_on_a_planted_duplicate (discriminating RED) PASS w_distinctness_fold_accepts_a_distinct_control (positive control) The RED matters: a distinctness fold that cannot fail proves nothing, so a planted same-name pair must come back false or the live green is empty. Dissolve-on is named on the carrier: the roster carrying exact DeclFact identity (qualified_name, or name plus kind) makes the join exact by construction and retires the precondition. That is a 201-row re-authoring and is deliberately not bundled here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Sweep the stale-status class across the whole plan, not the one line I fixed (review 49289) Review 49289 caught three more instances of exactly the defect this PR exists to remove, and it was right: I corrected one occurrence and did not sweep for the class -- the same shortcut I would send back on someone else's diff. Fixed, all verified against main rather than recalled: section 7 C0(c) bullet still called the split "the next implementation slice" while the C0 summary said DELIVERED section 9 operator ruling same, so a reader of section 9 could still treat C0(c) as open section 2 table still listed a single lens_contract_complexity row carrying RatchetForever. That row does not exist on main. Replaced with the two rows that do -- derived_kernel (WallAfterGrounding) and optimality (RatchetForever, the genuinely undecidable residue) Two the review did not name, found by sweeping for the class instead of the reported lines: section 4 asserted in the present tense that lens_contract_complexity carries RatchetForever. Marked RESOLVED by #7841 and kept as the argument the split rests on, rather than deleted -- the reasoning is why the split has the shape it has section 8 a discriminating control named the deleted contract. Re-pointed at both split rows, where the mode/consumer disagreement is actually checkable status blockquote said one carrier "currently" declares it permanent Remaining bare mentions at lines 119, 143 and 147 are deliberate historical references ("the mixed row", "as written then") describing the pre-split state in explicitly past tense, not live claims about the tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the dissolution trigger: it named a deleted row and set an unsatisfiable condition (review 49307) Third catch of the same class, and this one was more than a stale name -- the retirement criteria were unsatisfiable as written. Clause 1 required that "lens_contract_complexity's boundary class distinguishes the genuinely undecidable residue from the decidable capability". That is precisely what #7841 delivered, so the clause is SATISFIED and now says so, naming the two rows that replaced the mixed one. Clause 2 required that "lens_contract_complexity has a live consumer witness and has flipped off AuditOnly". Read onto the split, that can never be true. lens_contract_complexity_optimality is RatchetForever and stays permanently AuditOnly by the plan's own section 7 C0(c) ruling -- so a retirement condition demanding it flip would make this doc unretirable BY CONSTRUCTION. Re-scoped to lens_contract_complexity_derived_kernel, which is the half that can legitimately flip, and the clause now states explicitly why optimality is excluded so nobody re-adds it later. That second half is the part worth noticing: a dissolution trigger that cannot fire is a scaffold with no exit, which is the failure DESIGN section 6 asks a named trigger to prevent. The stale name would have been cosmetic; the unsatisfiable condition was not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * WIP: cost analysis * Remove merge-event status prose from the plan; cite carrier symbols instead Reviews caught this plan stale in four separate places, each claim true when written and false a day later. The common shape was a merge-event assertion -- "DELIVERED ON MERGE of #7841", "both rows are on main" -- which rots without anyone touching it, because nothing in the tree can decide it. Replace the class, not the four lines. Every delivery claim now names the carrier SYMBOL a reader can resolve (DESIGN section 3, cite-the-symbol), so it is decidable by grep rather than by remembering which PR merged when. Verified: all seven cited symbols resolve; zero PR numbers and zero merge-event phrases remain in the carrier. Also fixes a real dangling reference the C0(c) split created: the census note in gunbc.v1_complexity_capability_census cited lens_contract_complexity, a row that no longer exists. Re-pointed at lens_contract_complexity_derived_kernel, which is the one a consumer surface would actually move -- optimality is AuditOnly permanently. * chore: regenerate drifted generated artifacts (ci auto-heal) * WIP: cost analysis * Stop the witness-admission row scan slicing UTF-8 prose mid-character The floor worker died exit 101 with no terminal receipt on witness_admission_entry_function_keys_from_source: WINDOW is a 400-BYTE budget applied to UTF-8 prose via after.len().min(WINDOW), so whenever byte 400 lands inside a multi-byte char the slice panics and takes the whole run down. The row that exposed it simply happened to put an em dash across that offset. Every other slice in that function takes its offset from find/split_once, which return char boundaries; this fixed offset was the only unguarded one. It now walks down to the nearest boundary, so an over-long row is truncated rather than fatal. Note the comment directly above already recorded an earlier panic in this same function, so the failure mode has form. Fixing the string instead of the scanner was the tempting move and it is the unmarked workaround DESIGN section 5 forbids: it would leave the defect armed for the next author who writes prose with an em dash in it. So the em dash stays in the row, and the scanner is what changed. PROVEN DISCRIMINATING BY EXECUTION, both directions: - unfixed: panics "byte index 400 is not a char boundary; it is inside em dash (bytes 398..401)" -- the production failure reproduced exactly - fixed: passes The fixture puts entry: and function: FIRST, mirroring real rows, because the scanner deliberately panics when entry: falls outside the window and that refusal is correct behaviour rather than the bug under test -- an earlier draft of this fixture tripped that arm and would have tested the wrong thing. The prose is a run of em dashes shifted by 0/1/2 ASCII bytes, covering every residue class mod 3, so at least one iteration is GUARANTEED to put the window edge strictly inside a character. The test deliberately does not model where the scanner sets search_from; pinning that offset would make the fixture fail for the wrong reason the moment the scanner moves. * Lane A C1: evaluate CostExpr at concrete SizeVariable bindings, with the CostSum binder lexically scoped The subject is semantic work, not hardware time: a CostExpr plus concrete bindings yields exact, unresolved, or refused modelled work. No duration, rate, or calibration constant enters this module. The load-bearing part is binder handling. v2.lens.cost.expr normalize_cost_expr_to_symbolic matches CostSum with the binder discarded, so a binder-dependent body projects a SymbolicCost naming a variable that is bound inside the sum - the magnitude may survive, the variable identity does not. This evaluator does not inherit that: env_bind prepends and lookup_bindings answers the head-most match, so a binder shadows an outer binding of the same identity for the extent of its body and only for that extent, with removal on exit structural rather than an unbind step. Proven by execution against a binder-discarding mutation: pinning the binder to Zero reds binder_dependent_body_is_summed_not_treated_as_invariant and binder_shadowing_is_scoped_and_removed_on_exit while leaving the invariant-body and zero-iteration witnesses green - which is the exact signature of the defect, and is why the existing C1 fixture (same variable for binder and bound) cannot see it. A second mutation answering zero for an unbound variable reds both missing-binding witnesses while zero_iterations_produce_exact_zero stays green: that pairing is what keeps a zero in a cost report unambiguous between nothing to do and nothing modelled. Carriers: v2.lens.cost.valuation CostEvaluation, CostValuationBinding, evaluate_cost_expr, evaluate_cost_expr_partially; std.algebra FreeSemigroup with v2.std.algebra non_empty_singleton / non_empty_append. The nonempty arms are carried by std.algebra FreeSemigroup - the free semigroup over T exactly as FreeMonoid is the free monoid over T, one algebraic axis at two identities. Its head field is the construction wall: an UnresolvedCost naming no free variable and a refusal carrying no cause have no representation, rather than being validated against. It is deliberately not the Refined<List<T>> spelling of the manual fixture, whose refinement predicate concedes the empty state is writable, and the NonEmptyList alias is withheld until that fixture retires so one name never resolves to two declarations. NonEmptyDiagnostics, FiniteSet, NonEmptySymbolChain and NonEmptyGrammarChoiceAmbiguityRows are the same shape monomorphised and dissolve onto it at their own already-declared triggers; that corpus-wide de-fork is not carried here. Stated gaps rather than silent ones: BoundedCost is declared-not-produced because its seed is a ceiling-valued binding, which is the sibling discrete-cost note's envelope scope; CostLog refuses its magnitude through the expression authority's own vocabulary until a cited integer-logarithm authority exists on std.nat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review 49620 and the parent lane review: interval arithmetic, claim totality, quadratic accumulation, and a false citation Bounded-upper CostSum answered an EXACT value. combine_evaluation of ExactCost 3 and ExactCost 7 under nat_max reaches combine_known with both endpoints at 3, so bounded_or_exact collapses to ExactCost 7 — an exactness claim manufactured from an inexact input, sitting exactly where the envelope lane plugs its seed in. Replaced with evaluation_interval, which pairs the LOWER of one evaluation with the UPPER of a different one; an inverted interval refuses with InvertedCostInterval rather than being normalised by swapping the endpoints. Witnessed at its own seam because the arm has no producer yet; restoring the max spelling reds three of the four new witnesses. The PR body claim that the envelope lane adds a seed and no arithmetic was therefore false and is retracted. The Tier1 claim conjunction ran three of thirteen witnesses while its label asserted the refusal and scoping behaviour the other ten established, so CI could go green with the label false. every_valuation_witness_holds now enumerates every test fn in the file. Refusal accumulation was quadratic and reachable today: non_empty_append is linear in its left argument, so a body refusing under upper = u built u causes left-nested — and CostEffect refuses unconditionally in this slice. The causes were also IDENTICAL, because the body expression is fixed and only the binder value changes, so this amplified one deficit u times rather than counting u deficits. evaluate_summation now stops evaluating the body once the accumulator has settled; the soundness argument (a refusal here is a property of the body expression, not the binder's value) and its dissolve-on are on the carrier, and the new one-cause witness reds when the skip is removed. The FreeSemigroup grounding note carried a false citation, in the note arguing for single authority. Of the four carriers it named as each already carrying a migrate_when_nonempty_list_refinement_promoted trigger, grep says only two do, and a fifth instance was omitted. Corrected to five carriers, two triggered. Both surviving triggers are RETARGETED onto FreeSemigroup: they named the event as promoting the manual refinement to std, and this change decided that promotion is not the mechanism, so they would have been left waiting on an event that cannot occur as spelled. Also: the partial evaluation mode is stated as not being an escape from a refusal (swapping entry points to silence one is the author-side absorbing fallback); the order note now says causes accumulate WITHIN an arm and the dominated side's payload is dropped ACROSS arms; and the projection-path binder discard in normalize_cost_expr_to_symbolic is named as tracked debt with its trigger and the reason the existing fixture cannot see it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trigger the pull_request ci workflow, which never fired for this branch Zero GitHub Actions runs were created for either pushed SHA over roughly an hour, while sibling branches got ci runs throughout that window; the run was not awaiting approval either. main protection requires the ci check, so an empty synchronize is the only way to get one. Two approvals on the previous head are re-earned rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Recut to the exact core; fix the log arm reporting the wrong cause Operator ruling: scope to complete exact valuation. Kept — exact valuation at a complete binding set, CostSum binder scoping and shadowing, duplicate refusal, effect refusal, log refusal, and the earned-zero versus fabricated-zero distinction. Held on session/deep-owl-215-partial-bounded-hold — partial evaluation and bounded evaluation, sequenced rather than rejected, with both constraints that ride with them recorded on the carrier so they cannot be quietly undone: complete and partial stay TWO ENTRY POINTS over one fold, and the unresolved state must carry the residual CostExpr, not just free-variable names. CostEvaluation is two states here because at the exact core there are exactly two honest answers: the work is this number, or here is why it cannot be. Declaring an arm with no producer is rung inflation — and the bounded arm in particular was carried through an earlier revision while its arithmetic silently collapsed a two-endpoint envelope to its own ceiling. The log arm reported the wrong cause. It matched CostLog { base: _, .. }, ignored the base entirely, and answered UngroundedLogArgument for every non-refusing case — so base 0 read as an ARGUMENT problem, and a fully grounded argument was called UNGROUNDED, while v2.lens.cost.expr's normalizer right beside it already checked the base against its own InvalidLogBase. Two paths disagreeing about one fact. Now three distinct causes for three unrelated failures: an invalid base refuses through the expression authority's InvalidLogBase (cited, not re-minted), an unbound argument refuses as MissingSizeBinding like any other unbound size, and a valid base with an exact argument refuses as IntegerLogarithmUnavailable, which names the real gap and is countable against it. Restoring the base-ignoring arm reds both invalid-base witnesses; the base-zero and grounded-argument pair is the discriminating one, since the old arm gave both the same cause. All 16 witnesses green by execution, all 16 enrolled in the claim conjunction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Enroll the claim conjunction as a test fn: it was the one orphan the naming-hygiene gate found The enroll-or-refuse pre-plan walk refused v2.test.lens_cost.valuation every_valuation_witness_holds as a plain fn unreachable from every test fn and test data — silent de-enrollment. It is the single root of the run: the ci job's failure was FloorUpstreamAlreadyRed with build=success and heal=success, which adds no verdict of its own. Note the remedy that does NOT work, since it is the obvious one: the claim data already referenced the helper by name — that is what it exists for — and a plain `data` does not count as reachability for this gate. Promotion to `test fn` is what satisfies it. Worth naming the shape rather than just fixing it: the conjunction was factored out to close review 49620's finding that the claim enumerated three of thirteen witnesses, and the helper that fixed a specification-without-execution defect was itself not executed by anything the gate recognises. Same class, one level out. The totality note now says so, so the next reader sees why the enrollment is deliberate rather than incidental. 17 witnesses green by execution; the local pre-plan walk that produced the CI refusal now completes clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Regenerate the stage0 seed: adding FreeSemigroup to a std authority drifted the self-host fixed-point regen_verify_gate_passes returned false because src/v1/stage0/src/std_algebra.rs did not contain the FreeSemigroup the emitter now produces from dag/std/algebra.dag. Adding a type to a std authority without regenerating the committed emitter output is exactly what the fixed-point gate exists to catch. Regenerated with regen_stage0 rather than hand-edited — the file is emitter output, and a hand-added struct would pass the diff while making the seed diverge from its authority. The diff is 7 lines, additive, only the struct. Order mattered: regenerate, rebuild the seed binaries from the regenerated sources, then verify with those binaries — regen_divergence_count=0, and 17 witnesses green against the rebuilt seed. The ci job's failure was again FloorUpstreamAlreadyRed with build and heal green, so regen was the only real signal. Note on the emitted shape, since it is visible in the diff: FreeSemigroup carries a _phantom: PhantomData<T> beside an already-inhabited T. That is the emitter's established convention for generic structs here, not something this type triggered — 25 of 39 structs in this one file carry it, including Magma<T> and the FieldOfFractions<R> pair DESIGN names as correctly grounded. There are zero construction sites for it in emitted Rust, so it imposes nothing on call sites. Whether the convention is a defect is a corpus-wide emitter question, not this PR's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Zero is a two-sided annihilator: fix CostMul and symbolic_product together CostMul routed both sides through the generic refusal-dominates combiner, so CostMul of zero and CostEffect refused while the symbolic authority answered zero for the same expression — two paths disagreeing about one CostExpr, and the exact evaluator was the wrong one: a zero multiplicity means the body is never executed, so refusing because an unexecuted factor lacks an effect model is the did-no-work versus could-not-model conflation the earned-zero controls exist to prevent. Fixing only that would have traded one divergence for a narrower one, because the reference was itself ASYMMETRIC: symbolic_product matched a's arms first, including UnknownCost, and only then inspected b — so zero times unknown absorbed while unknown times ZERO returned unknown. Multiplication is commutative, so an operand order that changes the answer is a defect, not a style. Both paths are fixed in one motion and now agree in both orders. The annihilator lives in a multiplication-specific function and beats refusal-dominance, which governs every other combination here. That ordering is load-bearing and the carrier says so: checking refusals first reads like tightening a fail-closed path and would silently restore the defect, with only the two zero-times-effect witnesses reding. Controls, both directions on both properties, because an asymmetry defect cannot be caught by an asymmetric test: zero times unknown and unknown times zero both absorb; one times unknown and unknown times one both stay unknown; zero times CostEffect and CostEffect times zero are both ExactCost zero; one times CostEffect still refuses. Mutations discriminate — reverting multiply_evaluations reds both zero-order witnesses while one-times-effect still refuses; restoring the old arm order reds only the right-operand case. 24 witnesses green. The Blocking cost-wall baseline was re-run because this touches an authority feeding it: loop_iteration_floor (incl. its independent linear-times-zero control), disj_alternative_floor, bounded_summation (incl. existing_cost_lens_kernel_unchanged), atom_zero, expr_refusal and copied_port_derivation — 19 witnesses, all green. Witness accounting corrected: the claim label and note now say every LEAF witness, which the aggregating conjunction obviously cannot include itself in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Qualify the one bare nat_max the second Nat authority made ambiguous Adding v2.std.nat.nat_max (the Peano Nat, needed by evaluate_size_expr's SizeMax and evaluate_cost_expr_in_env's CostMax) gave the name two declarations. test.claim.realization_schedule_critical_path is the single module whose import closure carries both authorities, so its bare call site stopped resolving and reddened the compile-clean gate. Qualified as std.nat.nat_max, matching the std.nat.nat_compare already qualified on the line above it inside the same witness. The four other bare nat_max call sites were checked by scoped compile rather than assumed safe -- dag/std/realization_width, dag/std/realization_measurement, dag/gunbc/econ/free_tier_serving and dag/gunbc/econ/scm_serving_model each compile with zero errors, so their closures do not carry both authorities and they need no qualification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Close the two hygiene gates: exhaustive arms where possible, one rostered residue The floor reported three reds. Two roots, one of them not mine. NON-FOLD RESIDUE. Three wildcard arms landed with the exact evaluator. Two were avoidable and are now gone rather than declared: combine_evaluation and evaluate_summation match over CostEvaluation, which has exactly two variants, so `_ =>` became `ExactCost { value: .. } =>`. DESIGN section 5 puts construction above declaring a residue, and it buys what a roster row cannot -- a third CostEvaluation variant would now make both matches non-exhaustive and compile-clean would refuse, instead of the new variant being swept into a wildcard silently. multiply_evaluations is rostered, not "fixed". Its arms discriminate a nested VALUE (an ExactCost carrying Zero) rather than a variant, so enumerating would write the residual combine_evaluation call twice, underneath the arm order that multiply_evaluations_zero_note declares load-bearing. Duplicating that expression to satisfy a counter would make the function more fragile. Its reason says so, rather than reusing the generic nfr_reason_mint_era its neighbours carry. INERT CARRIER. Not from this change. Probing inert_carrier_names_live returned eleven live names against twelve rostered; the extra is ProcMeminfo, whose consumer landed in #7891 -- already an ancestor of this branch -- while the roster row stayed. origin/main carries both files byte-identically, so this witness is red on main too. The row is deleted here because it blocks merge and deletion is exactly what its own dissolve trigger prescribes; reported upstream so another lane does not duplicate it. Also records, in symbolic_product_zero_absorption_note, that the symmetry the annihilator fix established is CLASSIFICATION and not provenance: unknown times unknown still returns the left operand's diagnostic. That is pre-existing and module-wide (symbolic_sequential drops the same way), and closing it means widening UnknownCost to carry several diagnostics, which is its own change with its own witnesses. Green by execution: non_fold_residue_clean_holds, non_fold_residue_no_unrostered_or_stale, inert_carrier_no_unrostered_or_stale, and every_valuation_witness_holds over all 23 leaf witnesses -- the last because the exhaustive-arm edits rewrote live evaluation paths, not just roster data. Seed receipt reports unrostered=0 stale=0 live=149. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Roster #7965's two carriers and handle #7909's two git mock cases Both failing witnesses on this PR are MAIN-SIDE DEBT landed today, not this branch's. Neither constituent introduced them and neither of main's own runs executed the witnesses that catch them. inert_carrier_no_unrostered_or_stale. TerminatingOneShotFrontier and WitnessPurpose (dag/std/witness_purpose.dag, #7965) are a taxonomy landed ahead of its consumer: nothing outside their own module and their own test file references them, which is precisely the seed's inert predicate (declared once, referenced by a test, zero non-test references outside the declaration block). They get roster rows in the established shape, carrying the shared dissolve trigger -- a live consumer reads the carrier and the stale-roster check then forces the row's deletion. git_mock_consumer_is_total_holds. #7909 added git.Inspect.WorkingTreeStatus and git.Inspect.IgnoredFiles to git_published_mock_corpus without arms in materialize_git_mock, so both fell to mock_unhandled_rejection and totality failed. main publishes 20 cases against 18 arms; this adds the two arms. HOW THE OWNER WAS ESTABLISHED, because my first attribution was wrong. I found seven type carriers new versus main and inferred they were the inert ones -- true premise, unsupported conclusion, since none of the seven can satisfy the predicate (five are referenced by no test file at all; the other two are consumed in non-test fn signatures). The primitive is not directly invocable (no unique runtime declaration identity) and a second-worktree run is invalid because the corpus scan anchors to the compile-time-baked workspace root. So the rule was read out of the seed and reimplemented, and THE REPLICA IS VALIDATED AGAINST THE EXECUTED ORACLE: on this head it reproduces 13 inert against 11 rostered, exactly the unrostered=2 stale=0 the real run measured. Run over three trees it gives main 2 unrostered + 1 stale, pr-7932 clean, this branch main's 2 with main's stale already fixed -- so this branch was strictly better than main before this commit. GREEN BY EXECUTION, with the discriminating red on the real consumer: inert_carrier_no_unrostered_or_stale FAILED on this head before this commit and PASSES after; git_mock_consumer_is_total_holds likewise, alongside both omission RED controls (git_mock_omitted_member_is_red_holds, git_mock_omitted_show_tree_is_red_holds) still passing, so the added arms did not disarm the controls. No regen: neither module projects to a stage0 seed file and no seed .rs references the roster units, verified against a positive control (dag/std/algebra.dag does project, to std_algebra.rs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: gunbai-bot[bot] <289086189+gunbai-bot[bot]@users.noreply.github.com>
briansrls
added a commit
that referenced
this pull request
Sep 5, 2026
….E, §4.I, §4.J (#10576) * Correct §4.A hygiene-reaper row: CASE 2 — four host_hygiene_reap_*_body symbols deleted by #8583 The §4.A row at L379 described host_hygiene_reaper_script.dag's 4 body symbols as A5-deferred. The file was deleted by ffa16a5 (#8583, Migrate host-hygiene reaper and liveness onto typed observation) and the construction was migrated to typed host_hygiene_reaper_observe.dag / host_hygiene_reaper_remediate.dag / host_hygiene_liveness_observe.dag. No direct successor body names exist — CASE 2 (file deletion upstream) with hybrid CASE 1 (body names dissolved). Verification: - ffa16a5 is ancestor of origin/main ✅ - host_hygiene_reaper_script.dag: D in #8583's diff - zero files define host_hygiene_reap_install_units_body et al. - observe/remediate files present at dag/gunbc/host/ Part of #10537's per-row adjudication program. * Correct §4.D srv3_chown_directory_to_current_user: CASE 4 — renamed AND climbed The row at §4.D L436 listed srv3_chown_directory_to_current_user as A5-deferred (srv3). It was actually renamed AND climbed by 20ad5b3 (#8796): successor is gunbc.host_effect_realize.srv3_ensure_directory_owned_by_current_user. New name has a stronger guarantee (readback-based, not chown exit-status based). This is CASE 4 (rename plus climb) — distinct from CASE 1 (dissolution) because the construction did not disappear; it acquired a better name and a stronger guarantee. Verification: - 20ad5b3 is ancestor of origin/main ✅ - srv3_chown_directory_to_current_user: 0 declaration files - srv3_ensure_directory_owned_by_current_user: 2 declaration files Part of #10537's per-row adjudication program. * Correct §4.E: 4 stale foreign-executor rows Four symbols claimed as 'already on emit' are no longer present in the corpus. Each is struck through with its deletion commit: 1. ci_selection_control_script — DELETED by 611fd02 (#8283, CI floor cut). CASE 1/2: the ci.yml file was deleted and its selection-control script dissolved with it. Successor workflow is witnesses.yml via gunbc.witness_floor_workflow. 2. gunbc_ci_run_script — DELETED by 489346f (#9252, plan/walk CLI delete). CASE 1: the gunbc ci verb was deleted, taking its run script. 3. ci_regen_ensure_rustfmt_path_script — DELETED by 3b431f3 (#8406, REGEN ROOT CUT). CASE 1: regen_stage0 root deleted; rustfmt path script was zero-consumer machinery. 4. expected_live_deploy_retract_script — DELETED by d409b75 (#7909, Phase A release identity refactor). CASE 1: recategorized to runtime-present, then dissolved. All four deletion commits are ancestors of origin/main ✅. Part of #10537's per-row adjudication program. * Correct §4.I, §4.J, §4.D ownership table: 18+ stale symbols §4.I — ci_native_cache_root_toolchain_segment_command DELETED #7436 (003d960). CASE 1 dissolution — toolchain segment computation reordered after setup-rust-toolchain; fallback table entry struck through as RESOLVED. §4.J.A — ci_floor_stamp_merge_admission_script All three raw leaves (ci_floor_stamp_ambient_exit_command, ci_floor_stamp_root_command, merge_admission_stamp_command) DELETED #7522 (87a4af3). CASE 1 dissolution. 'PARTIAL #7293' status stale. §4.J.B — ci_floor_materialization_receipt_gate_script, ci_floor_resolve_receipt_gate_script DELETED #7470 (b01cdf4). CASE 1 dissolution — WalkPlan success stages finalization dissolved both receipt gates. §4.J.C (ci_spec.dag table): - gunbc_ci_floor_only_script DELETED #9252 — CASE 1 - ci_regen_floor_skip_shortcut_script DELETED #8406 — CASE 1 - gunbc_ci_regen_floor_only_script DELETED #8406 — CASE 1 - scheduler_invoke/scheduler_invoke_with DELETED #9252 — CASE 1 - git_fetch_script RENAMED #6833 — CASE 3 (successor: git_fetch_no_tags_shell / git_fetch_prune_shell) §4.J.D (ownership table): - Merge-admission row: all three raw leaves struck #7522 (CLOSED) - CI materialization row: both receipt gates struck #7470 (CLOSED) - CI-spec row: stale symbols struck through individually - Already-routed row: ci_selection_control_script #8283, gunbc_ci_run_script #9252, ci_regen_ensure_rustfmt_path_script #8406 (and 11 rustfmt raw leaves) struck through - Runtime terminal row: host_effect_plan_placeholder_effect DELETED #10509 - Deferred srv3 row: srv3_chown_directory_to_current_user struck #8796 (ref §4.D), all 4 host_hygiene_reap_*_body + liveness body struck #8583 (ref §4.A) All deletion commits verified as ancestors of origin/main ✅. Part of #10537's per-row adjudication program. --------- Co-authored-by: Brian Searls <briansearls1@gmail.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 14, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prerequisite half of the ordered merge train for #7802 (operator ruling 2026-08-05). #7802 stays draft until this is live on srv1.
What this establishes
gunbc servebinds its compiled graph once at process start, so the only authority for "what revision is the public route serving" is the running process. This makes the process say so, and makes the deploy compare against the candidate it admitted rather than against anything it can re-read.gunbc serve --release-revision <sha>— validated (40 lowercase hex) before graph compile and before listener bind, so an unusable identity refuses before the port is taken. The captured value is cloned per request, so no request can see a different release than the one the process launched as./healthzpublishesrevisionandsurface_bundle_identityas two separate members. They fail independently and their remedies differ, so they are deliberately not fused. Two deploys of the same tree at different commits render byte-identical surfaces — a commit touching only Rust, only tests, or only docs changes the revision and not one served artifact — so a surface-only readiness greens against the previous process on most deploys.gunbc.live_deploy.candidateobserves the deploying checkout's HEAD once, at the public apply launcher, before any host mutation. Not a slot's disk HEAD — that is the disk-derived ordering the ruling struck down.observe_candidate_releaseis a function, not a top-leveldata, so importing the module performs no Git I/O.candidate.revisionandcandidate.expected_surface_identity— the same single values the launcher captured and the emitter wrote intoExecStart. It does not re-materialize the bundle at comparison time, which is what closes the TOCTOU: a recomputed expectation would describe the tree as it is when compared, so expectation and verdict would drift together and agree with a process other than the one admitted.Topology and release are now separate, and the scaffold is gone
The earlier revision rode
release_revisiononDeploymentServiceConfig. That field is deleted. The tell was on the retract path: a retract installs no release, yet it had to construct aDeploymentSpecand therefore name a revision it never read.The launch identity now travels beside the spec as
ReleaseRevisionBinding = RevisionBoundAtEmission { revision } | RevisionNotInstalled, supplied at the apply seam and read only whereExecStartis rendered. The retract fold passesRevisionNotInstalledand so cannot name a revision even by accident — the arm that would render one emits a poison marker that reds the golden drift gate rather than emitting a plausible line.Refusal causes are peers, not kinds of one another
HealthzRefusalCauseis flat:ProcessIdentityAbsent·ProcessIdentityMalformed·ProcessRevisionMismatch·ProcessSurfaceMismatch·ServedSurfaceStale. Four different remedies, four arms —Absentis the migration case (a process launched before the channel existed),Malformedis a defect,RevisionMismatchmeans identity read cleanly and disagrees,SurfaceMismatchmeans another tree won the route,ServedSurfaceStalemeans the graph did not rebind. Check order is probe → parse → identity → revision → surface, chosen so the first refusal is the most locating one.The observation carrier one layer down stays grouped (
RunningReleaseIdentified | RunningReleaseUnidentified { cause }) because a real consumer branches on identified-versus-not without enumerating causes. A grouping something reads is a level; a grouping nobody reads is a nickname.A defect this change had to fix in itself
roadmap_site_healthz_surface_is_currentcompared the whole healthz body againstbundle.healthz_body. Adding identity members would have made every deploy refuse forever — the permanently-refusing shape this lane exists to remove, reintroduced one layer down. The surface comparison now projects the surface keys out of both documents and normalizes both through the same parser and emitter, so it is a claim about values rather than about two spellings of them.Dead artifacts deleted
.github/live-deploy-srv1-apply.shand-retract.sh(136 lines) are gone, with their generated-artifact registry rows and.gitattributesentries. Their byte oracle moved intoemit_test.dagwitnesses that pinExecStart— including that it carries--release-revision <candidate>— with discriminating REDs.The checkout that produces the candidate is admitted, not assumed
observe_candidate_release()reads HEAD, then admits the working tree, then renders the surface — in that order, because the surface identity is a fingerprint of the working tree and computing it first would mean the refusal was built from bytes already decided untrustworthy.CandidateCheckoutClean | CandidateCheckoutDirty { tracked_changes, staged_changes, untracked_deployed_paths, ignored_deployed_paths } | CandidateCheckoutUnobservable. Candidate construction proceeds only fromClean. Four path lists rather than adirty: Bool, because the remedies differ per axis and a bool is the same refusal with the locating information deleted.A hole the first version of this admission had, found in review and closed here.
git status --porcelain=v1 -z --untracked-files=alldoes not report ignored files, while the source sync excludes onlytargetand.git. This repository ignores.env,/.gunbc/,.claude/,/target-codex/,/.cargo-home/, and — inside a source root the compiled graph actually reads —src/v2/test/claim/manual/sg2_scratch_probe.dag. So an ignored path outside those two prefixes was copied by rsync while the admission reported the tree clean, and the resulting state is self-consistent and therefore invisible downstream: HEAD names A, rsync installs B, the expected surface is rendered from B, the process reports A and B, readiness compares A/A and B/B and greens. That is the false identity this admission exists to forbid, surviving inside it.Closed by a second operation, not an
--ignoredflag on the first, and the choice is measured rather than stylistic:git status … --ignored=matchingreports a directory that itself matches an ignore pattern as one folded entry (!! ign/) and never names the file inside it — the same folded-ancestor loss--untracked-files=allexists to avoid, and fatal here because the consumer must filter each path through the deployed-scope authority and must name the offending path in its refusal.git ls-files --others --ignored --exclude-standard -zanswers with real file paths. Both spellings were run side by side against git 2.43.0 on a constructed repository.ignored_deployed_pathsis its own axis rather than folded into untracked, because folding would tell an operator togit adda path.gitignoreexists to keep out of the index.Evidence
Green by execution, not by typecheck:
running_release_identity_witness_test.dag. They vary bundle identity and artifact bodies separately, assert the expected locating cause, and deny the earlier causes — so a red is attributable to the one axis it varies. Healthy and refusal fixtures come from the same producer differing in one input.candidate_checkout_witness_test.dag, covering each dirty axis in isolation, the both-columnsMMcase, the scope filter in both directions on both the untracked and ignored axes, segment-vs-character-run (targetish/x.dagis deployed), the rename source that defeats a shape-first parser ("ab cdef"), the truncated rename, and the malformed field.emit_test.dagpins the emittedExecStartagainst the candidate value, with three REDs.--target dagcompile clean atc93e035; the CI floor's own compile-clean gate re-verifies it at the current head (local re-runs were being OOM-killed under fleet contention, so I am not asserting it by hand).Two boundary facts were established by running them, because the unit witnesses above provably cannot reach them. Everything in
candidate_checkout_witness_test.dagfeeds synthetic fields to the scan: it proves the parser and the scope filter, and it would have passed just as green while the operation asked Git the wrong question — which is the ignored-file defect itself, one level up..dagfile under a source root, marked ignored, produceduntracked-deployed=(none) ignored-deployed=dag/test/claim/manual_candidate_wet_probe.dag. The same file was confirmed absent fromgit status --porcelain=v1 -z --untracked-files=allin that same tree, so before this axis existed the admission read the tree as clean. Positive and negative halves in one run at real scale: 1368 ignored paths in the checkout, 1367 undertarget/, exactly one reaching the refusal.gunbc serveprocesses on ephemeral ports at three declared revisions. A process launched at A kept answering A and its original service surface after the.dagauthority beneath it changed; a process launched after the edit answered the new one. Same bytes on disk, same instant, two different answers, distinguished only by launch time. The first attempt at this was vacuous — mutatingROADMAP.mdchanged nothing because it is itself a generated projection — and only the fresh-process positive control exposed that, which is why the mutation subject moved to a.dagauthority.What is still owed, named rather than implied
extdeps.git.inspect, not a harness trick, and it is the receipt's dissolution trigger. The live-process one is ~100 s of graph compile per process and belongs on the budgeted wet cadence lane, not the per-PR floor.ProcessReadysuccess coproduct is not claimed to exist. The flat typed refusal causes carry the operational distinctions today; a richer success value becomes useful when Recut srv1 deploy as a leased, monotonic reconciliation of one public target #7802 consumes the routed process observation as its ancestry predecessor, and may be completed there.CI
Was blocked for several rounds on a main-side break this PR did not cause (#7937's
ensure_is_convergedhomonym, since unblocked). One consequence is worth recording: because the compile-clean gate stops the run, batch 3 had never executed on this branch at all, so its first execution surfaced eleven witness failures that had been accumulating unseen. All four causes were separate and are fixed — three unused imports (one of which narrowed a zero-import module's resolution pool), alabel_list_containshomonym where a local declaration lost to a pool member, a stale registry count literal, and an unrostered non-fold-residue row.That homonym is fresh executed evidence for selection-invariant reference binding: it is not merely that two modules share a name, it is that a module's own call sites bound to another module's declaration. Recorded on the declaration; the property itself stays on the namespace lane.
The
ci.ymlconflicts in the main merges are resolved by regenerating from the merged authorities, per the generated-artifact merge driver's own refusal — never by picking a side.🤖 Generated with Claude Code