Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e31bccb
Declare private-repository material as an ignore reason, not a hand edit
Sep 13, 2026
1b88840
Derive the private-material ignore rows from repository visibility
Sep 13, 2026
5d05183
Admit the six repository-visibility relocation deltas
Sep 13, 2026
d7cf3d3
Merge remote-tracking branch 'origin/main' into session/vivid-stag-90
Sep 14, 2026
1ece3c4
Retire the seven consumed #11182 relocation admissions, charged to th…
Sep 14, 2026
6888f48
Anchor the derived ignore patterns at the workspace root
Sep 14, 2026
abb0758
Name the declaring authority for extdeps.python's bare String and Unit
Sep 14, 2026
abd25d3
Merge origin/main: #11274 retired the same seven rows
Sep 14, 2026
8c03693
Emit the materialisation patterns without a trailing slash, so a syml…
Sep 14, 2026
67abf66
Name all three arms in the generated .gitignore header
Sep 14, 2026
71b9808
Remove a decoration from the public-exclude witness rather than repai…
Sep 14, 2026
c351a1c
Merge origin/main: keep #11193's row beside this branch's six
Sep 14, 2026
0d109c8
Retire the consumed #11193 anchor admission row, charged to this touc…
Sep 14, 2026
7eed991
Consume gunbc.repository instead of forking the instance fact
Sep 14, 2026
0d60267
Point the failure-mode receipt at gunbc.repository, not the deleted fork
Sep 14, 2026
4976af0
Merge origin/main: #11356 retired the same #11193 row this branch had
Sep 14, 2026
05b12e9
Merge origin/main: #11214's fifteen rows beside this branch's six
Sep 14, 2026
5ddc99e
Retire the fifteen consumed #11214 rows, charged to this touch by the…
Sep 14, 2026
1453ce0
Merge origin/main: #11138's thirty-seven rows beside this branch's six
Sep 15, 2026
d5b5719
Retire the thirty-seven consumed #11138 rows and their label constant
Sep 15, 2026
462b516
Merge origin/main: #11373's thirty-two rows, and stop deleting main's…
Sep 15, 2026
7c6d64e
Retire the thirty-two consumed #11373 rows, keeping main's history
Sep 15, 2026
b9db0ec
Merge origin/main: #10729's thirty-five rows beside this branch's six
Sep 15, 2026
0c7c280
Retire the thirty-five consumed #10729 rows and their two label const…
Sep 15, 2026
348be3c
RFM receipt: name the committed row as /private_overlay (no trailing …
Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# gunbc .gitignore — generated from gunbc.repo_workspace via gunbc.gitignore_emit — do not hand-edit.
# gunbc .gitignore — generated via gunbc.gitignore_emit from gunbc.repo_workspace, gunbc.workspace_repository_materialisation and gunbc.generated_artifact — do not hand-edit.
#
# Every pattern is declared by its producer (an extdeps product's workspace footprint, or a repo tool's own declaration) with its meaning and a typed ignore reason; the trailing section is derived from gunbc.generated_artifact commit policy.
# THREE ARMS, each with its own producer and its own reason for being untracked.
# 1. Workspace footprints, from gunbc.repo_workspace: an extdeps product's own declaration or a repo tool's, each artifact carrying its meaning and a typed std.workspace_artifact IgnoreReason.
# 2. Material from a repository that is not world-readable, from gunbc.workspace_repository_materialisation joined with each source repository's visibility in gunbc.repository: the pattern is the row's root-anchored prefix and the reason text is DERIVED from that visibility, never authored per row.
# 3. Generated artifacts, derived from gunbc.generated_artifact commit policy.

# --- Cargo build artifacts [cited: doc.rust-lang.org/cargo/] ---
# regenerable from committed source — cargo build output tree (compiled artifacts, incremental state)
Expand Down Expand Up @@ -164,6 +167,18 @@ src/v2/test/claim/manual/sg2_scratch_probe.dag
# superseded; canonical home: docs/plans/ (generated PlanArtifact) and DESIGN.md — pre-plan-carrier fixture scratch
/scripts/fixtures/

# --- Material from a repository that is not world-readable (derived from gunbc.workspace_repository_materialisation and each source repository's visibility) ---
# gunb-ai/gunbc-private is private — never tracked by this repository; materialised here by gitlink (160000): a commit sha and a path, with a warning — nested clone of the private repository beside the public checkout
/private_repo
# gunb-ai/gunbc-private is private — never tracked by this repository; materialised here by full file content (100644), silently — overlay copy made by the private-witness step (cp -a of the private dag tree)
/private_overlay
# gunb-ai/gunbc-private is private — never tracked by this repository; materialised here by full file content (100644), silently — overlay copy under the first observed recipe argument
/base_overlay
# gunb-ai/gunbc-private is private — never tracked by this repository; materialised here by full file content (100644), silently — overlay copy under a third observed recipe argument, which is why this roster is a list of spellings
/wb62_overlay
# gunb-ai/gunbc-private is private — never tracked by this repository; materialised here by full file content (100644), silently — private .dag source root materialised beside the public one
/privdag

# --- Generated artifacts (gitignored-by-default, derived from generated_artifact_registry) ---
# Regenerable; not committed. Whitelist: ci.yml, .gitignore, .md docs (ROADMAP.md, DESIGN.md, docs/plans/).
/docs/design-failure-modes.md
Expand Down
5 changes: 1 addition & 4 deletions dag/extdeps/github/actions_token.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module extdeps.github.actions_token

import std.types { NonEmptyStr }
import extdeps.github.repository { RepositoryVisibility, PublicRepository, PrivateRepository }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef }
import extdeps.uri { Uri, Https }
Expand Down Expand Up @@ -197,10 +198,6 @@ type PullRequestOrigin
| ForkedRepositoryOrigin
| OriginNotApplicable

type RepositoryVisibility
= PublicRepository
| PrivateRepository

type ActionsRepositoryPolicyObservation
= ActionsRepositoryPolicyUnobserved
| ActionsRepositoryPolicyObserved {
Expand Down
17 changes: 8 additions & 9 deletions dag/extdeps/github/hosted_runners.dag
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import std.measure {
hardware_thread_count,
}
import extdeps.github.actions { RunnerLabel, Ubuntu2404, Ubuntu2404Arm, UbuntuSlim }
import extdeps.github.repository { RepositoryVisibility, PublicRepository, PrivateRepository }

import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
Expand All @@ -32,11 +33,9 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
// alias of the current x64 row and deliberately not a row - pinning by alias lets the vendor move
// the fact.

type RepoVisibility = PublicRepo | PrivateRepo

type GithubHostedRunnerCatalogRow {
runs_on_label: RunnerLabel
visibility: RepoVisibility
visibility: RepositoryVisibility
vcpus: HardwareThreadCount
memory_bytes: ByteSize
storage_bytes: ByteSize
Expand All @@ -45,7 +44,7 @@ type GithubHostedRunnerCatalogRow {

data github_hosted_ubuntu_slim_public_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: UbuntuSlim,
visibility: PublicRepo,
visibility: PublicRepository,
vcpus: hardware_thread_count(1),
memory_bytes: byte_size(5000000000),
storage_bytes: byte_size(14000000000),
Expand All @@ -54,7 +53,7 @@ data github_hosted_ubuntu_slim_public_catalog: GithubHostedRunnerCatalogRow = Gi

data github_hosted_ubuntu_slim_private_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: UbuntuSlim,
visibility: PrivateRepo,
visibility: PrivateRepository,
vcpus: hardware_thread_count(1),
memory_bytes: byte_size(5000000000),
storage_bytes: byte_size(14000000000),
Expand All @@ -63,7 +62,7 @@ data github_hosted_ubuntu_slim_private_catalog: GithubHostedRunnerCatalogRow = G

data github_hosted_ubuntu_2404_public_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: Ubuntu2404,
visibility: PublicRepo,
visibility: PublicRepository,
vcpus: hardware_thread_count(4),
memory_bytes: byte_size(16000000000),
storage_bytes: byte_size(14000000000),
Expand All @@ -72,7 +71,7 @@ data github_hosted_ubuntu_2404_public_catalog: GithubHostedRunnerCatalogRow = Gi

data github_hosted_ubuntu_2404_private_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: Ubuntu2404,
visibility: PrivateRepo,
visibility: PrivateRepository,
vcpus: hardware_thread_count(2),
memory_bytes: byte_size(8000000000),
storage_bytes: byte_size(14000000000),
Expand All @@ -81,7 +80,7 @@ data github_hosted_ubuntu_2404_private_catalog: GithubHostedRunnerCatalogRow = G

data github_hosted_ubuntu_2404_arm_public_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: Ubuntu2404Arm,
visibility: PublicRepo,
visibility: PublicRepository,
vcpus: hardware_thread_count(4),
memory_bytes: byte_size(16000000000),
storage_bytes: byte_size(14000000000),
Expand All @@ -90,7 +89,7 @@ data github_hosted_ubuntu_2404_arm_public_catalog: GithubHostedRunnerCatalogRow

data github_hosted_ubuntu_2404_arm_private_catalog: GithubHostedRunnerCatalogRow = GithubHostedRunnerCatalogRow {
runs_on_label: Ubuntu2404Arm,
visibility: PrivateRepo,
visibility: PrivateRepository,
vcpus: hardware_thread_count(2),
memory_bytes: byte_size(8000000000),
storage_bytes: byte_size(14000000000),
Expand Down
74 changes: 74 additions & 0 deletions dag/extdeps/github/repository.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module extdeps.github.repository

import std.types { String, Bool }
import extdeps.github.github { Repository }
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }

data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "docs.github.com/en/rest/repos/repos#get-a-repository"
}
}

// THE SINGLE AUTHORITY FOR WHETHER A GITHUB REPOSITORY IS WORLD-READABLE, cited to the
// `visibility` field of the repository object at the anchor above. It was previously forked:
// extdeps.github.actions_token declared RepositoryVisibility = PublicRepository | PrivateRepository
// for the fork-token rule, and extdeps.github.hosted_runners declared
// RepoVisibility = PublicRepo | PrivateRepo for the runner hardware tables — one concept, two
// spellings, free to disagree (DESIGN §3 nicknaming). Both now import this type.
//
// TWO FURTHER SPELLINGS OF THE SAME FACT SURVIVE AND ARE NOT CONSOLIDATED HERE, stated rather
// than left silent: extdeps.github.github Repository carries `private: Bool` and
// extdeps.github.code_search CodeSearchRepository carries `private: Bool`. Those are the same
// upstream fact spelled as the API's boolean alias, they have a consumer set two orders larger
// than the two coproducts unified here, and folding them in is its own change.
//
// GitHub's `visibility` field admits a third value, `internal`, for repositories inside an
// enterprise account. It is NOT modeled, because no row in this corpus observes one and an arm
// nothing constructs would be a state no evidence supports (DESIGN §4d). What it would take is an
// observed internal repository plus a decision per consumer — the fork-token rule and the runner
// hardware tables each treat public and private differently and neither publishes an internal row.

type RepositoryVisibility
= PublicRepository
| PrivateRepository

// WORLD-READABLE IS THE PREDICATE EVERY CONSUMER ACTUALLY ASKS, so it is spelled once here rather
// than as a match at each site. A consumer that needs the asymmetry itself matches the coproduct.
fn visibility_is_world_readable(visibility: RepositoryVisibility) -> Bool {
match visibility {
PublicRepository => true
PrivateRepository => false
}
}

// GitHub's own spelling of the value, for rendering into a projection that a human reads.
fn repository_visibility_label(visibility: RepositoryVisibility) -> String {
match visibility {
PublicRepository => "public"
PrivateRepository => "private"
}
}

// THE BRIDGE BETWEEN THE UPSTREAM'S TWO SPELLINGS OF ONE FACT, which is where the consolidation
// named above actually begins rather than being promised. GitHub's repository object carries BOTH
// `visibility` and the older boolean `private`, and extdeps.github.github Repository models the
// boolean because that is what the API returns. A consumer that wants the typed answer asks here
// instead of matching on a Bool at its own site, so the mapping exists ONCE.
//
// This is deliberately a function over the EXISTING Repository rather than a second instance type.
// An earlier draft of this module minted a RepositoryVisibilityStanding carrying owner, name and
// visibility, and gunbc.repo_identity authored a row of it for gunb-ai/gunbc -- which was 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 in as many words:
// the answer to five nicknames is not a sixth carrying a promise to consolidate later, it is one
// authority the others CONSUME. The type is deleted and its consumers read Repository.
fn repository_visibility(repository: Repository) -> RepositoryVisibility {
if repository.private { PrivateRepository } else { PublicRepository }
}

fn repository_is_world_readable(repository: Repository) -> Bool {
visibility_is_world_readable(visibility: repository_visibility(repository: repository))
}
3 changes: 2 additions & 1 deletion dag/extdeps/python/python.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module extdeps.python

import std.types { FilePath, String, List }
import std.types { FilePath, List, Unit }
import std.string_type { String }
import std.workspace_artifact { WorkspaceFootprint, WorkspaceArtifact, CitedUpstream, RegenerableFromSource }
import extdeps.external_authority { ExternalAuthority }
import extdeps.uri { Uri, Https }
Expand Down
84 changes: 80 additions & 4 deletions dag/gunbc/gitignore_emit.dag
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import std.workspace_artifact {
footprint_display, ignore_reason_label
}
import gunbc.repo_workspace { repo_workspace_footprints }
import gunbc.workspace_repository_materialisation {
WorkspaceMaterialisation, workspace_materialisations, untracked_materialisations,
materialisation_ignore_reason
}
import gunbc.generated_artifact {
GeneratedArtifact, generated_artifact_registry, artifact_is_committed, artifact_path
}
Expand Down Expand Up @@ -49,11 +53,22 @@ fn footprint_doc(f: WorkspaceFootprint) -> Doc {
doc_concat(parts: append([header], items: map(f.artifacts, a => artifact_doc(a: a))))
}

// THE HEADER NAMES EVERY ARM AND ITS HOME, and it is regenerated with the file rather than
// maintained beside it. A projection's header is a claim about which authorities produced the
// bytes under it; when an arm is added and the header is not, the file keeps attributing itself to
// a model it no longer has, and a reader who trusts it is told that every pattern carries a typed
// std.workspace_artifact IgnoreReason when one whole arm derives its reason from a repository's
// visibility instead. That is prose reading as coverage for a model that is no longer true -- the
// same defect this repository has had to repair in the admission roster's own comments -- so the
// arms are listed here and each new arm owes this function a line.
fn gitignore_header_doc() -> Doc {
doc_concat(parts: [
gitignore_comment_line(text: "gunbc .gitignore — generated from gunbc.repo_workspace via gunbc.gitignore_emit — do not hand-edit."),
gitignore_comment_line(text: "gunbc .gitignore — generated via gunbc.gitignore_emit from gunbc.repo_workspace, gunbc.workspace_repository_materialisation and gunbc.generated_artifact — do not hand-edit."),
gitignore_comment_line(text: ""),
gitignore_comment_line(text: "Every pattern is declared by its producer (an extdeps product's workspace footprint, or a repo tool's own declaration) with its meaning and a typed ignore reason; the trailing section is derived from gunbc.generated_artifact commit policy."),
gitignore_comment_line(text: "THREE ARMS, each with its own producer and its own reason for being untracked."),
gitignore_comment_line(text: "1. Workspace footprints, from gunbc.repo_workspace: an extdeps product's own declaration or a repo tool's, each artifact carrying its meaning and a typed std.workspace_artifact IgnoreReason."),
gitignore_comment_line(text: "2. Material from a repository that is not world-readable, from gunbc.workspace_repository_materialisation joined with each source repository's visibility in gunbc.repository: the pattern is the row's root-anchored prefix and the reason text is DERIVED from that visibility, never authored per row."),
gitignore_comment_line(text: "3. Generated artifacts, derived from gunbc.generated_artifact commit policy."),
doc_blank()
])
}
Expand All @@ -71,6 +86,8 @@ fn expected_gitignore() -> String {
gitignore_header_doc(),
footprints_doc(footprints: repo_workspace_footprints()),
doc_blank(),
non_world_readable_section_doc(),
doc_blank(),
generated_artifacts_section_doc()
])
render(doc: body, proto: gitignore_protocol)
Expand All @@ -86,7 +103,8 @@ fn footprint_patterns(f: WorkspaceFootprint) -> List<String> {

fn ignore_patterns() -> List<String> {
let tool_patterns = fold(repo_workspace_footprints(), init: [], f: fn(acc, f) { concat(acc, footprint_patterns(f: f)) })
concat(tool_patterns, map(non_committed_artifacts(), a => generated_artifact_ignore_pattern(a: a)))
let with_private = concat(tool_patterns, map(non_world_readable_materialisations(), m => materialisation_ignore_pattern(m: m)))
concat(with_private, map(non_committed_artifacts(), a => generated_artifact_ignore_pattern(a: a)))
}

fn generated_artifact_ignore_pattern(a: GeneratedArtifact) -> String {
Expand All @@ -97,6 +115,51 @@ fn non_committed_artifacts() -> List<GeneratedArtifact> {
filter(generated_artifact_registry, a => !artifact_is_committed(a: a))
}

// THE NON-WORLD-READABLE ARM. It is a THIRD derived arm beside the tool footprints and the
// generated-artifact rows, and it authors nothing: the patterns are the prefixes of
// gunbc.workspace_repository_materialisation, the membership test is the source repository's
// visibility, and the comment text is derived from that same visibility. A new overlay spelling is
// a compose row over there, and its ignore row appears here by construction.
fn non_world_readable_materialisations() -> List<WorkspaceMaterialisation> {
untracked_materialisations(all: workspace_materialisations())
}

// THE EMITTED PATTERN IS ANCHORED AND CARRIES NO TRAILING SLASH, and both halves are facts about
// what the row MEANS rather than spelling preferences. Git's pattern rules decide two independent
// questions here (git-scm.com/docs/gitignore, pattern format), and each was got wrong once.
//
// THE LEADING SLASH -- WHAT DEPTH. A materialisation row declares a prefix AT THE WORKSPACE ROOT,
// the compose step lands the repository there and nowhere else, while a pattern with no leading
// slash matches at ANY DEPTH. The unanchored form therefore said something the model does not: it
// also ignored fixtures/private_overlay/example.dag, which no materialisation claims and which may
// be perfectly public.
//
// THE ABSENT TRAILING SLASH -- WHAT KIND OF THING. A pattern ending in `/` matches DIRECTORIES
// ONLY, so `/private_repo/` sails straight past a SYMLINK named private_repo -- measured, it
// stages as mode 120000. A symlink is one of the three observed staging behaviours, so a
// directory-only pattern leaves a real one uncovered. Without the trailing slash the pattern
// matches a file, a directory and a symlink at that path -- all three measured, with the nested
// path still spared -- which is the coverage the row intends.
//
// The row hands over a bare path prefix and this function owns the punctuation, so neither
// question is answerable from the model's side. The generated-artifact arm below anchors by the
// same concat.
fn materialisation_ignore_pattern(m: WorkspaceMaterialisation) -> String {
concat("/", m.prefix)
}

fn materialisation_doc(m: WorkspaceMaterialisation) -> Doc {
doc_concat(parts: [
gitignore_comment_line(text: materialisation_ignore_reason(m: m)),
doc_line_of(text: materialisation_ignore_pattern(m: m))
])
}

fn non_world_readable_section_doc() -> Doc {
let header = gitignore_section_header(title: "Material from a repository that is not world-readable (derived from gunbc.workspace_repository_materialisation and each source repository's visibility)")
doc_concat(parts: append([header], items: map(non_world_readable_materialisations(), m => materialisation_doc(m: m))))
}

fn generated_artifacts_section_doc() -> Doc {
let header = gitignore_section_header(title: "Generated artifacts (gitignored-by-default, derived from generated_artifact_registry)")
let rationale = gitignore_comment_line(text: "Regenerable; not committed. Whitelist: ci.yml, .gitignore, .md docs (ROADMAP.md, DESIGN.md, docs/plans/).")
Expand All @@ -116,6 +179,15 @@ fn path_basename(path: String) -> String {
fold(split(s: path, delimiter: "/"), init: "", f: fn(_, seg) { seg })
}

// A LEADING SLASH ON A DIRECTORY PATTERN BINDS IT TO THE ROOT. Without this arm the dir branch
// below strips the slash and answers the unanchored question for every anchored pattern, so the
// predicate would report a nested fixtures/private_overlay/... path as ignored while the emitted
// .gitignore -- read by git, which honours the anchor -- does not ignore it. Two answers to one
// question, and the wrong one is the one a witness would read.
fn anchored_dir_matches(dir: String, path: String) -> Bool {
path == dir || starts_with(path, prefix: concat(dir, "/"))
}

fn dir_pattern_matches(dir: String, path: String) -> Bool {
let anchored = concat(dir, "/")
path == dir
Expand All @@ -130,7 +202,11 @@ fn strip_slashes(p: String) -> String {

fn pattern_matches(pattern: String, path: String) -> Bool {
if ends_with(s: pattern, suffix: "/") {
dir_pattern_matches(dir: strip_slashes(p: pattern), path: path)
if starts_with(pattern, prefix: "/") {
anchored_dir_matches(dir: strip_slashes(p: pattern), path: path)
} else {
dir_pattern_matches(dir: strip_slashes(p: pattern), path: path)
}
} else if starts_with(pattern, prefix: "*.") {
ends_with(s: path, suffix: without_prefix(s: pattern, prefix: "*"))
} else if starts_with(pattern, prefix: "/") {
Expand Down
Loading
Loading