Skip to content

Widen the deployed-tree exclusions to the host-state prefixes rsync was shipping - #8522

Merged
briansrls merged 3 commits into
mainfrom
session/nimble-fox-671-deployscope
Aug 19, 2026
Merged

briansrls merged 3 commits into
mainfrom
session/nimble-fox-671-deployscope

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

What happened

An operator deploy of srv1 (live_deploy_apply_srv1_operator_wet) refused at admission:

CandidateCheckoutNotClean: the deploying checkout's working tree does not equal HEAD, and the
source sync ships the working tree — so this deploy would install bytes the revision does not
name and every later comparison would agree with itself.
staged=(none) tracked=(none) untracked-deployed=(none)
ignored-deployed=.claude/settings.local.json, .claude/worktrees/agent-a08ba29cba32fef1b/, …×9

The refusal is correct and fired before any mutation, exactly as
live_deploy_candidate_observed_at_launcher_note promises. Nothing was touched.

Why the fix is a widening rather than a cleanup

The working-tree leg of the source sync is rsync, which does not read .gitignore. An ignored
path outside deployed_tree_excluded_prefixes is therefore copied to the host under a revision
that does not name it. candidate.dag already names the two remedies for that arm — widen the
exclusions, or remove the file
— and already listed .claude/ as a known member of the class.

Four prefixes take the first remedy because they are host state by construction: agent
session state and local tool/build caches that no revision ever names. Shipping one is never the
intent, so refusing on one is pure friction.

deployed_tree_excluded_prefixes goes ["target", ".git"] → + .claude, .gunbc, target-codex, .cargo-home.

What deliberately stays refused

The .env family. The repo ignores .env, .env.local, .env.*.local. The third is a
wildcard, and both path_is_deployed and rsync's --exclude /<p> match a literal segment.
Listing the two spellable forms would silence admission on them while .env.*.local kept
shipping — partial cover on the one class where the shipped bytes are secrets, strictly worse
than the present loud refusal on all three.

src/v2/test/claim/manual/sg2_scratch_probe.dag, for a different reason: it sits inside a
source root the graph reads, so excluding it would change what the host compiles, not merely
what it stores.

Not derived from gunbc.gitignore_authority, on purpose

That authority answers must not be tracked; this one answers must not be shipped, and the
sets genuinely differ — a tracked file ships and is not ignored. Deriving would also let an
ordinary .gitignore edit silently change what a deploy installs, and would put an import edge
into a module held leaf for the acyclicity reason its own header states. Flagging it as a
question rather than acting on it.

Witnesses

dag/test/claim/live_deploy/candidate_checkout_witness_test.dag, 21 → 24 claims:

  • the existing one-home pin moves to the six-prefix list (it pinned == 2 / "target,.git")
  • the observed blocker as a claim — the exact .claude paths that refused
  • a control that the residue stays loud — .env, .env.local, .env.production.local and
    the scratch probe must still read as deployed, so a later widening cannot silence them unnoticed
  • segment matching over the appended dotted prefixes — .claudex/, dag/.claude/,
    target-codexish/ are the easy regression into a character run

Scope

Prose in candidate.dag that read "the two prefixes deployed_tree_scope names" is updated in the
same commit; leaving it would be the stale-citation class §3 names.

…as shipping

An operator deploy of srv1 refused at admission with CandidateCheckoutNotClean,
naming ten ignored paths it would otherwise have installed: .claude/settings.local.json
and nine stale .claude/worktrees/ directories. The refusal is correct and fired before
any mutation, exactly as live_deploy_candidate_observed_at_launcher_note promises.

The working-tree leg of the source sync is rsync, which does not read .gitignore, so an
ignored path outside deployed_tree_excluded_prefixes is copied to the host under a
revision that does not name it. candidate.dag already names the two remedies for that
arm -- widen the exclusions, or remove the file -- and already listed .claude/ as a
known member of the class.

Four prefixes take the first remedy because they are host state by construction: agent
session state and local tool/build caches that no revision ever names, so shipping one
is never the intent and refusing on one is pure friction.

The .env family deliberately stays refused. The repository ignores .env, .env.local and
.env.*.local; the third is a wildcard, and both path_is_deployed and rsync's
--exclude /<p> match a literal segment. Listing the two spellable forms would silence
admission on them while .env.*.local kept shipping -- partial cover on the one class
where the shipped bytes are secrets, which is strictly worse than the present loud
refusal on all three. src/v2/test/claim/manual/sg2_scratch_probe.dag stays refused for a
different reason: it sits inside a source root the graph reads, so excluding it would
change what the host compiles rather than merely what it stores.

Not derived from gunbc.gitignore_authority, on purpose: that authority answers "must not
be tracked" and this one answers "must not be shipped", and the sets genuinely differ --
a tracked file ships and is not ignored. Deriving would also let an ordinary .gitignore
edit silently change what a deploy installs, and would put an import edge into a module
held leaf for the acyclicity reason its own header states.

