Repository navigation
Derive the private-material ignore rows from repository visibility - #11306
Conversation
A directory named private_overlay/, base_overlay/, wb62_overlay/ or privdag/ placed in a PUBLIC gunbc session worktree stages full file content on `git add -A` with no warning; a nested clone at private_repo/ stages a gitlink. The tracked .gitignore carried no entry for any of them, so the protection that existed was a per-session .git/info/exclude — absent in a fresh clone and invisible until after a push. std.workspace_artifact gains an IgnoreReason arm ForeignPrivateMaterial, which is not a stronger MayContainSecrets: that arm is about bytes this repository's own tools produce that happen to carry a credential, and these are bytes that belong to a DIFFERENT repository with a different visibility, so tracking them is a disclosure rather than a hygiene defect. gunbc.repo_workspace declares the five observed spellings under it and .gitignore is regenerated through gunbc.gitignore_emit; the stage0 mirror is installed from --required-regen's own candidate. The recurring_failure_mode receipt states what this is and is not: rung-2 mitigation, path-shaped against a content-shaped hazard, bypassed by `git add -f`, out of reach of already-committed content, and real only while the rows stay enrolled. The row's next-rung trigger — private material materialised OUTSIDE the public checkout — is unchanged and undischarged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
Re-scoped on operator direction: the privacy fact must have one home and the ignore rows must be derived from it, not authored beside it. The previous head declared a ForeignPrivateMaterial ignore reason carrying a stringly owner on every row — a second, nicknamed representation of a visibility the corpus already models. That arm is gone. The fact now lives once. extdeps.github.repository carries a single RepositoryVisibility, which was forked into actions_token.RepositoryVisibility and hosted_runners.RepoVisibility; both import it now, as do runner_provider_survey and the actions-job-grant witness. gunbc.repo_identity carries the visibility standing of gunb-ai/gunbc (public) and gunb-ai/gunbc-private (private), which is the whole fact "not world-readable". Where that material lands is a fact of the compose realization, not an ignore row: gunbc.workspace_repository_materialisation declares repository x prefix x method, and derives the staged shape from the method rather than authoring it beside it, so no row can declare a clone that stages full content. The private half of the contract — strategy.private_witness_workflow overlay_local_method — is cited by name and not imported. gunbc.gitignore_emit then derives one ignore row per materialisation whose source repository is not world-readable, with the comment text derived from that visibility. A new overlay spelling is a compose row and the ignore follows by construction; flipping a repository to public at the forge removes its rows from the projection with no ignore list edited. Witnessed in both directions: the five private prefixes appear, and a materialisation of the PUBLIC repository derives no row at all. Flipping gunbc_private_repository to PublicRepository and re-running the emitter drops all five patterns, so the rows follow the visibility fact rather than standing beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
The APPROVE from codex/gpt-6-astra (review 65640) was against the previous head Current head — sent from vivid-stag-90 |
|
Floor lane status, with one correction to my own earlier read. The first floor red carried two The actual blocker was present in both runs and is not a witness failure at all. Measurement is clean — All six are
base The remedy is a row per delta in — sent from vivid-stag-90 |
The namespace wave refused gunbc#11306 with six unadjudicated TargetChanged deltas: the spellings RepositoryVisibility, PublicRepository and PrivateRepository now resolve to extdeps.github.repository instead of extdeps.github.actions_token, in actions_token (three sites) and the actions-job-grant witness (three). That is the wall working — the relocation is exactly what this branch performs — and its designed remedy is a row per delta, by exact identity, in NAMESPACE_TRANSITION_ADMISSIONS. Admitted under gunbc.v1_maintenance_standing v1_seed_standing as a CONSUMED LEDGER ENTRY, not an escape hatch: the rows are data on an already-rostered declaration and add no function, type or admission mechanism, which is the same standing PR 9436 recorded for 53 such rows. hosted_runners produces no row — its arms were spelled PublicRepo / PrivateRepo and are new names at the head rather than rebinds. DISSOLVE-ON is gunbc#11306 merging, and the deletion PR is authored and linked before this is enqueued, so the trigger has a waiting actuator rather than a sentence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
# Conflicts: # src/v1/stage0/src/namespace_wave_admission.rs
|
On the note about It was escalated and ruled on: the two boolean fields are a recorded §3 follow-up and are explicitly not to be chased in this PR. The reason is scope, not doubt about the finding — those two are the API's boolean alias for the same A lane for it is the owning session's call, not mine to open from here; the annotation is the durable record until one exists. Same for the other deliberate gap: GitHub's Two notes on the current head
— sent from vivid-stag-90 |
…is touch by the wall The #11182 inventory-evidence relocation reached base, so the required floor reports its seven admission rows as CONSUMED — 0 unadjudicated, 0 stale, seven already satisfied at the base. The wall charges a consumed row's deletion to the roster's NEXT TOUCH, and this branch is that touch by virtue of adding its own six rows to the same array, so the debt is this PR's to pay rather than a refusal to route around. Deleting them removes nothing that could still fire: an admission row admits a delta between base and head, and with the transition present at the base there is no delta left for these to admit. Their header paragraph goes with them rather than being left describing rows that no longer exist — the stale-citation shape the paragraph above it already names — and that paragraph's own closing sentence, which said the #11182 set was what remained below, is corrected for the same reason. #11274 is not emptied by this: it keeps its recurring_failure_mode row. Identical deletions on both sides merge cleanly. Verified by identity rather than by count: the four #11182 spellings (AllLedgersAdmitted, InventoryLotEvidence, LedgerEvidenceRefused, admit_ledger_evidence) are gone, and this PR's six rows stand unchanged at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Head The previous floor run reported Deleting them removes nothing that could still fire: with the transition present at the base there is no delta left for them to admit. Their header paragraph went with them rather than being left describing rows that no longer exist, and the preceding paragraph's closing sentence — which claimed the #11182 set was what remained below — is corrected for the same stale-citation reason that paragraph itself names. #11274 is not emptied by this: it keeps its Verified by identity, not by count: the four #11182 spellings ( — sent from vivid-stag-90 |
REFUSAL FOLDED IN: gunbc.gitignore_emit emitted the materialisation prefixes
unanchored, so `private_overlay/` also ignored `fixtures/private_overlay/x.dag`
— material outside the modeled private prefix, which may be perfectly public.
Git reads a pattern with no leading slash as matching at any depth, while a
materialisation row declares a prefix AT THE WORKSPACE ROOT, so the unanchored
form asserted something the model does not.
The pattern is now derived through materialisation_ignore_pattern as
concat("/", prefix) — the same anchoring the generated-artifact arm in this file
already applies, and derived from the row rather than hand-spelled.
A SECOND DEFECT SAT BEHIND THE FIRST and would have survived an emitter-only
fix: pattern_matches discarded the leading slash on every directory pattern, so
path_is_ignored answered the UNANCHORED question for anchored patterns. The
predicate would have called the nested fixture path ignored while git, reading
the same file, would not — two answers to one question, and a witness would have
read the wrong one. anchored_dir_matches closes it; path_is_ignored has no
consumer outside this gate.
The witness pair is the discriminator, and it was verified to go red without the
fix rather than assumed to: a root-level materialisation path is ignored, and
fixtures/private_overlay/example.dag, dag/test/fixture/privdag/sample.dag and
vendor/private_repo/readme.md are NOT.
The receipt gains the measured host facts. The only thing covering the overlay
prefix before this change was the common git dir's info/exclude — uncommitted,
unreviewed, absent in a fresh clone, so check-ignore returned a reassuring answer
for the wrong reason; and that same file is itself unanchored, so it is
simultaneously too narrow to travel and too broad where it applies. Three
staging behaviours are now measured rather than two — clone/gitlink/warns,
copy/100644/silent, symlink/120000/target-path/silent — and the third needs no
method arm, because the derivation reads visibility and prefix only, so one
prefix row covers every staging behaviour at that prefix. private_overlay_m/
gets no row: it was never observed being produced, appearing only in that
exclude. And the ignore row is one wall of several: `git worktree prune` does
not delete files, so residue survives outside every worktree git knows about.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
The floor refused the witnesses lane with CLAIM-SCOPE REFUSAL
cause=AmbiguousBareNameRead scope=test.claim.gitignore_gate, two sites: `String`
and `Unit` are read bare by extdeps.python while two transitively-reached modules
declare each of them.
The ambiguity is latent in extdeps.python and this branch surfaced it by
widening that scope's closure — an ambiguous bare read refuses only once BOTH
declaring modules are reachable, so the defect was there and unreachable rather
than introduced here.
Verified at the declaration sites rather than taken from the diagnostic's
wording: `String` is declared by std.string_type, so the existing
`import std.types { String }` named a module that does not declare it and bound
nothing, which is the case the refusal text calls out explicitly; and `Unit` is
declared by std.types and used bare at the exit arm with no import naming it at
all. The import now names the declaring authority for each.
Note on evidence: the gitignore_gate witnesses pass locally both before and
after this change, so that green does not cover this refusal — claim_batch does
not perform the floor's claim-scope check. The required lane is the verifier.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
#11274 landed on main and retired the seven consumed #11182 relocation admissions — the same rows this branch retired when the wall charged them to its own touch. The identical deletions reconcile exactly as predicted: main's array is empty, this branch's carries its own six #11306 rows, and the merge keeps those six with the seven gone from both sides. One difference is resolved in this branch's favour rather than split. #11274 deleted the seven rows but left the paragraph above them standing, still asserting that "THE #11182 RELOCATION ROWS SURVIVE THIS DISSOLUTION" and describing seven rows that no longer exist. That is the stale-citation shape the preceding paragraph in the same file names explicitly — prose naming rows neither of which exists reads as coverage — so the paragraph stays deleted here, and the earlier paragraph's closing sentence continues to name what actually remains below. Verified by identity: six rows, RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3; no #11182 spelling and no #11182 prose survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
…ink is covered REFUSAL FOLDED IN, and the refused claim was mine. The annotation added with the anchoring repair said one prefix row covers every staging behaviour at that prefix. That is false. A gitignore pattern ending in a slash matches DIRECTORIES ONLY, so /private_repo/ sails straight past a symlink named private_repo — measured in a scratch repository, it stages as mode 120000. THE REASONING THAT PRODUCED IT IS RECORDED RATHER THAN QUIETLY REPLACED, in the module annotation and in the RFM receipt, because it is the seductive kind: the derivation IS method-agnostic — it reads visibility and prefix and nothing else — but coverage is decided by the emitted PATTERN, not by the derivation. A true statement about one layer was carried into a claim about another, and the gap was invisible because both sentences describe the same row. THE FIX IS THE PATTERN FORM, not a new method arm. The materialisation row now carries a bare PATH PREFIX (private_repo, not private_repo/) and the emitter owns the gitignore punctuation, so neither the depth question nor the inode-kind question is answerable from the model's side. The emitted pattern is anchored and unslashed, which matches a file, a directory and a symlink at that path — all three measured in a scratch repo, with a nested same-named path still spared, so the public-material property the anchoring repair bought is unchanged. EVIDENCE, and one honest limit. Nine gate witnesses pass. The red control is line-exact: witness_private_material_rows_are_derived asserts each whole line (newline-delimited), so restoring the trailing slash makes it FAIL — verified. THE LIMIT: witness_materialisation_patterns_are_root_anchored still PASSES under the broken pattern, because path_is_ignored has no inode-type input and cannot distinguish a symlink from a directory at all. So the byte-level assertion is the wall for this class, and the scratch-repo measurement is what grounds it; the predicate is not evidence here and is not cited as such. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Migration recipe from #11345 (directory-is-membership cut of This PR still authors const rows. After #11345 lands (or if you rebase onto it):
Consumed-row deletion after merge is deleting that file (#11316 stays the follow-up; it becomes a file delete). |
The const's prose about what remains used to rot beside the array. Empty-is-not-permissive and the #11306/#11316 inheritance live on gunbc.namespace.transition_admission; this cut still inherits six files or zero, never a pre-migration onto an unlanded PR. Co-authored-by: Cursor <cursoragent@cursor.com>
REQUEST_CHANGES folded in (review 65999). The emitted header still described the pre-derivation world: it attributed the file to gunbc.repo_workspace alone and claimed every pattern carries "a typed ignore reason", while this branch had added a third arm whose reason text is derived from a repository's VISIBILITY and not from std.workspace_artifact IgnoreReason at all. A reader trusting the header was told the file had a model it no longer has. That is the same defect this branch already repaired twice in the admission roster's comments — prose that reads as coverage for a model that is no longer true — and it is worse in a generated artifact, because nothing else in the file disagrees with the header and it goes stale silently. The header now names each arm with its real home: workspace footprints from gunbc.repo_workspace carrying a typed IgnoreReason; non-world-readable material from gunbc.workspace_repository_materialisation joined with the visibility standings in gunbc.repo_identity, whose pattern is the row's root-anchored prefix and whose reason is derived rather than authored per row; and generated artifacts from gunbc.generated_artifact commit policy. Regenerated through the drift-gated path. The witness is strengthened to match rather than left asserting one substring: witness_has_header now requires every producer to be named, so adding a fourth arm without announcing it turns the gate red instead of passing quietly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Fixed in The header now names each arm with its real home rather than attributing the file to Regenerated through the drift-gated path, not hand-edited. The witness is strengthened rather than merely updated. It previously asserted one substring, which is exactly how the old header survived the arm being added. — sent from vivid-stag-90 |
…r it REQUEST_CHANGES folded in (review 66024). The clause asserting that expected_gitignore() does not contain "public_overlay_probe/" was permanently green by construction, and the review is right that it would have been cited as coverage. THE PRESCRIBED FIX WOULD HAVE LEFT A SUBTLER DECORATION STANDING, which is why the clause is deleted instead of edited. The review names one reason — this branch made the emitter produce bare prefixes, so a leak would read /public_overlay_probe and a search for the slash-bearing spelling can never fail. Dropping the slash repairs that and not the second reason: the probe rows are TEST-LOCAL data and are never members of workspace_materialisations(), which is the roster expected_gitignore() folds, so no spelling of the probe can appear in the projection under any behaviour of the code. The clause was unfalsifiable about the emitter, not merely misspelled. What remains is discriminating, and that is verified rather than asserted: with materialisation_must_not_be_tracked forced to admit everything — the exact defect the witness exists to catch — witness_public_source_derives_no_ignore_row FAILS. Restored, all nine gate witnesses pass. The reason for the deletion is recorded beside the witness, so the next author does not helpfully add the clause back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Fixed in The review names one reason: this branch made the emitter produce bare prefixes, so a leak would read What remains is discriminating, and I verified that rather than assuming it: forcing The reason for the deletion is recorded beside the witness, so the clause does not get helpfully added back later. — sent from vivid-stag-90 |
#11347 landed an admission row for the gunbc#11193 artifact_store_fs anchor leaf into the same const this branch appends to — the third append-vs-append collision on this array in one day. Both row sets survive: main's one row and this branch's six, seven total, verified by identity rather than count. Main's three bare-read repairs (ipmi, cron, artifact_store_fs) are disjoint from this branch's extdeps.python repair, so those do not interact. The stale #11182 paragraph stays deleted. It returned on main's side because #11274 removed its seven rows and left the prose asserting they survive; this branch removed it once already and the same reasoning applies unchanged — prose describing rows that no longer exist reads as coverage, which the paragraph above it names as the defect to avoid. The standing sentence naming what remains below is corrected again, to name both sets. That sentence has now gone stale twice in one day from merges that changed the population under it, and it now says so: a census kept beside an array rots every time the array moves, which is the standing argument for the directory-is-membership cut in #11345. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
…h by the wall #11347 landed, so the artifact_store_fs anchor relocation is at base and its one admission row reports CONSUMED — 0 unadjudicated, 0 stale, 1 consumed due for deletion. The wall charges a consumed row's deletion to the roster's NEXT TOUCH, and this branch is that touch again by virtue of carrying its own six rows. APPLIED UNDER THE STANDING RULING RATHER THAN RE-ASKED. The identical question was decided today for the seven #11182 rows: the wall is designed to charge the next touch, identical deletions on both sides merge cleanly in git, and waiting costs hours of serialized CI and review on another lane's PR plus a re-run here, against one push. #11356 owns the same retirement and is not emptied by this — it keeps whatever else it carries, exactly as #11274 kept its failure-mode row. Deleting it removes nothing that could still fire: with the transition present at the base there is no delta left for it to admit. Its doc paragraph goes with it rather than being left describing a row that no longer exists. Verified by identity: the extdeps_external_authority_anchor spelling is gone and no #11193 reference survives anywhere in the file; this branch's six stand unchanged at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. The standing sentence above the array is corrected for the THIRD time in one day, and now says so. This one retired a row this branch never authored, which is the sharpest form of the argument for the directory-is-membership cut in #11345: a census kept beside an array that every lane appends to rots continuously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
REQUEST_CHANGES folded in (review 66093), and the finding is correct and sharp: this branch minted gunbc_public_repository in gunbc.repo_identity carrying owner "gunb-ai", name "gunbc" and world-readability — a SIXTH spelling of an instance fact gunbc.repository gunbc_repository already owns, in a change whose whole thesis is de-forking. gunbc.repository's own note anticipates the move verbatim: the answer to five nicknames is not a sixth carrying a promise to consolidate later, it is one authority the others CONSUME. I did not grep the gunbc layer for an existing repository instance before authoring one, which is the whole defect. WHAT LANDS INSTEAD. RepositoryVisibilityStanding is DELETED along with its two helpers; nothing carries owner/name/visibility a second time. extdeps.github .repository gains repository_visibility, a function over the EXISTING Repository that maps the API's boolean alias to the typed coproduct, plus repository_is_world_readable over it. That is the bridge between the upstream's two spellings, it exists once, and it is where the consolidation this module already named as a follow-up actually begins rather than being promised. gunbc.repository gains gunbc_private_repository beside the row it already owns, because no typed row for the private repository existed anywhere — that fact is new, not a fork. Every field is OBSERVED from the GitHub repository object read 2026-09-14, not assumed: full_name, private and default_branch came back gunb-ai/gunbc-private, true and main. default_branch is not inferred from the public repository's; a sibling is free to differ and a guessed field is the kind of quietly-wrong fact that survives until something acts on it. gunbc.repo_identity is back to its own subject — default branch and remote — and the materialisation rows source Repository directly, with the slug read off full_name rather than recomputed. Evidence: nine gate witnesses pass, and the derivation still tracks its authority rather than a copy of it — flipping gunbc_private_repository to private: false drops all five derived patterns from the projection (3 to 0 on the three original prefixes), restored when reverted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Fixed in What lands instead of a repaired fork:
On the instance fields: observed, not assumed. Every field of the new row came from the GitHub repository object read 2026-09-14 — Evidence: nine gate witnesses pass, and the derivation still tracks its authority rather than a copy — flipping One correction to something I said earlier on this PR: I described the — sent from vivid-stag-90 |
REQUEST_CHANGES folded in (review 66104). When the forked instance row was deleted from gunbc.repo_identity, the receipt in this row still said that module carries the visibility standing of both repositories. That became false in the same commit that fixed the fork, and it is the same stale-citation shape this branch has now corrected four times elsewhere — prose naming an authority that no longer holds the fact, which reads as coverage. The receipt now names gunbc.repository and its two typed Repository rows, says the private row's fields were read from the GitHub repository object rather than assumed, and names extdeps.github.repository repository_visibility as the mapping from the API's boolean alias to the typed coproduct. The PR body carried the same claim and is corrected with it. The one surviving mention of gunbc.repo_identity, in extdeps.github.repository's annotation, is deliberate and stays: it is past tense, recording what an earlier draft did and why it was wrong, which is history rather than a citation. No projection is owed: docs/design-failure-modes.md is gitignored and untracked, so the failure-mode ledger has no committed artifact to regenerate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
|
Fixed in The receipt now names One mention of No projection is owed. — sent from vivid-stag-90 |
#11356 landed and retired the gunbc#11193 anchor admission row — the row this branch had already retired when the wall charged it to this touch. The identical deletions reconcile exactly as the standing ruling predicts: the row is gone from both sides and this branch's own six remain. Both sides also independently removed the stale #11182 prose, which is the fourth time that paragraph has been repaired today by two different lanes reaching the same conclusion from the same rule. One sentence of main's is kept rather than dropped with its body: main's revision says empty is the roster's resting state, which is true in general and false in the instant after this merge, since six rows are present. It is folded in as a sentence that stays true either way — the six are consumed by their own merge and #11316 is already authored to delete them, so a populated roster is a transition and never a steady state. Verified by identity: six rows, RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3; no #11193 row and no #11182 prose survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
Sixth re-integration, fifth append-vs-append on this one const. Main accumulated three commits in the file — #11356 emptying the roster, #11256, and #11214 landing fifteen rows for the operator-supplied-token relocation — producing seven conflict regions. Both row sets survive: fifteen plus six, verified by spelling rather than by count. Main's own "THE ROSTER IS EMPTY AGAIN, which is its resting state" paragraph is dropped, and not on this branch's preference: it became false ON MAIN when #11214 landed fifteen rows above it and nothing updated the prose. Keeping it would have carried a claim contradicted by the array directly beneath it, which is the stale-citation shape this file names as the defect to avoid and which this branch has now repaired five times. The sentence naming what remains below already records that it has gone stale repeatedly; this merge is another instance and does not change its argument. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
… wall #11214 landed, so the operator-supplied-token relocation is at base and its fifteen admission rows report CONSUMED — 0 unadjudicated, 0 stale, 15 consumed due for deletion, with this branch's own six adjudicating cleanly. The wall charges a consumed row's deletion to the roster's next touch and this branch is that touch again. Applied under the standing ruling rather than re-asked; this is the third time it has fired on this branch and the second time for rows another lane authored. #11259 is the authored deletion draft for these fifteen and is not emptied by this — it keeps whatever else it carries, as #11274 and #11356 did. Their paragraph goes with them rather than being left describing rows that no longer exist. Verified by identity: no #11214 reference survives anywhere in the file; this branch's six stand unchanged at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
…11345) * Cut the namespace transition-admission roster to directory membership. Delete the hand-Rust const so permission is one .dag file per relocation; appends and consumed deletions no longer collide on a shared array. Co-authored-by: Cursor <cursoragent@cursor.com> * Type expected_candidates as DeclarationRef, not a string list. The wall still joins declaring-module identity; the authored row now names those identities with the same declaration-reference type as enclosing. Co-authored-by: Cursor <cursoragent@cursor.com> * Supersede the const-ness-as-safety claim in the roster carrier. Permission stays authored and reviewable; the vehicle is directory membership, not a const. Missing directory is the empty roster and admits fewer rows, never more. Co-authored-by: Cursor <cursoragent@cursor.com> * Home the empty-roster standing record on the membership module. The const's prose about what remains used to rot beside the array. Empty-is-not-permissive and the #11306/#11316 inheritance live on gunbc.namespace.transition_admission; this cut still inherits six files or zero, never a pre-migration onto an unlanded PR. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop the leftover const census and refuse a phantom candidate leaf. The #11182 permission paragraph had attached to WaveAdmissionPopulation after the const died. Candidate DeclarationRef.decl_name must equal Binding.spelling. Co-authored-by: Cursor <cursoragent@cursor.com> * Enumerate the directory-row parse helpers on the seed census. The bijection sentence on that row was false after the const was replaced; name the helpers the loader actually added. Co-authored-by: Cursor <cursoragent@cursor.com> * Do not re-home the consumed gunbc#11193 admission as a directory row. Co-authored-by: Cursor <cursoragent@cursor.com> #11356 owns that deletion (also carried on #11214). Copying it onto the new carrier would duplicate their receipt and keep a consumed row on a roster touch. * Own the used_row fixture strings; Binding is no longer &'static. Co-authored-by: Cursor <cursoragent@cursor.com> #11250's merge-queue tests used const-roster literals; the directory cut stores owned String. * Stop claiming the admission roster is a .rs file. Row files are .dag and in sweep; roster_touched still reads the unfiltered diff because prefix match is not in_sweep_scope. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Seventh re-integration, sixth append-vs-append on this const. #11138 landed thirty-seven string_eq collapse rows carrying a label CONSTANT rather than a literal, which is why a label grep for this branch's spelling found none of them — the shape of a row changed, not just the population. Both sets survive: thirty-seven plus six, the label const kept, verified by label rather than count. Main's "THE ROSTER IS EMPTY AGAIN, which is its resting state" paragraph is dropped for the second time, on the same ground as the first and not as a preference: it is false ON MAIN, where thirty-seven rows sit directly beneath it. It returned because the lanes that emptied and refilled the array did not touch the prose above it — which is the whole failure this paragraph keeps demonstrating about itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
#11138 landed, so the string_eq collapse is at base and its thirty-seven rows report CONSUMED — 0 unadjudicated, 0 stale, 37 consumed due for deletion, with this branch's six adjudicating cleanly throughout. The wall charges a consumed row's deletion to the roster's next touch; this branch is that touch for the fourth time, and the third time for rows another lane authored. STRING_EQ_COLLAPSE_LABEL goes with them. A label constant is text shared by the rows that cite it — no function, type or host capability — and this file's own history records the rule: it dissolves with the last row carrying it. Leaving it would be a declaration nothing consumes. Their paragraph goes too, rather than being left describing rows that no longer exist. Verified by identity: no STRING_EQ_COLLAPSE_LABEL reference survives anywhere in the file; this branch's six stand unchanged at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F
… prose Eighth re-integration, seventh append-vs-append on this const. #11373 landed thirty-two PCI identity rows; #11138's thirty-seven were retired on main by that same touch. Both live sets survive: thirty-two plus this branch's six. I REVERSED A JUDGEMENT I HAD MADE TWICE. On the previous two merges I deleted main's "THE ROSTER IS EMPTY AGAIN" paragraph as false — thirty-seven rows sat beneath it. Reading main's block properly this time, that paragraph is not orphaned prose: main carries a corrected successor ("THE ROSTER RETURNED TO EMPTY ON MAIN ... AND THIS BRANCH REFILLS IT"), and the #11138 paragraph that looks stale is followed by its own trigger record saying the thirty-seven went when #11138 merged and were deleted by #11373. The block is a curated history whose authors state why entries are kept — "the only place in this file that NAMES the retired row" — and one of them is explicit that an unexamined deletion must not be passed to whoever next touches the file. So main's prose is taken INTACT here and nothing of it is removed. My earlier deletions were defensible on the text I had read, and repeating them now would be me re-removing, for a third time, text another lane deliberately maintains. The one paragraph this branch adds is its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#11373 landed, so the PCI identity repair is at base and its thirty-two rows report CONSUMED — 0 unadjudicated, 0 stale, 32 consumed due for deletion, this branch's six adjudicating cleanly throughout. Fifth time the wall has charged this touch, fourth for rows another lane authored. #11373's own paragraph says a separate cleanup PR must remove these and that the admission does not waive it. That remains true and this does not replace it: the wall charges the roster's next touch, this branch is it, and the standing ruling is to pay rather than serialize behind another lane's PR. That lane keeps whatever else its cleanup carries. WHAT IS DELETED AND WHAT IS NOT, now that the distinction has bitten once. The paragraph BOUND to these rows goes with them — it describes thirty-two rows that no longer exist. Main's HISTORICAL entries stay untouched: the empty-roster paragraph, its corrected successor, the #11138 record, and the sentence noting those rows were deleted on #11373's touch. That last one still cites #11373 and is correct history, not a dangling reference to what this commit removed. I deleted main's prose twice before on a reading that turned out to be wrong; the rule I am applying now is narrower and is the file's own: a paragraph dies with the rows it describes, and nothing else does. Verified: no #11373 ROW survives; main's three historical paragraphs survive; this branch's six stand at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ninth re-integration, eighth append-vs-append on this const, and the third distinct row shape this branch has had to recognise: #10729 landed thirty-five rows under TWO label constants (SCM_MERGE_BASE_COHOME_LABEL, SCM_SOURCE_RECOVERY_REHOME_LABEL), after literals and after a single constant. Both live sets survive: thirty-five plus this branch's six, both label constants kept, verified by label form rather than by a pattern guessed from the previous shape. Main's prose is taken intact again. Nothing of main's is removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ants #10729 landed, so the SCM re-home cohort is at base and its thirty-five rows report CONSUMED — 0 unadjudicated, 0 stale, 35 consumed due for deletion, this branch's six adjudicating cleanly throughout. Sixth time the wall has charged this touch, fifth for rows another lane authored. Both SCM_SOURCE_RECOVERY_REHOME_LABEL and SCM_MERGE_BASE_COHOME_LABEL go with them, by the rule this file states and this branch applied once already: a label constant is text shared by the rows citing it and dissolves with the last one. THE PROSE SPLIT IS THE CAREFUL PART. The paragraphs BOUND to this cohort go with it — the one introducing the SCM re-home rows and the one recording that five of them had already gone on their own trigger, both of which describe rows that no longer exist here. The paragraph ABOVE them stays: it records that #11373's thirty-two rows dissolved on their own trigger, which is history about a different cohort and remains true. That distinction is the one this branch got wrong twice by deleting main's history along with stale text, and the rule it now applies is the file's own: a paragraph dies with the rows it describes, and nothing else does. Verified: no SCM label constant or row survives; the #11373 dissolution record survives; this branch's six stand at RepositoryVisibility 1, PublicRepository 2, PrivateRepository 3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…slash), matching the emitted bytes The gate refused 0c7c280 because the receipt still described the committed anchored row as /private_overlay/ while the head deliberately emits it without the slash (trailing-slash patterns are directory-only and miss a symlink). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qc3VJ9KnBA7aXp2ZQjCwTq
Their DISSOLVE-ON fired: #11306 merged as 9f5e498, so the repository- visibility relocation is present at the base of every pull_request build, the six TargetChanged deltas are no longer producible, and the rows report CONSUMED — refusing every unrelated PR until they go. Their paragraph goes with them rather than being left describing rows that no longer exist. REBUILT FROM MAIN RATHER THAN MERGED FORWARD. This branch was authored before #11306 landed and had drifted 114 commits behind; merging main into it conflicted on the branch's own stale copies of that PR's files, and its diff against main had become a 715-file, 81k-line revert. A draft in that state is dangerous rather than merely stale, so the branch is reset to main and carries exactly one change: the roster returns to empty, which is its resting state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep the #11306 visibility rows beside the C2 deployment_spec_srv1 rebinds. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve roadmap_authority.dag onto the PR's active(...) form (keeping main's updated fn and new nodes, authored sizing dropped), union the stage0 namespace transition admissions (#11306 rows + #10994 rows), and regenerate docs/design-rung-drops.md from gunbc.rung_drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What this is
A directory named
private_overlay/,base_overlay/,wb62_overlay/orprivdag/placed inside a public gunbc session worktree stages full file content ongit add -Awith no warning; a nested clone atprivate_repo/stages a gitlink. The tracked.gitignorehad no entry for any of them — protection so far was per-session.git/info/excludelocal excludes, absent in a fresh clone and invisible until after a push.The privacy fact has one home, and the ignore rows are derived from it. An earlier head of this branch declared an ignore reason
ForeignPrivateMaterial { owner_repository: String }and authored five rows under it. That was a second, nicknamed representation of a visibility the corpus already models, and it is gone.The six namespace admission rows, and why they are admissible (v1_seed_standing)
Moving
RepositoryVisibility,PublicRepositoryandPrivateRepositoryto their resource module is a relocation the namespace wave adjudicates, so the required run refused with six unadjudicatedTargetChangeddeltas — three sites inextdeps.github.actions_token, three intest.claim.actions_job_grant_witness, every one of thembase {extdeps.github.actions_token} -> head {extdeps.github.repository}. That is the wall working:gunbc.namespace_wave_admissionstates its rows exist for exactly this motion, "a spelling authored on both sides now resolves to a different module". The membership half was auto-admitted asExplicitlyEvaluatedZeroDelta.hosted_runnersproduces no row — its arms were spelledPublicRepo/PrivateRepo, so they are new names at the head rather than rebinds.The remedy is a row per delta, by exact identity, in
NAMESPACE_TRANSITION_ADMISSIONS.The admission, stated rather than left to inference. These six rows are admitted under
gunbc.v1_maintenance_standingv1_seed_standingas consumed ledger entries, not a new escape hatch: they are DATA on an already-rostered declaration and add no function, no type, and no admission mechanism — the same standing the module's own justification records for the 53 such rows in PR 9436. They adjudicate one measured delta each, by exact identity, and are consumed by this PR's merge.Deletion follow-up: #11316, authored before this PR was enqueued. Its trigger fires when this merges — base and head then both carry the relocation, the deltas stop being producible, and the rows report stale. Per the standing ruling it lands after this PR. Authoring it first is the condition that distinguishes a consumed ledger entry from an exemption, and it is why this change did not take the cheaper route of leaving the two forked spellings in place to avoid producing a delta at all — that would have been DESIGN §5's workaround, routing around the substrate rather than using it.
Where each fact lives now
extdeps.github.repository(new) — ONERepositoryVisibility, cited to thevisibilityfield of GitHub's repository object. It was forked:extdeps.github.actions_tokendeclaredRepositoryVisibility = PublicRepository | PrivateRepositoryfor the fork-token rule andextdeps.github.hosted_runnersdeclaredRepoVisibility = PublicRepo | PrivateRepofor the runner hardware tables — one concept, two spellings, free to disagree. Both import this type now, as dogunbc.runner_provider_surveyandtest.claim.actions_job_grant_witness.gunbc.repository—gunb-ai/gunbcandgunb-ai/gunbc-privateas typedRepositoryrows. This is the single instance authority and it already owned the public one; the private row is new (nothing carried it) and its fields were read from the GitHub repository object, not assumed —full_name,private,default_branch=gunb-ai/gunbc-private,true,main. If either is flipped at the forge, this is the row to change and the projection follows.extdeps.github.repositoryrepository_visibilitymaps the API's boolean alias to the typed coproduct, so "not world-readable" is asked of one row in one place.An earlier head of this branch authored a second instance row in
gunbc.repo_identity— a sixth spelling of a factgunbc.repositoryalready owned, in a PR whose thesis is de-forking. That fork is deleted, along with theRepositoryVisibilityStandingtype it needed;gunbc.repo_identityis back to its own subject (merge target and default branch).gunbc.workspace_repository_materialisation(new) — the compose contract: which repository is materialised under which prefix by which method. The staged shape is derived from the method (NestedClone → gitlink,FileCopy → full content) rather than authored beside it, because a row carrying both could declare a clone that stages full content, which no git does. The private half of the contract — the private repository'sstrategy.private_witness_workflowoverlay_local_method— is cited by name and deliberately not imported.gunbc.gitignore_emit— DERIVES one ignore row per materialisation whose source repository is not world-readable, with the comment text derived from that visibility. No reason is authored per row. A new overlay spelling is a compose row over there and its ignore row appears here by construction.std.workspace_artifactandgunbc.repo_workspace— byte-identical tomain; the whole authored-arm approach is reverted.Evidence
Six witnesses PASS in
test.claim.gitignore_gate(claim_batch --entry dag/test/claim/gitignore_gate_test.dag):The derivation is witnessed in both directions, because a filter that always passes and a filter that never fires emit the same green: over a mixed list,
untracked_materialisationskeeps exactly the private-sourced row,materialisation_must_not_be_trackedis false for the public-sourced one, andpublic_overlay_probe/never reaches.gitignore. The negative on the other axis is kept too —dag/gunbc/private_direction_note.dagmust stay un-ignored, so this is not a substring sweep.A stronger control, since the witnesses share the emitter they test. Flipping
gunbc_private_repositorytoPublicRepositoryand re-running the emitter:One row flip removes every derived pattern. The rows follow the visibility fact rather than standing beside it.
Measurement — before and after, throwaway repo
Re-measured against the current head, so the receipt matches the bytes the PR actually carries. One scratch repo per side: five compose directories populated, a symlink at a materialisation prefix, and a nested
fixtures/private_overlay/path that no row claims.Before (
.gitignoreas it stands on the merge base):Every prefix stages full file content, and the symlink stages as
120000with the target path as its blob.After (this branch's derived
.gitignore, blob1d8aee59bc76, which is the committed file):Three properties in one run: the five directories are ignored; the symlink at
private_repois ignored (it is absent from the staging list, which the trailing-slash form did not achieve); andfixtures/private_overlay/— nested, same-named, claimed by no row — stays trackable, which is the public material the anchoring protects.What rung this is, and what it is not (DESIGN §4b)
Rung 2, mechanically preventable, at best. The derivation does not change that — it removes a second representation of the privacy fact, which is a §3 defect, not a §4b rung.
git add -fbypasses a tracked ignore entirely.git status.The row's next-rung trigger is unchanged and undischarged: the private-witness step must materialise its overlay outside the public checkout. An ignore naming prefixes inside the worktree is by construction not that capability, and the receipt says so rather than narrowing the trigger to what it just built.
Not in scope, stated rather than left silent
The same upstream fact is spelled two more times:
extdeps.github.githubRepository.private: Boolandextdeps.github.code_searchCodeSearchRepository.private: Bool. Those are the API's boolean alias forvisibility, their consumer set is two orders larger than the two coproducts unified here (dozens of witnesses constructprivate: false), and folding them in is its own change. It is recorded in the new module's annotation as a named follow-up.GitHub's
visibilityalso admitsinternalfor enterprise accounts. It is not modeled: no row in this corpus observes one, and an arm nothing constructs would be a state no evidence supports. The annotation names what closing it would take.🤖 Generated with Claude Code
https://claude.ai/code/session_01MSpQ3EJ1Nn8V16yEDqbG4F