Skip to content

Retire the srv1 sum: main supersedes the revision srv1 is running - #10803

Merged
briansrls merged 16 commits into
mainfrom
retire-srv1-sum
Sep 7, 2026
Merged

briansrls merged 16 commits into
mainfrom
retire-srv1-sum

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

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 deploy refused to move srv1 onto main:

deploy-target REFUSED -- deployed and candidate revisions have diverged: they share
an ancestor but neither is an ancestor of the other, so installing the candidate
would move the running host ...

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:

  • is on no origin branch, and
  • is not even an ancestor of 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 — /healthz reports 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 ours is honest here

-s ours is precisely the move that silently drops content, so it was verified before being taken rather than after:

files present in srv1-sum-refresh and absent from main .... 0

Every remaining difference is main's newer version of a file the sum also has. The diff main -> sum is +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-refresh permanently.

Verified locally before pushing:

tree identical to main ............... 0 lines of diff
542c034485b is now an ancestor ....... YES

🤖 Generated with Claude Code

https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY

gunbc-ci-auto-heal and others added 16 commits September 6, 2026 18:40
…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
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@briansrls
briansrls merged commit 334d6bf into main Sep 7, 2026
3 checks passed
@briansrls
briansrls deleted the retire-srv1-sum branch September 7, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant