Skip to content

A fresh host can be deployed: an empty repository root is bootstrapped to the candidate and read back as that revision - #10722

Merged
briansrls merged 1 commit into
mainfrom
fresh-host
Sep 7, 2026
Merged

briansrls merged 1 commit into
mainfrom
fresh-host

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

The stall this closes

gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall — current=OutsideTheLadder, ceiling=StructurallyGuaranteed, blocker=ClimbableButUnbuilt, population of one: deployment_spec_srv1 at a fresh repository root.

It is the reason a new host cannot be deployed at all. The tree member refuses with RepositoryBootstrapUnavailable because a Create would leave a non-repository directory that Git-native convergence then refuses forever — turning a recoverable absence into a permanent one.

The pre-state refusal is not relaxed; one of its causes is routed

ConvergencePreStateUnobservable fired for two states with opposite remedies:

state remedy
repository whose refs cannot be read broken — needs a person
root with nothing in it unborn — needs a repository created

A non-empty root and an unobservable one still refuse with the identical typed cause. The stall explicitly rules out relaxing that refusal, and this does not: it separates two states the refusal had collapsed.

The distinction is observed, not supplied. The deploy established the tree's absence by listing its parent, but this process reads the root itself rather than taking a caller's word about host state it can observe.

Emptiness, not "is there a .git". A directory holding files but no repository is the rsync-era partial state — its bytes are already wrong and its repair is not git init.

Both new writes are fail-closed readings of the arm's own claim

  • base ref created with an empty expected-old — git's "this ref must not exist"
  • worktree moves by checkout -b, which refuses an existing branch

If "nothing was here before" is false, the bootstrap refuses rather than initializing over somebody's repository.

The readback is the only verdict

Every step can exit zero and leave a repository that is not at the candidate — exactly how the rsync-era tree was assembled. The outcome comes from a fresh observation through repository_convergence_verdict, the same authority the ordinary path uses, not a bootstrap-specific notion of success. before and after are deliberately the same observation: an empty root had no refs or worktrees to preserve, so the preservation comparison is vacuous rather than skipped.

Branch name is gunbc_repository.default_branch, not a literal "main".

Evidence is wet

Nothing else can close this stall — its trigger asks for HEAD, index and tracked worktree read back as one revision, and every step can exit zero without that holding. Two rows, both PASS by execution (claim_batch --wet):

an_empty_root_is_bootstrapped_to_the_candidate_and_reads_back_as_that_revision — HEAD at the candidate, git diff --quiet HEAD clean, tracked file holding the candidate's bytes; all checked from outside the transition rather than from its exit status.

a_root_holding_content_without_a_repository_still_refuses_and_is_not_initialized — the discriminator. Without it the first row would pass equally well had the refusal simply been deleted. The stray file is untouched and no .git appeared.

The fixture authors no answer: it builds a source repository with ordinary git and leaves the target as a directory holding nothing. Every fact the assertions read is produced by the transition's own observation chain.

What this does not do

The tree member still refuses absence with RepositoryBootstrapUnavailable. Flipping that arm to Create belongs in its own commit now that the capability is proven — landing it here would make a fresh host depend on a capability introduced in the same change.

🤖 Generated with Claude Code

https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY

…d to the candidate and read back as that revision

gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall declared this OutsideTheLadder
with a bounded population of one -- deployment_spec_srv1 at a fresh repository root -- and it is the
reason a new host could not be deployed at all. The tree member refuses with
RepositoryBootstrapUnavailable because a Create would leave a non-repository directory that
Git-native convergence then refuses forever, turning a recoverable absence into a permanent one.

THE PRE-STATE REFUSAL IS NOT RELAXED; ONE OF ITS CAUSES IS ROUTED. ConvergencePreStateUnobservable
fired for two states with opposite remedies: a repository whose refs cannot be read is BROKEN and the
remedy is a person, while a root with nothing in it is UNBORN and the remedy is to create the
repository. A non-empty root and an unobservable one still refuse with the identical typed cause. The
stall rules out relaxing that refusal, and this does not relax it -- it separates two states the
refusal had collapsed.

THE DISTINCTION IS OBSERVED, NOT SUPPLIED. The deploy that ordered the transition established the
tree's absence by listing its parent, but this process reads the root itself rather than taking a
caller's word about host state it can observe -- the operand would be a second account of a fact the
filesystem already holds. Emptiness is the test rather than "is there a .git": a directory holding
files but no repository is the rsync-era partial state, whose bytes are already wrong and whose
repair is not `git init`.

BOTH NEW WRITES ARE FAIL-CLOSED READINGS OF THE CLAIM THE ARM MAKES. The base ref is created with an
empty expected-old, which is git's "this ref must not exist", and the worktree moves by `checkout -b`,
which refuses a branch that already exists. If "nothing was here before" is false, the bootstrap
refuses rather than initializing over somebody's repository.

THE READBACK IS THE ONLY VERDICT. Every step can exit zero and leave a repository that is not at the
candidate -- exactly how the rsync-era tree was assembled -- so the outcome comes from a fresh
observation through repository_convergence_verdict, the same authority the ordinary path uses rather
than a bootstrap-specific notion of success. `before` and `after` are deliberately the same
observation: an empty root had no refs or worktrees to preserve, so the preservation comparison is
vacuous rather than skipped.

The branch is gunbc_repository.default_branch, not a literal "main" -- that authority's own header
refuses a sixth nickname for which repository this project is.

EVIDENCE IS WET, because nothing else can close this stall: its trigger asks for HEAD, index and
tracked worktree read back as one revision, and every step can exit zero without that holding. Two
rows in test.claim.repository_bootstrap_wet_witness_test, both PASS by execution:

  an empty root is bootstrapped and reads back as that revision
    -- HEAD at the candidate, `git diff --quiet HEAD` clean, tracked file holding the candidate bytes,
       all checked from OUTSIDE the transition rather than from its exit status
  a root holding content without a repository still refuses and is not initialized
    -- the discriminator: without it the first row would pass equally well had the refusal simply been
       deleted. The stray file is untouched and no .git appeared.

The fixture authors no answer: it builds a source repository with ordinary git and leaves the target
as a directory holding nothing; every fact the assertions read comes from the transition's own
observation chain.

WHAT THIS DOES NOT DO: the tree member still refuses absence with RepositoryBootstrapUnavailable.
Flipping that arm to Create belongs in its own commit now that the capability is proven; landing it
here would make a fresh host depend on a capability in the same change that introduced it.

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

Copy link
Copy Markdown
Contributor Author

CI note: the required-witnesses-floor red here is main's, not this branch's.

The failure is adjudication BLOCKING phase=declarations (7 finding(s)) — all CITED-DECLARATION-ABSENT in src/v2/lens/enforcement/complexity_contract_subject.dag, src/v2/lens/grounding.dag, src/v2/lens/synthesis.dag and src/v2/test/claim/construction_justification/outside_modeled_guarantee_witness_test.dag. This branch touches none of them; its diff is repository_convergence.dag plus a new wet witness module.

Those citations are required_capability / PolicyJudgmentRequired references on an OutsideModeledGuarantee carrier — a carrier whose purpose is to name a capability that does not exist. The declarations phase requires a cited declaration to resolve, so a DeclarationRef to a deliberately absent capability is indistinguishable from a typo. That needs a ruling from whoever owns construction_justification; guessing symbol names to make it green would erase the modelling question. The operator is taking main's repair separately.

Every branch cut from current main inherits this, so #10724 and #10732 carry it too.

@briansrls
briansrls merged commit 2f9e12f into main Sep 7, 2026
2 of 4 checks passed
@briansrls
briansrls deleted the fresh-host branch September 7, 2026 09:32
briansrls added a commit that referenced this pull request Sep 7, 2026
…nly the repository-convergence oneshot remains (#10724)

* A fresh host can be deployed: an empty repository root is bootstrapped to the candidate and read back as that revision

gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall declared this OutsideTheLadder
with a bounded population of one -- deployment_spec_srv1 at a fresh repository root -- and it is the
reason a new host could not be deployed at all. The tree member refuses with
RepositoryBootstrapUnavailable because a Create would leave a non-repository directory that
Git-native convergence then refuses forever, turning a recoverable absence into a permanent one.

THE PRE-STATE REFUSAL IS NOT RELAXED; ONE OF ITS CAUSES IS ROUTED. ConvergencePreStateUnobservable
fired for two states with opposite remedies: a repository whose refs cannot be read is BROKEN and the
remedy is a person, while a root with nothing in it is UNBORN and the remedy is to create the
repository. A non-empty root and an unobservable one still refuse with the identical typed cause. The
stall rules out relaxing that refusal, and this does not relax it -- it separates two states the
refusal had collapsed.

THE DISTINCTION IS OBSERVED, NOT SUPPLIED. The deploy that ordered the transition established the
tree's absence by listing its parent, but this process reads the root itself rather than taking a
caller's word about host state it can observe -- the operand would be a second account of a fact the
filesystem already holds. Emptiness is the test rather than "is there a .git": a directory holding
files but no repository is the rsync-era partial state, whose bytes are already wrong and whose
repair is not `git init`.

BOTH NEW WRITES ARE FAIL-CLOSED READINGS OF THE CLAIM THE ARM MAKES. The base ref is created with an
empty expected-old, which is git's "this ref must not exist", and the worktree moves by `checkout -b`,
which refuses a branch that already exists. If "nothing was here before" is false, the bootstrap
refuses rather than initializing over somebody's repository.

THE READBACK IS THE ONLY VERDICT. Every step can exit zero and leave a repository that is not at the
candidate -- exactly how the rsync-era tree was assembled -- so the outcome comes from a fresh
observation through repository_convergence_verdict, the same authority the ordinary path uses rather
than a bootstrap-specific notion of success. `before` and `after` are deliberately the same
observation: an empty root had no refs or worktrees to preserve, so the preservation comparison is
vacuous rather than skipped.

The branch is gunbc_repository.default_branch, not a literal "main" -- that authority's own header
refuses a sixth nickname for which repository this project is.

EVIDENCE IS WET, because nothing else can close this stall: its trigger asks for HEAD, index and
tracked worktree read back as one revision, and every step can exit zero without that holding. Two
rows in test.claim.repository_bootstrap_wet_witness_test, both PASS by execution:

  an empty root is bootstrapped and reads back as that revision
    -- HEAD at the candidate, `git diff --quiet HEAD` clean, tracked file holding the candidate bytes,
       all checked from OUTSIDE the transition rather than from its exit status
  a root holding content without a repository still refuses and is not initialized
    -- the discriminator: without it the first row would pass equally well had the refusal simply been
       deleted. The stray file is untouched and no .git appeared.

The fixture authors no answer: it builds a source repository with ordinary git and leaves the target
as a directory holding nothing; every fact the assertions read comes from the transition's own
observation chain.

WHAT THIS DOES NOT DO: the tree member still refuses absence with RepositoryBootstrapUnavailable.
Flipping that arm to Create belongs in its own commit now that the capability is proven; landing it
here would make a fresh host depend on a capability in the same change that introduced it.

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

* The publication member stops writing two programs to one unit name: only the repository-convergence oneshot remains

THE DEPLOY WROTE THE SAME UNIT FILE TWICE, WITH DIFFERENT PROGRAMS, ON EVERY RUN. The publication
member wrote the rsync tree-sync unit, installed it, reloaded systemd and restarted it; then wrote
the repository-convergence unit TO THE SAME PATH UNDER THE SAME UNIT NAME
(apply_repository_convergence_unit_write_step stages under spec.names.tree_sync_unit_name),
reloaded, and restarted it again. One name, two programs, sequentially, in a single deployment.

IT WAS ALREADY DEAD BY DECLARATION. gunbc.live_deploy.deployed_tree_scope binds
deployed_tree_repository_transition = GitNativeConvergence and repository_transition_admission
admits it, while the emitter kept emitting and running rsync anyway. That module's own header says a
PR comment is not a wall; the wall was declared and the route it named was never cut. This is the
cut.

WHAT REMAINS IS THE THIN ACTUATOR the re-scope asked for, carrying only what lies outside the
transition's authority: the directories, the environment file the convergence ExecStart reads, the
slice it joins, the receipts directory, one unit written, one daemon-reload, one restart as the
service user, and the readback. Sixteen steps to twelve, one program per unit. Repository existence,
empty-root discrimination, candidate installation, HEAD/index/tracked-byte agreement and refusal of
non-empty non-repositories stay owned by the transition proven wet in #10722; nothing here
re-observes or reinterprets them.

DELETED, all orphaned by the cut: apply_tree_sync_unit_write_step, emit_tree_sync_unit_doc,
live_deploy_tree_sync_unit_file, live_deploy_tree_sync_unit_for, and the GunbcSourceTree arm of
emit_artifact_upsert -- which was already unreachable, since that kind renders its own member
commands and is therefore excluded from the membership reconcile.

WITNESSES REPOINTED WHERE THE CLAIM SURVIVED, RETIRED ONLY WHERE THE SUBJECT DIED:
  - apply_script_and_tree_sync_unit_stay_within_ci_runner_grants -> apply_script_stays_within_ci_runner_grants.
    The grant boundary was the claim; the unit-byte pin was not. It is now STRICTLY STRONGER: `rsync`
    is forbidden anywhere in the emission rather than only in the spellings the old unit avoided.
  - the git-metadata-as-files wall keeps its positive control and its discriminating red; only the
    negative arm moves from the deleted unit to the whole apply script.
  - retired: the byte oracle over the rsync ExecStart, seven tree-sync decisions (principal, slice,
    environment file, ExecStart, oneshot, installable, description) and their two red controls. Each
    lost its subject, and a decision over a unit no producer emits is permanently green while reading
    as coverage of a route that does not run. The principal fact those decisions protected is
    asserted by repository_convergence_placement's own witnesses, over the unit that now runs.

EVIDENCE: apply closure resolves with zero diagnostics; emit_test 59/59; the emission oracle 31/33.
The two failures -- the_serve_unit_matches_the_deployed_bytes and
serve_unit_execstart_carries_admitted_entrypoint -- were MEASURED FAILING ON THE PRE-CUT TREE as
well, so they are pre-existing and not this change. They are deployed-bytes oracles, and the fleet
desired ref is currently pinned five commits behind main while main is red, which is enough on its
own to drift them.

RESIDUE, DECLARED RATHER THAN FIXED: the unit is still NAMED tree-sync. tree_sync_unit_name,
tree_sync_unit_path, tree_sync_env_path and tree_sync_restart_step_with_diagnosis now all name a unit
that runs repository convergence -- a section 3 nickname. Renaming a live systemd unit is a
member-lifecycle migration: the old unit must be stopped and removed on the host, which is its own
change with its own failure modes, and folding it in here would put two claims in one review.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Sep 7, 2026
…d to the candidate and read back as that revision (#10722)

gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall declared this OutsideTheLadder
with a bounded population of one -- deployment_spec_srv1 at a fresh repository root -- and it is the
reason a new host could not be deployed at all. The tree member refuses with
RepositoryBootstrapUnavailable because a Create would leave a non-repository directory that
Git-native convergence then refuses forever, turning a recoverable absence into a permanent one.

THE PRE-STATE REFUSAL IS NOT RELAXED; ONE OF ITS CAUSES IS ROUTED. ConvergencePreStateUnobservable
fired for two states with opposite remedies: a repository whose refs cannot be read is BROKEN and the
remedy is a person, while a root with nothing in it is UNBORN and the remedy is to create the
repository. A non-empty root and an unobservable one still refuse with the identical typed cause. The
stall rules out relaxing that refusal, and this does not relax it -- it separates two states the
refusal had collapsed.

THE DISTINCTION IS OBSERVED, NOT SUPPLIED. The deploy that ordered the transition established the
tree's absence by listing its parent, but this process reads the root itself rather than taking a
caller's word about host state it can observe -- the operand would be a second account of a fact the
filesystem already holds. Emptiness is the test rather than "is there a .git": a directory holding
files but no repository is the rsync-era partial state, whose bytes are already wrong and whose
repair is not `git init`.

BOTH NEW WRITES ARE FAIL-CLOSED READINGS OF THE CLAIM THE ARM MAKES. The base ref is created with an
empty expected-old, which is git's "this ref must not exist", and the worktree moves by `checkout -b`,
which refuses a branch that already exists. If "nothing was here before" is false, the bootstrap
refuses rather than initializing over somebody's repository.

THE READBACK IS THE ONLY VERDICT. Every step can exit zero and leave a repository that is not at the
candidate -- exactly how the rsync-era tree was assembled -- so the outcome comes from a fresh
observation through repository_convergence_verdict, the same authority the ordinary path uses rather
than a bootstrap-specific notion of success. `before` and `after` are deliberately the same
observation: an empty root had no refs or worktrees to preserve, so the preservation comparison is
vacuous rather than skipped.

The branch is gunbc_repository.default_branch, not a literal "main" -- that authority's own header
refuses a sixth nickname for which repository this project is.

EVIDENCE IS WET, because nothing else can close this stall: its trigger asks for HEAD, index and
tracked worktree read back as one revision, and every step can exit zero without that holding. Two
rows in test.claim.repository_bootstrap_wet_witness_test, both PASS by execution:

  an empty root is bootstrapped and reads back as that revision
    -- HEAD at the candidate, `git diff --quiet HEAD` clean, tracked file holding the candidate bytes,
       all checked from OUTSIDE the transition rather than from its exit status
  a root holding content without a repository still refuses and is not initialized
    -- the discriminator: without it the first row would pass equally well had the refusal simply been
       deleted. The stray file is untouched and no .git appeared.

The fixture authors no answer: it builds a source repository with ordinary git and leaves the target
as a directory holding nothing; every fact the assertions read comes from the transition's own
observation chain.

WHAT THIS DOES NOT DO: the tree member still refuses absence with RepositoryBootstrapUnavailable.
Flipping that arm to Create belongs in its own commit now that the capability is proven; landing it
here would make a fresh host depend on a capability in the same change that introduced it.


Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Sep 7, 2026
…nly the repository-convergence oneshot remains (#10724)

* A fresh host can be deployed: an empty repository root is bootstrapped to the candidate and read back as that revision

gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall declared this OutsideTheLadder
with a bounded population of one -- deployment_spec_srv1 at a fresh repository root -- and it is the
reason a new host could not be deployed at all. The tree member refuses with
RepositoryBootstrapUnavailable because a Create would leave a non-repository directory that
Git-native convergence then refuses forever, turning a recoverable absence into a permanent one.

THE PRE-STATE REFUSAL IS NOT RELAXED; ONE OF ITS CAUSES IS ROUTED. ConvergencePreStateUnobservable
fired for two states with opposite remedies: a repository whose refs cannot be read is BROKEN and the
remedy is a person, while a root with nothing in it is UNBORN and the remedy is to create the
repository. A non-empty root and an unobservable one still refuse with the identical typed cause. The
stall rules out relaxing that refusal, and this does not relax it -- it separates two states the
refusal had collapsed.

THE DISTINCTION IS OBSERVED, NOT SUPPLIED. The deploy that ordered the transition established the
tree's absence by listing its parent, but this process reads the root itself rather than taking a
caller's word about host state it can observe -- the operand would be a second account of a fact the
filesystem already holds. Emptiness is the test rather than "is there a .git": a directory holding
files but no repository is the rsync-era partial state, whose bytes are already wrong and whose
repair is not `git init`.

BOTH NEW WRITES ARE FAIL-CLOSED READINGS OF THE CLAIM THE ARM MAKES. The base ref is created with an
empty expected-old, which is git's "this ref must not exist", and the worktree moves by `checkout -b`,
which refuses a branch that already exists. If "nothing was here before" is false, the bootstrap
refuses rather than initializing over somebody's repository.

THE READBACK IS THE ONLY VERDICT. Every step can exit zero and leave a repository that is not at the
candidate -- exactly how the rsync-era tree was assembled -- so the outcome comes from a fresh
observation through repository_convergence_verdict, the same authority the ordinary path uses rather
than a bootstrap-specific notion of success. `before` and `after` are deliberately the same
observation: an empty root had no refs or worktrees to preserve, so the preservation comparison is
vacuous rather than skipped.

The branch is gunbc_repository.default_branch, not a literal "main" -- that authority's own header
refuses a sixth nickname for which repository this project is.

EVIDENCE IS WET, because nothing else can close this stall: its trigger asks for HEAD, index and
tracked worktree read back as one revision, and every step can exit zero without that holding. Two
rows in test.claim.repository_bootstrap_wet_witness_test, both PASS by execution:

  an empty root is bootstrapped and reads back as that revision
    -- HEAD at the candidate, `git diff --quiet HEAD` clean, tracked file holding the candidate bytes,
       all checked from OUTSIDE the transition rather than from its exit status
  a root holding content without a repository still refuses and is not initialized
    -- the discriminator: without it the first row would pass equally well had the refusal simply been
       deleted. The stray file is untouched and no .git appeared.

The fixture authors no answer: it builds a source repository with ordinary git and leaves the target
as a directory holding nothing; every fact the assertions read comes from the transition's own
observation chain.

WHAT THIS DOES NOT DO: the tree member still refuses absence with RepositoryBootstrapUnavailable.
Flipping that arm to Create belongs in its own commit now that the capability is proven; landing it
here would make a fresh host depend on a capability in the same change that introduced it.

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

* The publication member stops writing two programs to one unit name: only the repository-convergence oneshot remains

THE DEPLOY WROTE THE SAME UNIT FILE TWICE, WITH DIFFERENT PROGRAMS, ON EVERY RUN. The publication
member wrote the rsync tree-sync unit, installed it, reloaded systemd and restarted it; then wrote
the repository-convergence unit TO THE SAME PATH UNDER THE SAME UNIT NAME
(apply_repository_convergence_unit_write_step stages under spec.names.tree_sync_unit_name),
reloaded, and restarted it again. One name, two programs, sequentially, in a single deployment.

IT WAS ALREADY DEAD BY DECLARATION. gunbc.live_deploy.deployed_tree_scope binds
deployed_tree_repository_transition = GitNativeConvergence and repository_transition_admission
admits it, while the emitter kept emitting and running rsync anyway. That module's own header says a
PR comment is not a wall; the wall was declared and the route it named was never cut. This is the
cut.

WHAT REMAINS IS THE THIN ACTUATOR the re-scope asked for, carrying only what lies outside the
transition's authority: the directories, the environment file the convergence ExecStart reads, the
slice it joins, the receipts directory, one unit written, one daemon-reload, one restart as the
service user, and the readback. Sixteen steps to twelve, one program per unit. Repository existence,
empty-root discrimination, candidate installation, HEAD/index/tracked-byte agreement and refusal of
non-empty non-repositories stay owned by the transition proven wet in #10722; nothing here
re-observes or reinterprets them.

DELETED, all orphaned by the cut: apply_tree_sync_unit_write_step, emit_tree_sync_unit_doc,
live_deploy_tree_sync_unit_file, live_deploy_tree_sync_unit_for, and the GunbcSourceTree arm of
emit_artifact_upsert -- which was already unreachable, since that kind renders its own member
commands and is therefore excluded from the membership reconcile.

WITNESSES REPOINTED WHERE THE CLAIM SURVIVED, RETIRED ONLY WHERE THE SUBJECT DIED:
  - apply_script_and_tree_sync_unit_stay_within_ci_runner_grants -> apply_script_stays_within_ci_runner_grants.
    The grant boundary was the claim; the unit-byte pin was not. It is now STRICTLY STRONGER: `rsync`
    is forbidden anywhere in the emission rather than only in the spellings the old unit avoided.
  - the git-metadata-as-files wall keeps its positive control and its discriminating red; only the
    negative arm moves from the deleted unit to the whole apply script.
  - retired: the byte oracle over the rsync ExecStart, seven tree-sync decisions (principal, slice,
    environment file, ExecStart, oneshot, installable, description) and their two red controls. Each
    lost its subject, and a decision over a unit no producer emits is permanently green while reading
    as coverage of a route that does not run. The principal fact those decisions protected is
    asserted by repository_convergence_placement's own witnesses, over the unit that now runs.

EVIDENCE: apply closure resolves with zero diagnostics; emit_test 59/59; the emission oracle 31/33.
The two failures -- the_serve_unit_matches_the_deployed_bytes and
serve_unit_execstart_carries_admitted_entrypoint -- were MEASURED FAILING ON THE PRE-CUT TREE as
well, so they are pre-existing and not this change. They are deployed-bytes oracles, and the fleet
desired ref is currently pinned five commits behind main while main is red, which is enough on its
own to drift them.

RESIDUE, DECLARED RATHER THAN FIXED: the unit is still NAMED tree-sync. tree_sync_unit_name,
tree_sync_unit_path, tree_sync_env_path and tree_sync_restart_step_with_diagnosis now all name a unit
that runs repository convergence -- a section 3 nickname. Renaming a live systemd unit is a
member-lifecycle migration: the old unit must be stopped and removed on the host, which is its own
change with its own failure modes, and folding it in here would put two claims in one review.

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 7, 2026
…nary Create

#10722 built the Git-native bootstrap and proved it wet; #10724 made the publication member invoke
that oneshot and nothing else. But the tree member still refused absence with
RepositoryBootstrapUnavailable, so the decision was made BEFORE the transition could run and a fresh
host stayed undeployable. The capability had no invoker. That sequencing was deliberate -- a fresh
host must not depend on a capability introduced in the same change -- and this is the commit it was
deferred to.

THE ABSENT ARM NEEDS NO MEMBER SPECIAL CASE AT ALL now. decide_member collapses to
`Create { member, to: desired }` for every member; the tree is no longer singled out.

REPOSITORYBOOTSTRAPUNAVAILABLE IS DELETED, NOT LEFT UNREACHABLE, and the reason is what its text
said. It read: "the repository root is absent and no bootstrap transition exists". That sentence was
true when written and is now false. A refusal cause whose message denies a capability shipping in the
same tree is worse than dead code -- the next author reads it as a standing limitation.

THE WALL DID NOT DISAPPEAR, IT MOVED TO WHERE IT CAN BE CHECKED. A fresh host is no longer refused at
PLANNING time on a stale claim; it is refused at EXECUTION time by the transition, on observation: a
non-empty root refuses with the original pre-state cause, `checkout -b` refuses an existing branch, an
empty expected-old refuses an existing ref, and the readback refuses a repository that is not at the
candidate. Construction over declaration.

THE STALL CLIMBS ONE RUNG, NOT TWO. gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall
goes OutsideTheLadder -> Mitigatable. It is no longer outside the ladder: the operation has a route
and it executes. It is NOT MechanicallyPreventable, and the distinction is the enrolment of the
evidence rather than the quality of the wall -- the wet witnesses perform host effects, so the
hermetic floor refuses them at its boundary and counts them executed while they assert nothing.
Reporting MechanicallyPreventable would cite a wall that does not run (DESIGN 4b(1)). The next-rung
trigger is now exactly that: enrol those two rows in a lane that executes them.

EVIDENCE, and it is split into two discriminating halves rather than one end-to-end assertion:
  member_identity_witness an_absent_repository_plans_the_tree_publication
    -- absence is a Create whose plan publishes; a plan that still refused fails the first arm, and
       one that derived nothing for the tree fails the second, because a bootstrap planning no
       publication is the fresh host that installs nothing and reports success.
  emit_test the_publication_member_emits_convergence_and_no_rsync
    -- the publishing plan's tree fragment carries the convergence invocation and its readback, and
       no rsync. This is #10724's outstanding acceptance item: that the ACTUATOR reaches the
       transition, which is a different claim from the transition working.
A single end-to-end witness over a full-install plan would have passed for reasons neither half pins
down, because a wholesale install emits the same line whatever the observation was.

THE UNIT-NAME RESIDUE IS RECORDED, NOT ASSERTED AWAY. The first draft of that witness also asserted
`gunbc-tree-sync.service` is absent from the fragment. It is not: the convergence unit is still
WRITTEN TO the tree-sync path, the section 3 nickname #10724 declared as residue rather than repaired.
Asserting its absence would assert a member-lifecycle migration that has not happened.

Verified on srv1: apply closure resolves with zero diagnostics, member_identity 11/11, emit_test
59/59 plus the corrected actuator claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY
briansrls added a commit that referenced this pull request Sep 7, 2026
…nary Create (#10760)

#10722 built the Git-native bootstrap and proved it wet; #10724 made the publication member invoke
that oneshot and nothing else. But the tree member still refused absence with
RepositoryBootstrapUnavailable, so the decision was made BEFORE the transition could run and a fresh
host stayed undeployable. The capability had no invoker. That sequencing was deliberate -- a fresh
host must not depend on a capability introduced in the same change -- and this is the commit it was
deferred to.

THE ABSENT ARM NEEDS NO MEMBER SPECIAL CASE AT ALL now. decide_member collapses to
`Create { member, to: desired }` for every member; the tree is no longer singled out.

REPOSITORYBOOTSTRAPUNAVAILABLE IS DELETED, NOT LEFT UNREACHABLE, and the reason is what its text
said. It read: "the repository root is absent and no bootstrap transition exists". That sentence was
true when written and is now false. A refusal cause whose message denies a capability shipping in the
same tree is worse than dead code -- the next author reads it as a standing limitation.

THE WALL DID NOT DISAPPEAR, IT MOVED TO WHERE IT CAN BE CHECKED. A fresh host is no longer refused at
PLANNING time on a stale claim; it is refused at EXECUTION time by the transition, on observation: a
non-empty root refuses with the original pre-state cause, `checkout -b` refuses an existing branch, an
empty expected-old refuses an existing ref, and the readback refuses a repository that is not at the
candidate. Construction over declaration.

THE STALL CLIMBS ONE RUNG, NOT TWO. gunbc.guarantee_stall.deployed_repository_empty_root_bootstrap_stall
goes OutsideTheLadder -> Mitigatable. It is no longer outside the ladder: the operation has a route
and it executes. It is NOT MechanicallyPreventable, and the distinction is the enrolment of the
evidence rather than the quality of the wall -- the wet witnesses perform host effects, so the
hermetic floor refuses them at its boundary and counts them executed while they assert nothing.
Reporting MechanicallyPreventable would cite a wall that does not run (DESIGN 4b(1)). The next-rung
trigger is now exactly that: enrol those two rows in a lane that executes them.

EVIDENCE, and it is split into two discriminating halves rather than one end-to-end assertion:
  member_identity_witness an_absent_repository_plans_the_tree_publication
    -- absence is a Create whose plan publishes; a plan that still refused fails the first arm, and
       one that derived nothing for the tree fails the second, because a bootstrap planning no
       publication is the fresh host that installs nothing and reports success.
  emit_test the_publication_member_emits_convergence_and_no_rsync
    -- the publishing plan's tree fragment carries the convergence invocation and its readback, and
       no rsync. This is #10724's outstanding acceptance item: that the ACTUATOR reaches the
       transition, which is a different claim from the transition working.
A single end-to-end witness over a full-install plan would have passed for reasons neither half pins
down, because a wholesale install emits the same line whatever the observation was.

THE UNIT-NAME RESIDUE IS RECORDED, NOT ASSERTED AWAY. The first draft of that witness also asserted
`gunbc-tree-sync.service` is absent from the fragment. It is not: the convergence unit is still
WRITTEN TO the tree-sync path, the section 3 nickname #10724 declared as residue rather than repaired.
Asserting its absence would assert a member-lifecycle migration that has not happened.

Verified on srv1: apply closure resolves with zero diagnostics, member_identity 11/11, emit_test
59/59 plus the corrected actuator claim.


Claude-Session: https://claude.ai/code/session_019iXPtAqzPaNBZbaaM5rxrY

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant