Repository navigation
Retire the srv1 sum: main supersedes the revision srv1 is running - #10803
Merged
Merged
Conversation
…in roster with named homes, and a three-valued answer DESIGN.md gains §3b, conformance: §2–§3 applied to a change in motion. For every concept domain a change touches the corpus already homes the concept, so the change inhabits that model or states why it diverges; the answer is three-valued (conforms, diverges with a stated reason, diverges with no reason) and only the last is red. The domain roster is a modeled fact, `gunbc.design_argument` `conformance_domains`, one row per domain with its home declarations as `DeclarationRef` rows: external facts (extdeps), materialization/realization, leasing/locking/grants, fabric/compute. The design row `s3b.conformance` derives its review parts from that roster, and `roadmap_review_criteria` derives one reviewer per part exactly as it does for every other proposition. The extdeps-placement reviewer folds into the roster as its first domain; `s1.grounding-intersubjective` records why it now mints no reviewer of its own. `single-authority` loses its "a concept an extdeps or std module already owns" tell, which was the conformance question wearing the wrong algebra, so the two stay exclusive. `PropositionReview` and `ReviewCriterion` carry the answer algebra (`YesIsADefect` | `ConformanceThreeValued`) and the homes; the brief names the homes and renders the instruction from the algebra; `roadmap_review_role` owns the `ConformanceAnswer` → `HeadVerdict` mapping, so a stated divergence is admitted with its reason as the summary. Witnesses: the role module gains the answer mapping and a per-domain brief check (red when a domain's homes are stripped, measured); the function module's fixture now names the five incomplete calls; `review_criteria_homes_match_their_algebra` proves every conformance criterion names a home and no yes/no criterion does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
Ledger-Repair-Judged: docs/design-failure-modes.md Ledger-Repair-Judged: docs/design-rung-drops.md
…into the plan revision Two findings from the review on gunbc#10679, both correct. DESIGN.md is a projection of `gunbc.design_document` `design_blocks`, so the hand-inserted §3b would have been deleted by regeneration and refused by the drift gate; the section is now authored as `section_3b_blocks` and DESIGN.md is regenerated through the generated-artifact driver. And `review_plan_revision_of` hashed only key, question and tells, so a change to a domain's homes or answer algebra would have reused a verdict produced under old semantics; the revision now hashes `review_criterion_identity_text`, which carries both, and the roster witness proves that dropping the homes moves the revision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
…who consumes a change and by what executing route Operator ruling (2026-09-06): beside conformance, review must interrogate consumption separately — who consumes what a change adds, and how — so dangling modeling is red however well shaped it is. DESIGN.md gains §3c (authored in `gunbc.design_document` `section_3c_blocks`, regenerated through the driver); the design argument gains row `s3c.consumption` (premises §2 and §5) with the `consumption` reviewer: for each added declaration name the consumer and the execution route, or the later change with its trigger; a declaration with neither is dangling. The roster is ten reviewers; the function witness fixture names the six incomplete calls. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
…the worktree is
Two of the three harness gaps the first live review run exposed (2026-09-06, attempt eca90231 on shell-typed-invocation). (1) The reviewer's turn ended only on the provider's stop reason, so a reviewer that wrote its verdict at round 19 kept spending rounds until the budget refused and its unit reported failure over a verdict the belt accepted. `HarnessTurnConfig` now carries `completion: HarnessTurnCompletion` — `CompletesOnProviderStop` for the worker, `CompletesWhenEntryPresent { directory, name }` for the reviewer and auditor — observed after each tool round by a successful listing of the verdict directory (`extdeps.filesystem.filesystem_io` `filesystem_entry_presence`, never a failed read), and `TurnCompletedByArtifact` exits zero with its own label and `turn.completed` event; an indeterminate listing is a counted `completion.indeterminate` event and the turn continues. (2) Reviewer and auditor guidance said the tool runs "in the candidate's worktree" without naming it, and every slow reviewer spent rounds guessing directories; `ToolRunsInTheCandidateWorktreeAt { path }` now carries the path, as the worker's `WorktreeAt` already did. The belt passes `verdict_dir` and `verdict_name` to the reviewer and auditor CLIs, which derive the path, so the completion subject and the guidance share one authority.
Not in this change: an isolated verdict slot per reviewer (the how-to-work reviewer read a sibling's verdict); the shared review directory stays and is a separate change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
…nding can create its mirror under a root-owned instance root The first ten-criterion review on the converged sum (2026-09-06) spawned every reviewer and every one refused at seat binding before posting a request: `could not create /opt/gunbc/fabric-event-log`, because the seat's event-log mirror and scratch live under the instance root and `/opt/gunbc` is root-owned. The compute root had the same defect and the same repair (`ComputeRoot`): the path is named once, in `gunbc.roadmap_dashboard_instance` `dashboard_instance_event_log_root`, read by both the deployment (`gunbc.live_deploy.spec` `FabricEventLogRoot`, applied by `install -d` as an owned directory and torn down recursively) and the seat binding (`gunbc.harness.harness_seat`, whose private copy of the path is deleted). `deployed_tree_remote_witness_test` carries the new kind. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
…able and serve unit are observed, decided, and mutated only when they differ
Operator direction (2026-09-06): the live deployment path was not converging, it was replaying installation. `deployment_apply_plan` reconciled the desired roster against `observed: []`, so every member was an add on every apply, and four of the five source-compiling evaluators a nineteen-minute converge started were downstream of unconditional restarts. docs/plans/deploy-convergence-observed-side.md refutes feeding that path-keyed roster an observed side (a member whose value is its key cannot be seen to be stale), so this change gives the three release-consuming members a real identity and moves them off the apply-all roster.
- `gunbc.live_deploy.member_identity` (pure): `TreePublicationIdentity { revision, tracked_projection_clean }`, `ExecutableIdentity { sha256 }`, `ServiceUnitIdentity { unit, document content-hash, active state }`; `ObservedMember` (observed | established-absent | unobservable); `decide_member` → `AlreadyConverged | Create | Replace | MemberRefused` (an unobservable member refuses, it is never skipped or reinstalled); `derive_effects` → ordered `DeploymentEffect` list (install executable before publishing the tree, because the publishing oneshot runs the admitted executable; daemon-reload once iff a unit document changed; restart iff anything changed) beside a readiness readback that runs even for the empty plan.
- `gunbc.live_deploy.member_observe` (effectful): one interface, two realizations per DESIGN §3 — typed operations when the executor is the host (`extdeps.git.inspect` HeadCommitIn / ReadTreeIntoIndex / StatusAgainstIndex / IgnoredAgainstIndex, `extdeps.crypto.hash` `crypto.Sha256Sum.File`, `extdeps.systemd.systemctl` ShowProperty, `extdeps.filesystem`), and the same invocations as argv over `run_typed_argv_transport` when the host is behind ssh. Absence is established by listing the parent, never by a failed read. The tree observation reuses the deployed-tree record's three-leg instrument and its reading.
- Wiring: `LiveDeployApply` carries the `EffectPlan`; `live_deploy_apply_mutation_via_transport` derives it from the host before building the mutation and refuses with every member's cause if it cannot; `live_deploy_apply_script_for` renders the plan's effects from the same fragments the apply-all arms used, and reconciles only the remaining owned artifacts against `observed: []` — the declared frontier.
Witnesses (fresh build, hermetic): `member_identity_witness_test` 7/7 — already converged → zero mutations; executable-only drift → install + restart; unit-only drift → write + daemon-reload + restart; failed process under a current unit → restart; dirty projection at the right revision → republish; absence creates and unobservability refuses; an observation of another member is refused. `emit_test` 58/58 including two new: a zero-mutation plan renders no install, no repository-convergence oneshot, no serve restart; a binary-only plan installs and restarts without publishing the tree. `deployed_tree_remote`, `shell_exec_run_argv_embed`, `ssh_transport`, `apply_witness`, `intent_witness` green except `witness_readback_requires_service_ready_gate`, which fails identically on main and is not touched here.
Not in this change: the thin service-user actuator for the repository transition (the rsync and git-native publication oneshots both still run inside PublishTree, in their old order, when and only when the tree differs), the timer members' identities, and folding mode=deploy into the planner.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
# Conflicts: # dag/extdeps/git/inspect.dag # dag/gunbc/harness/harness_seat.dag # dag/gunbc/host/host_effect.dag # dag/gunbc/host/host_effect_realize.dag # dag/gunbc/live_deploy/apply.dag # dag/gunbc/live_deploy/emit.dag # dag/gunbc/live_deploy/member_identity.dag # dag/gunbc/live_deploy/member_observe.dag # dag/test/claim/live_deploy/deploy_release_fixture.dag # dag/test/claim/live_deploy/emit_test.dag # dag/test/claim/live_deploy/member_identity_witness_test.dag # dag/test/claim/shell_exec_run_argv_embed_witness_test.dag # dag/test/claim/ssh_transport_witness_test.dag
The refresh took main uniformly for the thirteen conflicted files, which was right for twelve of them -- srv1-sum carried the pre-review member-identity work and main has the reviewed result. It was wrong for emit.dag: spec.dag AUTO-MERGED, so it kept #10687s FabricEventLogRoot member of OwnedArtifactKind while taking mains emit.dag dropped that lanes two arms. A coproduct member no emitter handled, in a merge reporting zero unresolved conflicts. The conflict list is not the change list. Only the total-match compiler saw it, naming both sites. The arms are restored verbatim from origin/srv1-sum -- an owned-directory install on apply, a recursive remove on retract -- rather than reconstructed from intent.
…hrough the transport runner The first wet operator deploy refused before mutating anything: every directory root reported could not observe the host: reading the owner of <path>: run_typed_argv_transport: LocalShell has no remote target. root_owner_leg routed BOTH arms through the transport runner and handed it LocalShell for the host-itself case, which refuses by construction, so root ownership could never be read on the host the deploy runs on and the whole member plan refused. IT SURVIVED ITS WITNESSES BECAUSE THEY NEVER EXECUTED A HOST EFFECT. The member-identity witnesses are hermetic and supply observations directly, so nothing exercised this leg until a real deploy. Twelve green witnesses, a clean typecheck and a passing floor over a path that could not run in production. Every other local arm in that module calls its modelled operation directly; this one now does too. shell.Stat.OwnerOf is a modelled operation rather than an argv spelled at the call site: stat -c %U is GNU coreutils and not POSIX, and naming that in the operation is why it exists. It is genuinely readonly -- it opens nothing for write and creates nothing. It failed closed. Nothing was mutated, no half-applied deploy, and the diagnostic named all three roots and the exact cause.
srv1 has been deployed from 542c034, not from main, and the two have diverged: they share an ancestor but neither is an ancestor of the other. gunbc converge --mode deploy REFUSES that, correctly -- installing a candidate that is not a descendant of the deployed revision can move a running host BACKWARDS on content, and the refusal names exactly that. This commit satisfies the guard rather than working around it. WORSE THAN A DIVERGENCE, AND THE REASON THIS MATTERS BEYOND ONE DEPLOY: 542c034 is on NO origin branch and is not even an ancestor of origin/srv1-sum-refresh. It was committed on srv1 and never pushed. The running host therefore carries a revision identity nobody else can resolve, and that no other machine can reproduce. Publishing this merge is what puts it back in the shared graph. EVERY BRANCH THE SUM CARRIED HAS MERGED: #10696 (deploy member identity), #10687 (fabric event-log root), #10684 (reviewer turn ends on verdict). The sum has no purpose left. VERIFIED BEFORE TAKING OURS, because -s ours is exactly the move that silently drops content, and an unverified supersede is worse than the divergence it closes: files present in the sum and absent from main .... 0 Every remaining difference is main NEWER version of a file the sum also has: the diff main->sum is +902/-2805, so it would restore old text rather than add anything. Nothing is lost by taking main tree. MERGE AS A MERGE COMMIT, NOT A SQUASH. A squash drops the second parent, which is the entire content of this change. The file diff is deliberately empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
The diff is deliberately empty. The second parent is the change.
Merge this as a merge commit, not a squash. A squash drops the second parent, which is the entire content.
What this fixes
gunbc converge --mode deployrefused to move srv1 onto main:That guard is correct. Installing a candidate that is not a descendant of the deployed revision can move a live host backwards on content, and the refusal names exactly that. This commit satisfies it rather than working around it.
The part that is worse than a divergence
srv1 is deployed at
542c034485b, and that commit:origin/srv1-sum-refresh.It was committed on srv1 and never pushed. So the running host has been carrying a revision identity that nobody else can resolve and no other machine can reproduce —
/healthzreports it, the deploy pins it, and it existed on exactly one disk. Publishing this merge is what puts it back in the shared graph.That is the general hazard of deploying from a host-local sum branch, and it is why this retires rather than refreshes it.
Why taking
oursis honest here-s oursis precisely the move that silently drops content, so it was verified before being taken rather than after:Every remaining difference is main's newer version of a file the sum also has. The diff
main -> sumis+902 / -2805— it would restore old text, not add anything.And every branch the sum carried has now merged on its own: #10696 (deploy member identity), #10687 (fabric event-log root), #10684 (reviewer turn ends on verdict). The sum has no purpose left.
Why it must land on main
Deploying from this branch unblocks srv1 today, but a host deployed off a side branch never becomes an ancestor of main by itself — so without this merge, the next deploy from main hits the identical refusal. Landing it makes every future deploy an ordinary fast-forward and retires
srv1-sum-refreshpermanently.Verified locally before pushing:
🤖 Generated with Claude Code
https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY