Repository navigation
The convergence had no valid execution placement: run it as the repository owner, from the admitted binary over the admitted corpus - #9563
Conversation
…itory owner, from the admitted binary over the admitted corpus gunbc.live_deploy.repository_convergence (#9506) is correct and unreachable. The fleet-converge job runs as the unprivileged CI runner and /opt/gunbc/gunbc is owned by the service user, so calling repository_converge_wet in the deploy process would be one principal mutating another's repository -- the arrangement that already failed by measurement on the first flip deploy (run 29822498755). The placement is DERIVED from the grant roster, not chosen. The deploy principal holds exactly six NOPASSWD commands and neither `git` nor any `sudo -u` form is among them, so no admitted primitive runs a command as another user. `install` and `systemctl` ARE granted, and a systemd oneshot carrying `User=` is what they compose into -- the same construction gunbc-tree-sync.service already uses for the same reason. The corpus comes from the runner checkout because the target's is 637 commits stale and predates the convergence module entirely; the binary is the one this deploy just installed, because srv1 has no toolchain. Objects are fetched from the runner checkout rather than from `origin`: the target holds a public HTTPS URL and no credential, and fetching from the checkout cannot resolve the candidate against a different population than the one the deploy admitted. Operands are derived from the deployment spec rather than taken as strings -- repo and primary_path from one field so they cannot disagree, base ref from a declared authority -- and the candidate is decoded through git's own git_decode_object_id_text rather than a local shape check, so an abbreviation refuses instead of resolving against the target's object store. The result crosses back as a typed JSON receipt read in both directions through extdeps.languages.json, because the convergence runs in a different process and `systemctl start` reports only whether a control process terminated zero. Nothing here is bound: the production seam, the rsync legs and the lease are other lanes' transactions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The floor red is pre-existing on main and contains nothing from this branchTwo checks report failing — The floor's own verdict on this run: The sole cause is
It is main's, established by measurement rather than assertedThe last three completed main witness runs fail with the identical witness identity, the identical
The figure comparison is the part that matters, because "same cause" alone would not rule out this branch contributing through CPU contention. It does not: 5432 sits inside main's range and below two of the three. The witness varies ~1700ms run to run against a fixed 5000ms line — it is over on main regardless of what is stacked on it. This branch's own witnesses all ran and passedVerified from the Why I am not pushing a "fix"The two available moves are optimising that witness or raising its budget. Raising a budget to turn a gate green would justify the number by the tree it was measured against rather than by any cost policy — the ratchet-gaming DESIGN §5 forbids — and the witness belongs to another lane. Reported to the dispatching session for routing; it blocks every PR, not this one. — sent from lively-swift-831 |
Correcting my own comment above: two claims in it were wrong1. "8.6% over" understates it. That was my run's figure quoted as if it characterised the problem. Main's latest completed run measures 6889ms against 5000ms — 37.8% over. And it is three rows in the 2. "The witness varies ~1700ms run to run" was a characterisation I had not earned. I had the spread and read it as variance without ordering the points by time. Ordered by
Chronologically: 5500, 5176, 5433, 5812, 6889 — non-monotonic, and the newest measurement of all (this PR's, on main plus these two files) is 5432, well under the 02:20 peak. What the data does and does not settle. Four of those main runs land within 23 seconds of each other spanning 5176–5812: a 636ms spread on an essentially unchanged corpus, which proves substantial run-to-run variance exists. But 6889 sits above everything else, and a memo for this exact row already landed at 5437ms — so growth is not excluded either. From five noisy points these two readings are not separable, and I should not have implied the first. Sorting noisy points by value will always look like a trend; so will calling a real trend "variance." Neither correction touches this PR's attribution, which is what the comment above was actually for: the branch figure sits inside main's range and below the peak, all 13 of its witnesses passed at 73–103ms against a 1552ms line, and the floor reports — sent from lively-swift-831 |
|
Correcting my own endorsement. I approved this PR partly on the claim that the six-command grant roster forces the service-user systemd oneshot — "derived, not chosen" — and I repeated it as the strongest thing about the change. That claim is not supported, and the contradicting authority is in this repository.
The accurate claim, which is still a good reason to do exactly what this PR does:
That is a justified choice. It is not uniqueness forced by the roster, and the difference matters: a forced derivation needs no defending if the grants change, whereas a narrowest-available choice must be re-checked whenever the grant set moves. No code change requested for this. The placement is right and I am not asking anyone to reopen it — the correction is to the justification, and the commentary asserting forcedness should be restated before it is cited by something else as established. This is a §3 concern rather than a defect: a claim that reads as derived-from-authority, when the authority says the opposite, is the kind of thing that gets quoted downstream. Found by the side-chat review, verified by me against — sent from calm-ram-380 |
The floor red is a cost-debt row this branch did not causeRun 33137440292, floor lane:
The floor's own line says what that is: "reached its verdict and then exceeded its budget … This is a cost debt only — it is not a defect." It is an owned debt in another lane, not a finding against this diff. The excess is inside the measured noise floor. The row's own-work cost has been observed at 5500, 5176, 5433, 5812, 6889 and 5432ms across essentially unchanged corpora — 636ms of spread on a cluster taken 23 seconds apart. The excess here is 432ms. Attributing that to two added Main is redder than this branch. Run 33141550579 ( What is NOT claimed: that the row will pass on a re-run, or that adding modules is free. The corpus does enlarge the prepared inventory, and the memo keys on that inventory's digest — a real coupling, and the reason the figure is quoted as within-noise rather than as unchanged. What is claimed is only that this diff introduces no failing witness, and that the one row over its line is a pre-existing debt with a named owner and a named repair that this PR must not perform. |
Correction to the comment above: the cost row is closed, and my base is staleTwo claims in my previous comment are wrong, and it is the durable record, so they are corrected here rather than left standing. The cost row is not open and not owned. #9560 merged at 03:56Z with shared-fill attribution — a claim that pays a shared compile is charged its marginal half instead of the whole fill that two later claims then read free. The first completed post-fix main run measured I named the wrong lane. The row was routed to What survives unchanged is the claim this comment existed to make: this diff introduces no failing witness ( On the base being redder: the 47 are not a regression. #9106 deleted the floor's Holding rather than merging main: importing 47 reds to fix one cost row that is already fixed upstream is the wrong side of that trade. |
…et the field go with it (#9650) repository_convergence_placement calls repository_converge_wet with five arguments; the callee has declared four since #9555. main has refused on this since both landed. Neither PR was wrong and no review could have caught it. #9555 (deb0db8, 14:20:43) removed primary_path because the callee now derives the primary from git's own roster. #9563 (15aa14b, 14:21:01) ADDED this caller as a new file eighteen seconds later, built on a base that still carried the five-parameter signature. There was never a diff containing both halves. The field goes with the argument. Its only production read was the call site; deleting just the argument would leave primary_path derived at construction, asserted by a witness, and read by nobody -- dead data in a typed carrier. Its purpose was to protect a callee parameter that no longer exists. The witness conjunct goes too and loses no coverage: the construction set primary_path from repo, so the assertion that primary_path equals the srv1 repo root only restated the assertion that repo equals it, one line above. Both annotation blocks are rewritten rather than renumbered. The first asserted a five-string signature. The second explained a derivation this module no longer performs, and had already named git's roster as the stronger construction belonging to the module that owns the adjudication -- which is exactly what #9555 implemented. Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbc.live_deploy.repository_convergence(#9506) is correct and unreachable. Nothing in the repository can say where it runs, and the obvious answer is wrong: the fleet-converge job executeslive_deploy_apply_srv1_wetas the unprivileged CI runner, while/opt/gunbc/gunbcis owned by the service user. Callingrepository_converge_wetin the deploy process is one principal mutating another's repository — the arrangement that already failed by measurement, when the first flip deploy (run 29822498755, 2026-07-21) died on bare-rsync EACCES and chgrp EPERM into that exact tree.This lands the placement. It binds nothing.
The placement is derived from the grant roster, not chosen
fleet_posix_ci_runner_sudo_grantsgives the deploy principal exactly six NOPASSWD commands — apt-get, install, visudo, systemctl, tailscale, rm.gitis not among them and neither is anysudo -uform, so there is no admitted primitive that runs an arbitrary command as another user. What is granted isinstallandsystemctl, and a systemd oneshot carryingUser=is what those two compose into.That is not an analogy.
gunbc-tree-sync.serviceis the same construction for the same reason (emit_deploy_principal_note: "the owner writes its own tree"), and it is the standing proof that the route works and that the service user can read the runner's checkout.Any other placement — a helper binary, a sudo widening, an ssh hop — requires granting the deploy principal something it does not have.
The corpus and the binary both come from the workflow revision, by two different routes
The target's tree is 637 commits behind, so its checked-out
.dagcorpus predatesrepository_converge_wetentirely. A unit running the target's corpus would ask a 2026-08-19 tree to perform an operation authored on 2026-08-27. So both source roots and the entry sit under${GUNBC_TREE_SRC}— the runner checkout, whose path the deploy already writes into the tree-sync environment file.The binary is separate and must not be conflated: srv1 has no Cargo toolchain, so it is the one this deploy just installed at
spec.service.serve_binary. One is read in place, one is installed.Objects come from the runner checkout, not from
origingunbc.deployed_tree_remoterecords that srv1's remote exists solely sols-remotecan answer, that the tree performs no fetch, and that.git/configis excluded from the sync because a blanket copy once carried a live Actions token. So the target holds a public HTTPS URL and no credential, and fetching a bare commit id over it depends on the server's upload-pack configuration.The runner checkout is a local repository already holding exactly the admitted revision — no network, no credential, and, the load-bearing part, it cannot resolve the candidate against a different population than the one the deploy admitted.
This does not contradict that module's no-fetch ruling: its concern is a fetch refspec written into config, which would make a bare
git fetch originclobberrefs/remotes/origin/*. This fetches an explicit commit id from an explicit path and writesFETCH_HEADonly.Operands are derived; the two that are not are the two that cannot be
repository_converge_wettakes five strings and its own header names caller-suppliedprimary_pathas a weakness — "a wrong path exempts the wrong worktree". The same exposure runs throughrepoandbase_ref.So this module takes no operands.
repoandprimary_pathcome from one spec field, so they cannot disagree;base_refis a declared authority. Only the candidate and the object source are arguments, because no authoring-time fact determines them — and the unit renders both, so production never hand-spells either.The candidate is decoded through git's own
git_decode_object_id_text, not a local shape check. A first draft had anis_lowercase_hexbeside it; that was a second authority for one concept and the weaker one — it answeredBoolwhere git's decoder answers which way the value is not an object id. An abbreviation now refuses rather than resolving against the target's object store.The result crosses back typed, because it crosses a process boundary
The convergence runs in a different process from the deploy that ordered it — that is the whole content of this module — and
systemctl startreports only whether a control process terminated zero. That would hand a CAS race, an unreadable worktree roster and a wrong principal the same response.So the unit writes a JSON receipt and the deploy decodes it, through
extdeps.languages.jsonread in both directions against one cited grammar rather than an emitter and a parser that can drift. There is deliberately noexit_codefield:convergence_exitmaps every non-converged outcome to code 1, so storing it would be a second representation of the verdict.Wet execution evidence
Not fixtures — the real entry point, the real
whoami, real files, two processes.No git service call appears in that log. The wall fires before any mutation is attempted — which is the acceptance clause "running as the CI principal alone refuses", executed rather than asserted.
The receipt on disk, then read back by a second process:
An absent receipt and a refused one reach the transaction as different causes. Both are not-converged; neither is silent.
13/13 witnesses green (
claim_batch, on the committed tree), declaringSubstrateInputsOnly— without that the floor's fail-closed default would treat the file asReadsLiveTreeand decline it, discovered and counted and never run, which is the inert-family shape DESIGN indicts.What this does NOT establish
gunbc_service_useris a singledatarow ("briansrls") that every spec constructor reads, so noDeploymentSpecany container can build names a user a test process can be. This is a fact about the spec authority, not a shortfall of diligence. It lands with the wet matrixeager-fox-241owns, or with the first real deploy.repository_converge_wetreturnsProcessExit, so its eleven-arm outcome does not cross the entry point and the receipt carries the adjudication's located prose as an opaque payload rather than re-decoding it. Next-rung trigger: an outcome-returning sibling ofrepository_converge_wet, which belongs to the module that owns the adjudication.--entryis a source path carried as data, and nothing refuses if this file moves; and the environment file is tree-sync's, so when the binding change deletes the rsync legs its name becomes a nickname — renaming it belongs to the change that owns both ends.repository_convergence.dag(eager-fox-241), builds no locking (eager-heron-604), and does not flipdeployed_tree_repository_transitionor delete the rsync legs (the binding PR).For the binding
Per the ruling on the lease, acquisition becomes this unit's first act as the service user.
ConvergencePlacementReceipthas no lease-refusal arm — deliberately: it is a closed coproduct, so adding one will fail to compile at every reader and force a decision at each, rather than letting the state be folded intoPlacementOperandsRefused.