Witnesses: the existing one-home pin moves to the six-prefix list, and three claims are
added -- the observed blocker as a claim, a control asserting the deliberately-excluded
residue still reads as deployed (so a later widening cannot silence it unnoticed), and
segment-matching over the appended dotted prefixes, where .claudex/ and target-codexish/
are the easy regression into a character run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title srv1 daily workspace still doesn't work at all Widen the deployed-tree exclusions to the host-state prefixes rsync was shipping Aug 19, 2026
the_ignored_scan_keeps_deployed_paths_and_drops_excluded_ones asserted
`.gunbc/ledger.json` on the KEPT side, which was correct while the exclusion set was
["target", ".git"] and is wrong now that local tool state is excluded. Executed against
the widened set it was the one red in the module, which is the pin doing its job.

The claim's purpose is unchanged and slightly sharpened: the kept side is now exactly the
residue the exclusion set deliberately does not cover -- the .env family, whose wildcard
spelling no prefix can express, and the probe inside a read source root -- and
.claude/settings.local.json joins the dropped side so the observed blocker appears in the
both-directions claim too, not only in its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 19, 2026 06:01
@gunbai-bot

gunbai-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Validated by execution before flipping out of draft: claim_batch --entry dag/test/claim/live_deploy/candidate_checkout_witness_test.dag --functions <all 24> on srv1 (aarch64) at e63c4ed7d0 — 24/24 PASS, 0 FAIL.

The first run at bd8da1ac7b had exactly one red, and it is worth recording because it is the one-home pin doing its job rather than a test needing appeasement: the_ignored_scan_keeps_deployed_paths_and_drops_excluded_ones asserted .gunbc/ledger.json on the KEPT side, correct under ["target", ".git"] and wrong once local tool state is excluded. Second commit moves it to the dropped side and adds .claude/settings.local.json beside it, so the observed blocker appears in the both-directions claim too.

Provenance of the subject: the refusal quoted in the description is from a real operator deploy attempt against srv1, not a constructed fixture — it fired before any mutation and the host was left untouched.

…me the claim it falsifies

The required floor caught the real consequence this PR has outside its own module:
deployed_tree_rsync_exclude_flags() renders one `--exclude /<p>` per excluded prefix
directly into gunbc-srv1-green-tree-sync.service's first ExecStart, so widening the list
from two prefixes to six changes emitted unit bytes that
live_deploy_unit_emission_oracle_witness pins. That pin firing is the oracle doing its
job -- the emitted units are exactly what a change to the exclusion set must not move
silently.

The expectation is updated, and so is the claim's NAME, because after this change
`..._matches_the_deployed_bytes` is literally false: the host still carries the two-prefix
spelling and will carry the six-prefix one only after the next deploy of that slot. A
claim asserting agreement with deployed bytes while deliberately disagreeing with them is
the named-for-X-claims-Y shape, so it is now
`..._matches_the_deployed_bytes_plus_the_exclusion_widening` with the delta, its
derivation and its closing condition recorded on the row.

The delta is derived rather than transcribed, which is the distinction that keeps this a
declared divergence instead of a blind regeneration: the base bytes remain the 2026-08-19
srv1 transcription, and the one field that moves is the one the exclusion list computes.
The second ExecStart is deliberately untouched -- the .git leg carries its own
include/exclude set and does not read the list -- so its bytes staying identical is the
control proving the widening reached the leg it should and no other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

CI red fixed at 397359fcc2, and the failure was a real consequence of this change reaching outside its own module rather than a flake.

What the floor caught. deployed_tree_rsync_exclude_flags() renders one --exclude /<p> per prefix directly into gunbc-srv1-green-tree-sync.service's first ExecStart, and those bytes are pinned by live_deploy_unit_emission_oracle_witness. Two prefixes to six changed emitted unit bytes, so the_tree_sync_unit_matches_the_deployed_bytes went red. That is the oracle doing exactly its job: a change to the exclusion set must not move deployed unit bytes silently. The floor run was passed=8831 known_red_held=306 failed=1 — this was the only red in 9,138 claims.

Two fixes, because the name was also wrong. After this change ..._matches_the_deployed_bytes is literally false: the host carries the two-prefix spelling and reconverges only after the next deploy of that slot. A claim asserting agreement with deployed bytes while deliberately disagreeing with them is the named-for-X-claims-Y shape, so it is now ..._matches_the_deployed_bytes_plus_the_exclusion_widening, with the delta, its derivation and its closing condition on the row. No roster referenced the old name.

Why this is a declared divergence, not a blind re-pin. The base bytes remain the independent 2026-08-19 srv1 transcription; the one field that moves is the one the exclusion list computes. The second ExecStart is untouched on purpose — the .git leg carries its own include/exclude set and does not read the list — so its bytes staying byte-identical is the control proving the widening reached the leg it should and no other.

Validated by execution on srv1 (aarch64) at 397359fcc2 before pushing: oracle module 6/6 PASS, candidate module 24/24 PASS, zero FAIL.

— sent from nimble-fox-671

@briansrls
briansrls merged commit e8b3e01 into main Aug 19, 2026
1 check passed
@briansrls
briansrls deleted the session/nimble-fox-671-deployscope branch August 19, 2026 18:19
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…8567)

main is red on test.claim.live_deploy.emit witness_scripts_stay_within_ci_runner_grants,
and the cause is mine. That claim pins the tree-sync ExecStart to the exact two-prefix
spelling `--exclude /target --exclude /.git`, which #8522 widened to six prefixes.

WHY #8522 WAS GREEN ANYWAY, WHICH IS THE PART WORTH RECORDING. Two witnesses assert those
same emitted bytes: live_deploy_unit_emission_oracle_witness, which #8522 updated because
the floor caught it, and this one, which was NOT enrolled at the time and therefore could
not be caught. #8503 restored 14 modules and 226 witness identities, this claim among
them, and it landed straight onto the already-merged change. So the red is not #8503's
defect -- the restoration did exactly what restoring evidence is for, which is to make a
previously invisible breakage observable.

The lesson is the one about denominators: I fixed the pin the floor showed me and inferred
from a green run that no other pin existed, when the correct question was which OTHER
claims assert these bytes. A dormant witness is not a passing witness, and a green floor
says nothing about the claims it never ran.

Fix is the same delta as the oracle row: the pattern gains the four host-state prefixes.
The surrounding claim is unchanged and still does its real job -- apply and retract scripts
must stay inside the ci-runner grants (no chown, no rmdir, no rsync outside the unit) --
and that half never depended on the exclusion list.

Not fixed here, and not mine: v2.test.lens_inert_carrier.inert_carrier_test
inert_carrier_no_unrostered_or_stale is the other red on main. It fails on main without
this branch's content, and a rebased run carrying an added prose row reported the same two
failures rather than three, so that one is independent of both #8522 and #8562.

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>
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…d literal

#8567 fixed the same red on the same line by hand-transcribing #8522's six
prefixes into the pattern. That restores the parallel representation this
branch removes: the literal is a second copy of
gunbc.live_deploy.deployed_tree_scope deployed_tree_excluded_prefixes, and it
rotted once already — that rot is exactly the red both PRs are answering.

Resolved to this branch's side, which derives the flags from that authority, so
the seventh prefix cannot desync the witness at all. The conjunct's real job is
unchanged and every other conjunct stays literal.
briansrls pushed a commit that referenced this pull request Aug 19, 2026
…8571)

Both failures are latent defects the quarantine was hiding, not defects in
#8503: the two witnesses sat under long/ while main moved out from under
their assertions, and un-quarantining them made the divergence executable.

1. test.claim.live_deploy.emit.witness_scripts_stay_within_ci_runner_grants
   pinned the tree-sync ExecStart with the two-prefix exclude list. #8522
   widened deployed_tree_excluded_prefixes to six prefixes and updated the
   two witnesses that were visible at the time (candidate_checkout_witness,
   live_deploy_unit_emission_oracle_witness); this one was in long/ and was
   not. The pin now transcribes the current six-prefix ExecStart, matching
   its two siblings.

2. v2.test.lens_inert_carrier.inert_carrier_test.inert_carrier_no_unrostered_or_stale
   #8413 landed the compute-fabric carriers; two of them — Attempt
   (product.fabric.execution) and Offer (product.fabric.supply) — are
   referenced only from fabric_terminal_contract_witness_test, so the live
   inert set went to 14 against a 12-row roster. Both get a roster row
   naming the consumer they await, under the existing row dissolution.

Measured, not inferred: an independent reimplementation of
compute_inert_carrier_data over the same corpus reproduces the 12 rostered
names exactly and adds precisely Attempt and Offer.

Verified by execution (claim_batch, both entries, this tree):
  src/v2/lens/inert_carrier_test.dag           4 PASS, 0 FAIL
  dag/test/claim/live_deploy/emit_test.dag    47 PASS, 0 FAIL
The RED control for both is main itself: run 32290602208 fails exactly
these two identities and nothing else (planned=9683 executed=9683 failed=2).

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 19, 2026
#8569)

`witness_scripts_stay_within_ci_runner_grants` transcribed the rendered
ExecStart line by hand, including `--exclude /target --exclude /.git`.
#8522 widened `deployed_tree_excluded_prefixes` to six entries while this
witness was quarantined to `long/`, so the literal went false the moment
the witness resumed executing — main is red on it at 43693a8.

Re-transcribing today's six flags would green it and re-arm the identical
rot. The conjunct now reads `deployed_tree_rsync_exclude_flags()`, the same
authority the emitter interpolates, so the exclusion set stops being a
second representation of a fact the model already carries.

The grant property keeps its teeth: no chown, no rmdir, no `rsync -a`, no
`--include=/config`, and the apply script must contain the unit verbatim —
all still asserted literally, because those are what the witness forbids
rather than what the authority derives.

Two executed controls:

- planting the emitter to drop the exclude flags reds this witness
  (FAIL witness_scripts_stay_within_ci_runner_grants), so the derived
  conjunct still discriminates;
- planting a seventh prefix on the authority reds
  `the_rsync_flags_derive_from_the_same_exclusion_set_the_filter_reads`
  in candidate_checkout_witness_test, which pins the exclusion set's
  content literally and is not quarantined — so deriving here removes a
  duplicate representation without removing coverage of the list itself.

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.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