@bongos/core 1.18.19 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +514 -194
- package/.claude/skills/builder-box/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +1 -1
- package/.claude/skills/planning-session/SKILL.md +5 -5
- package/.claude/skills/scan-before-install/SKILL.md +165 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +63 -41
- package/clients/bongos-client/index.cjs +63 -41
- package/clients/bongos-client/index.d.ts +78 -44
- package/clients/bongos-client/index.mjs +63 -41
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -0
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +2 -1
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +51 -0
- package/docs/adr/0156-user-set-display-name-wins.md +62 -0
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +93 -0
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +174 -0
- package/docs/adr/0159-merge-gate-reconciliation.md +88 -0
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +77 -0
- package/docs/adr/README.md +8 -0
- package/docs/api/openapi.json +1209 -233
- package/docs/api-reference.md +61 -46
- package/docs/architecture.md +1 -1
- package/docs/canonical-permissions.md +61 -16
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +4 -1
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/recipes/core-release-pipeline.md +179 -0
- package/docs/recipes/packaging-the-core.md +2 -0
- package/docs/recipes/parallel-goal-waves.md +1 -3
- package/docs/recipes/private-npm-distribution.md +2 -0
- package/docs/recipes/upgrading-the-core.md +2 -0
- package/migrations/core_197_display_name_source.sql +56 -0
- package/migrations/core_204_rekey_active_claim_per_session_worktree.sql +77 -0
- package/modules/autonomy/routes/autonomy.js +1 -1
- package/modules/builder-settings/db.js +7 -1
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +63 -41
- package/modules/dev-box/routes/box.js +11 -9
- package/modules/discord/auth.js +3 -1
- package/modules/discord/routes/discord.js +8 -2
- package/modules/discord/ship-broadcast.js +48 -1
- package/modules/governance/catalog.js +70 -20
- package/modules/governance/db.js +102 -0
- package/modules/governance/migrations/governance_006_version_create.sql +38 -0
- package/modules/governance/migrations/governance_007_delegate_archon_ops_to_metic.sql +72 -0
- package/modules/governance/routes/governance.js +235 -6
- package/modules/grading/grader-workers/index.js +18 -9
- package/modules/hall-ui/public/gate.js +6 -2
- package/modules/hall-ui/public/goals-page.js +6 -2
- package/modules/hall-ui/public/governance.css +100 -0
- package/modules/hall-ui/public/governance.html +77 -0
- package/modules/hall-ui/public/governance.js +353 -0
- package/modules/hall-ui/public/harbor.js +14 -8
- package/modules/hall-ui/public/instances-panel.js +3 -1
- package/modules/hall-ui/public/new-project-wizard.js +6 -0
- package/modules/hall-ui/public/people.html +51 -0
- package/modules/hall-ui/public/people.js +100 -0
- package/modules/hall-ui/public/profile.html +2 -1
- package/modules/hall-ui/public/profile.js +9 -3
- package/modules/hall-ui/public/sessions.js +4 -2
- package/modules/hall-ui/public/shell.js +28 -7
- package/modules/hall-ui/public/task-detail.js +6 -3
- package/modules/hall-ui/public/watch.js +17 -9
- package/modules/ideas/blockers.js +44 -7
- package/modules/ideas/routes/blockers.js +12 -7
- package/modules/ideas/routes/inbox.js +2 -2
- package/modules/lifecycle/db.js +84 -17
- package/modules/lifecycle/github-push.js +37 -10
- package/modules/lifecycle/publish-reconciler.js +128 -31
- package/modules/lifecycle/routes/claims.js +10 -4
- package/modules/lifecycle/routes/dependencies.js +2 -2
- package/modules/lifecycle/routes/done-when.js +4 -4
- package/modules/lifecycle/routes/gate-approvals.js +9 -3
- package/modules/lifecycle/routes/goals.js +5 -5
- package/modules/lifecycle/routes/tasks.js +16 -16
- package/modules/lifecycle/routes/versions.js +13 -2
- package/modules/lifecycle/ship-card.js +38 -7
- package/modules/memory/routes/memory.js +2 -2
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/routes/projects.js +10 -6
- package/modules/platform-identity/routes/scouting.js +2 -2
- package/modules/provisioning/migrations/provisioning_004_onboard_mode.sql +30 -0
- package/modules/provisioning/provisioning.js +24 -6
- package/modules/provisioning/routes/provisioning.js +24 -6
- package/modules/security/artifact-scan/aggregate.js +133 -0
- package/modules/security/artifact-scan/detectors/capabilities.js +53 -0
- package/modules/security/artifact-scan/detectors/config-obfuscation.js +41 -0
- package/modules/security/artifact-scan/detectors/credentials.js +112 -0
- package/modules/security/artifact-scan/detectors/dependencies.js +115 -0
- package/modules/security/artifact-scan/detectors/hooks.js +116 -0
- package/modules/security/artifact-scan/detectors/injection.js +118 -0
- package/modules/security/artifact-scan/detectors/mcp.js +102 -0
- package/modules/security/artifact-scan/detectors/network.js +67 -0
- package/modules/security/artifact-scan/detectors/npm-lifecycle.js +67 -0
- package/modules/security/artifact-scan/detectors/permissions.js +91 -0
- package/modules/security/artifact-scan/detectors/shared.js +219 -0
- package/modules/security/artifact-scan/fence-local.js +99 -0
- package/modules/security/artifact-scan/fetch.js +330 -0
- package/modules/security/artifact-scan/floor.js +319 -0
- package/modules/security/artifact-scan/limits.js +66 -0
- package/modules/security/artifact-scan/manifest.js +203 -0
- package/modules/security/artifact-scan/mirror.js +158 -0
- package/modules/security/artifact-scan/quarantine.js +149 -0
- package/modules/security/artifact-scan/rebuild-spec.js +120 -0
- package/modules/security/artifact-scan/report.js +155 -0
- package/modules/security/artifact-scan/sanitize.js +317 -0
- package/modules/security/migrations/security_001_tool_scans.sql +145 -0
- package/modules/security/module.json +2 -2
- package/modules/security/routes/reports.js +11 -7
- package/modules/security/routes/scans.js +115 -0
- package/modules/security/scan-store.js +214 -0
- package/modules/security/tests/aggregate.mjs +283 -0
- package/modules/security/tests/artifact_scan.mjs +591 -0
- package/modules/security/tests/floor.mjs +549 -0
- package/modules/security/tests/mirror.mjs +350 -0
- package/modules/security/tests/sanitize_rebuild.mjs +499 -0
- package/modules/security/tests/scan_store.mjs +250 -0
- package/modules/security/tests/tool_scans_migration.mjs +116 -0
- package/modules/sessions/routes/sessions.js +6 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/create.html +13 -3
- package/scripts/gds/abandon.js +1 -1
- package/scripts/gds/audit-authorship.sh +6 -0
- package/scripts/gds/box.js +1 -1
- package/scripts/gds/claim.js +15 -0
- package/scripts/gds/cli-lib.js +16 -4
- package/scripts/gds/gating.js +28 -13
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/goal.js +234 -0
- package/scripts/gds/init.js +70 -15
- package/scripts/gds/main-audit.js +80 -11
- package/scripts/gds/mirror-redact.js +6 -0
- package/scripts/gds/newcomer-floor.js +2 -2
- package/scripts/gds/provision.js +230 -94
- package/scripts/gds/run-unit-tests.js +9 -1
- package/scripts/gds/sandbox-stage.js +66 -9
- package/scripts/gds/search-ingest-md.js +1 -0
- package/scripts/gds/sequence.js +67 -8
- package/scripts/gds/ship.js +183 -50
- package/scripts/hall-preview/server.js +1 -1
- package/scripts/migrate.sh +5 -5
- package/src/bongos/auth.js +129 -27
- package/src/bongos/db-kernel.js +66 -5
- package/src/bongos/db.js +6 -7
- package/src/bongos/github-repos.js +19 -7
- package/src/bongos/goal-scope-check.js +1 -1
- package/src/bongos/module-scope-map.js +3 -1
- package/src/bongos/permission-path-check.js +1 -1
- package/src/bongos/route-rank-check.js +73 -30
- package/src/bongos/routes/audit-log.js +4 -2
- package/src/bongos/routes/auth.js +8 -4
- package/src/bongos/routes/backup.js +7 -4
- package/src/bongos/routes/builders.js +23 -11
- package/src/bongos/routes/github.js +15 -4
- package/src/bongos/routes/llm-cache.js +5 -2
- package/src/bongos/routes/modules.js +9 -5
- package/src/bongos/routes/security.js +21 -12
- package/src/bongos/serve-internal.js +22 -7
- package/src/module-api.js +86 -13
- package/src/platform-server.js +14 -0
- package/tests/api_client.mjs +18 -2
- package/tests/api_path_404.mjs +3 -0
- package/tests/art_stage.mjs +2 -1
- package/tests/atlas.mjs +32 -4
- package/tests/auth_admission_gate.mjs +17 -5
- package/tests/auth_error_brand.mjs +41 -21
- package/tests/auth_resolve_failure.mjs +155 -0
- package/tests/blocker_link.mjs +88 -0
- package/tests/bongos_downloads.mjs +2 -1
- package/tests/box_dns_repoint.mjs +2 -0
- package/tests/box_ship_permissions.mjs +2 -0
- package/tests/box_terminal_ssrf.mjs +23 -17
- package/tests/claim_error_surface.mjs +23 -0
- package/tests/claude_materialize.mjs +4 -0
- package/tests/cli_box_scope.mjs +40 -0
- package/tests/cloud_bongos_pack.mjs +34 -4
- package/tests/conductor.mjs +2 -1
- package/tests/criterion_authoring_gate.mjs +28 -5
- package/tests/currency_label.mjs +10 -1
- package/tests/deploy_instance.mjs +2 -0
- package/tests/devbox_downloads.mjs +2 -1
- package/tests/display_name.mjs +95 -6
- package/tests/eviction_policy.mjs +2 -1
- package/tests/file_map.mjs +10 -0
- package/tests/first_admin_bootstrap.mjs +10 -2
- package/tests/first_run_engine_check.mjs +2 -1
- package/tests/fitness.mjs +26 -6
- package/tests/gate_approvals.mjs +15 -2
- package/tests/github_push_land_proof.mjs +50 -4
- package/tests/github_repos_helpers.mjs +30 -5
- package/tests/github_token_store.mjs +23 -0
- package/tests/goal_abuse_matrix.mjs +13 -3
- package/tests/goal_cli.mjs +89 -0
- package/tests/governance_abuse_matrix.mjs +386 -0
- package/tests/governance_fold.mjs +156 -0
- package/tests/governance_fold_bench.mjs +50 -0
- package/tests/governance_live_proof.mjs +201 -0
- package/tests/governance_page_gates.mjs +21 -9
- package/tests/governance_parity.mjs +11 -2
- package/tests/governance_require_permission.mjs +125 -1
- package/tests/governance_routes.mjs +334 -0
- package/tests/grader.mjs +7 -3
- package/tests/hall_nav.mjs +14 -4
- package/tests/helpers.mjs +73 -0
- package/tests/hook_core_resolve.mjs +2 -1
- package/tests/host_topology_skips.mjs +97 -0
- package/tests/infra_exec_paths.mjs +14 -4
- package/tests/init.mjs +128 -0
- package/tests/known_failing_ratchet.mjs +89 -0
- package/tests/lifecycle-card.mjs +2 -1
- package/tests/main_audit.mjs +72 -1
- package/tests/memory_isolation.mjs +56 -9
- package/tests/migrate_root_resolution.mjs +1 -1
- package/tests/mirror_redact.mjs +19 -0
- package/tests/module_api.mjs +3 -1
- package/tests/module_loader.mjs +4 -0
- package/tests/modules.mjs +35 -7
- package/tests/modules_route.mjs +29 -5
- package/tests/onboarding_observability.mjs +49 -11
- package/tests/onboarding_route_signals.mjs +1 -0
- package/tests/one_active_claim_per_session.mjs +80 -0
- package/tests/pre_compact.mjs +2 -1
- package/tests/preview_server.mjs +2 -1
- package/tests/profile_route.mjs +1 -0
- package/tests/provision.mjs +170 -0
- package/tests/provisioning_onboard_mode.mjs +180 -0
- package/tests/publish_manifest.mjs +16 -3
- package/tests/publish_reconciler.mjs +181 -10
- package/tests/rank_tier_single_source.mjs +205 -0
- package/tests/recall_hints.mjs +2 -1
- package/tests/repo_map.mjs +4 -0
- package/tests/route_permission_check.mjs +14 -5
- package/tests/rubric_fixtures.mjs +3 -0
- package/tests/run_routine_two_root.mjs +2 -0
- package/tests/sandbox_stage.mjs +69 -7
- package/tests/search_ingest.mjs +7 -0
- package/tests/security_docs.mjs +28 -6
- package/tests/sequence.mjs +64 -0
- package/tests/session-cards.mjs +2 -1
- package/tests/session_start_freshness.mjs +2 -1
- package/tests/ship_broadcast_server.mjs +63 -1
- package/tests/ship_card.mjs +66 -0
- package/tests/ship_ci_deploy.mjs +66 -1
- package/tests/ship_smoke_gate.mjs +76 -0
- package/tests/versions_route.mjs +15 -0
- package/tests/workflow_exec_paths.mjs +16 -4
- package/tests/worktree_guard.mjs +2 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# 0158 — The choke-point verdict taxonomy, and why a verdict never executes itself
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-06
|
|
5
|
+
- **Deciders:** jaxri (Archon), Claude
|
|
6
|
+
- **Track:** `internal` (GDS / methodology)
|
|
7
|
+
- **Task:** [1002598](https://cloudbongos.com/builders#/task/1002598), under goal [1000024](https://cloudbongos.com/builders#/goal/1000024) (Build-pipeline integrity)
|
|
8
|
+
|
|
9
|
+
**Builds on:** [ADR 0016](<redacted>.md) (the server-enforced trust boundary — **unchanged**), [ADR 0043](<redacted>.md) (the out-of-band substrate), [ADR 0151](<redacted>.md) (permissions as atoms), [ADR 0157](<redacted>.md) (the delegation this taxonomy generalises the *reasoning* of).
|
|
10
|
+
|
|
11
|
+
**Produces:** [`docs/governance/<redacted>.md`](../governance/<redacted>.md).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Context
|
|
16
|
+
|
|
17
|
+
ADR 0157 delegated 20 permissions from Archon to Metic on a specific observation: each had been
|
|
18
|
+
Archon-gated by ADR 0016's fail-closed default, *never by a decision*. That reasoning was applied once,
|
|
19
|
+
in bulk, to a known backlog. It was not written down as a **method** — so the next person facing the
|
|
20
|
+
same question has to rediscover it.
|
|
21
|
+
|
|
22
|
+
Two things made the question tractable in 2026-08, and both are new:
|
|
23
|
+
|
|
24
|
+
1. **A second Archon exists.** Until 2026-08-06 there was exactly one, so *"needs an Archon"* and
|
|
25
|
+
*"needs Lars"* were the same sentence. They are now different sentences, and a large share of past
|
|
26
|
+
escalations reclassify on that distinction alone.
|
|
27
|
+
2. **The floor is declared once.** Post-ADR-0151, a floor lives in `modules/governance/catalog.js`
|
|
28
|
+
rather than at 41 call sites, so classifying a wall is cheap enough to do exhaustively.
|
|
29
|
+
|
|
30
|
+
The audit that produced the choke-point map found that the hard part is not *locating* walls — greps
|
|
31
|
+
and route censuses do that. The hard part is deciding whether a located wall **belongs there**, and
|
|
32
|
+
the codebase actively misleads on that question. Four comments and doc passages were found stating
|
|
33
|
+
something the code does not do: two justify a live fail-open on a premise that has since become false
|
|
34
|
+
(**D-4**, **D-6**), one asserts an enforcement mechanism that has never existed in the repository
|
|
35
|
+
(**D-2**), and one misstates its own fail-direction, claiming a permissiveness the code does not have
|
|
36
|
+
(**D-5**). A method that reads the code's self-description gets the wrong answer — in both directions.
|
|
37
|
+
|
|
38
|
+
It also found the reverse failure. Three walls the trust model describes as live are not enforced at
|
|
39
|
+
all (**G-2**, **G-3**, **G-6**), and one is enforced by a mechanism other than the one the model names
|
|
40
|
+
(**G-7**). "Where is the wall?" and "what does the model claim about the wall?" are independent
|
|
41
|
+
questions and must be answered independently.
|
|
42
|
+
|
|
43
|
+
## Decision
|
|
44
|
+
|
|
45
|
+
### 1. Six verdicts, ordered, first match wins
|
|
46
|
+
|
|
47
|
+
| Verdict | Test | Remedy shape |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| **GAP** | Nothing enforces this where the trust model implies something should | Add the gate at the correct floor |
|
|
50
|
+
| **UNINTENDED-MISSING-SURFACE** | The rank *should* be able to do this, but no API path exists at all | Add the endpoint at the rank already granted |
|
|
51
|
+
| **DRIFT** | Docs and code, or two code paths, disagree about where the wall is | Fix the wrong side, add a regression check |
|
|
52
|
+
| **STRUCTURAL** | Needs a resource only the human owner holds — money, legal identity, a credential | Owner acts, or pre-delegates a scoped credential |
|
|
53
|
+
| **INTENDED** | The wall sits exactly where the stated trust model says | None — but reduce friction if hit often |
|
|
54
|
+
| **UNINTENDED-OVERTIGHT** | A real wall, gated higher than the model's own intent requires | Lower the floor: code + migration + ADR |
|
|
55
|
+
|
|
56
|
+
**Order is load-bearing.** GAP precedes everything because an unenforced wall is not a floor question
|
|
57
|
+
at all — arguing about its rank while nothing enforces it is the more expensive mistake.
|
|
58
|
+
UNINTENDED-OVERTIGHT is last because it is the only verdict whose remedy *weakens* a boundary; it must
|
|
59
|
+
never be reached by default, only after the five cheaper explanations are excluded.
|
|
60
|
+
|
|
61
|
+
**DRIFT is never terminal.** A DRIFT row must name the category it collapses into once resolved.
|
|
62
|
+
Otherwise "the docs disagree" becomes a place findings go to be filed and forgotten.
|
|
63
|
+
|
|
64
|
+
### 2. A verdict is advisory and never self-executing
|
|
65
|
+
|
|
66
|
+
**This is the load-bearing rule of this ADR.** Recording `UNINTENDED-OVERTIGHT` is **not**
|
|
67
|
+
authorization to lower a floor. It is a finding that a floor *may* be wrong, addressed to a human.
|
|
68
|
+
|
|
69
|
+
Lowering a floor still requires, in order: an owner decision, a `catalog.js` change, a migration (the
|
|
70
|
+
`governance_002` compat seed is run-once, so an edited `floor:` never reaches an already-migrated
|
|
71
|
+
database — ADR 0157 §Consequences), and an ADR recording why. A governance document that could move a
|
|
72
|
+
floor by asserting a verdict would be a privilege-escalation path through markdown — precisely what
|
|
73
|
+
ADR 0016 exists to prevent. **The audit describes; the database enforces.**
|
|
74
|
+
|
|
75
|
+
The same rule inverted: a verdict of `INTENDED` does not immunize a wall from later review, and
|
|
76
|
+
`GAP` does not authorize adding a gate wherever seems reasonable. Both directions go through the same
|
|
77
|
+
door.
|
|
78
|
+
|
|
79
|
+
### 3. Every row carries a rigor marker
|
|
80
|
+
|
|
81
|
+
A verdict without evidence is an opinion. Four markers, and a row may not exist without one:
|
|
82
|
+
|
|
83
|
+
- **`obs`** — observed by executing the code path.
|
|
84
|
+
- **`read`** — established by reading the code, cited `file:line`.
|
|
85
|
+
- **`absent`** — established by the absence of a file, caller, or route. The negative *is* the finding.
|
|
86
|
+
- **`unver`** — cannot be settled from the checkout. Named explicitly, never guessed.
|
|
87
|
+
|
|
88
|
+
`absent` earns its own marker because the two most consequential findings in the first map are
|
|
89
|
+
absences — a hook that was never in git history, and an "authoritative" audit script with no caller.
|
|
90
|
+
Neither is visible to any technique that reads code that exists.
|
|
91
|
+
|
|
92
|
+
`unver` earns its own marker because the alternative is silent optimism. A map that quietly assumes an
|
|
93
|
+
unverifiable enforcement layer is running reads as *"secure"* when the honest reading is
|
|
94
|
+
*"unknown"*.
|
|
95
|
+
|
|
96
|
+
### 4. Rank is not identity — state the Archon count
|
|
97
|
+
|
|
98
|
+
An audit of this kind must record **how many Archons existed when it ran, and how many of them were
|
|
99
|
+
the owner**, because the classification depends on both. A choke point whose true cause was "only one
|
|
100
|
+
Archon existed" is **RESOLVED** by a second one existing — not an open remedy. Conflating the two
|
|
101
|
+
inflates the remedy list with work that is already done, and hides the real trust boundaries inside
|
|
102
|
+
the noise.
|
|
103
|
+
|
|
104
|
+
The second count is what keeps STRUCTURAL honest. "Requires Archon rank" is satisfiable by anyone the
|
|
105
|
+
owner promotes; "requires the owner" — money, a legal identity, a credential held by one person — is
|
|
106
|
+
satisfiable by exactly one, and **no rank change widens it**. Rank is grantable; being the owner is
|
|
107
|
+
not. An audit that collapses those two produces STRUCTURAL rows that a promotion would have cleared,
|
|
108
|
+
which is the same error in the opposite direction: it tells the owner they are needed when they are
|
|
109
|
+
not.
|
|
110
|
+
|
|
111
|
+
### 5. Two methodological constraints on any future probe
|
|
112
|
+
|
|
113
|
+
Both were discovered the expensive way and are cheap to honor:
|
|
114
|
+
|
|
115
|
+
- **An Archon cannot audit the ownership walls.** `gateTaskOwnership` short-circuits on
|
|
116
|
+
`rank === 'archon'` before the ownership lookup, so an Archon-authenticated probe passes all 7
|
|
117
|
+
routes it guards and observes no wall. Any probe **must** seed sub-Archon identities. This is also
|
|
118
|
+
why the single-Archon era hid a whole class of wall rather than merely queueing it.
|
|
119
|
+
- **Send two bodies per write.** Of 29 in-handler-gated routes, 16 return `400`/`422` on an empty `{}`
|
|
120
|
+
body *before* the authorization check. A `{}`-only sweep concludes there are no in-handler walls —
|
|
121
|
+
the exact opposite of the truth. Send `{}` for containment and a schema-minimal valid body for
|
|
122
|
+
classification, and assert the in-handler count is non-zero or treat the run as invalid.
|
|
123
|
+
|
|
124
|
+
### 6. An audit document carries no reproduction detail for an unpatched finding
|
|
125
|
+
|
|
126
|
+
An authority audit necessarily locates live flaws. Writing the *mechanics* of an unpatched one into a
|
|
127
|
+
repository document publishes a working recipe to whichever ranks can read that repository — which,
|
|
128
|
+
for a finding about sub-Metic builders, is precisely the population able to exploit it. The asymmetry
|
|
129
|
+
is stark: the finding's *existence* is what the owner needs, and the finding's *mechanics* are what an
|
|
130
|
+
attacker needs.
|
|
131
|
+
|
|
132
|
+
So: **existence, verdict, and severity go in the audit; reproduction goes in the security-report
|
|
133
|
+
surface**, which is gated on `security.report.adjudicate`. Once a finding is fixed, it may be
|
|
134
|
+
documented in full — the restriction is about the unpatched window, not about secrecy.
|
|
135
|
+
|
|
136
|
+
This rule was learned the hard way on the first map: the adversarial grader flagged its G-1 row as
|
|
137
|
+
information disclosure, correctly. Two facts made it land rather than stay contained — `docs/governance/`
|
|
138
|
+
is absent from `SENSITIVE_DOC_GLOBS`, so the search layer serves such documents at `xenos`, and
|
|
139
|
+
ADR 0035's Metic+ local-clone tightening has not shipped, so sub-Metic builders can hold a full
|
|
140
|
+
checkout. Neither is the audit's doing; both mean an audit cannot assume its own containment. **An
|
|
141
|
+
audit document must assume its most adversarial plausible reader is inside its stated threat model.**
|
|
142
|
+
|
|
143
|
+
## Consequences
|
|
144
|
+
|
|
145
|
+
- **The taxonomy is reusable.** The next authority audit classifies against this table instead of
|
|
146
|
+
inventing categories, and its rows are comparable to the first map's.
|
|
147
|
+
- **Verdicts are safe to write down.** Because §2 makes them inert, an auditor can record
|
|
148
|
+
`UNINTENDED-OVERTIGHT` honestly without the document becoming a lever. This is what lets the audit
|
|
149
|
+
be blunt.
|
|
150
|
+
- **`absent` and `unver` are first-class.** Two of the highest-severity findings in the first map are
|
|
151
|
+
absences, and the single sharpest structural finding is an `unver`: the layer ADR 0043 designates
|
|
152
|
+
authoritative lives entirely outside the repository that documents it, so the trust model cannot be
|
|
153
|
+
audited from the artifact that states it. A taxonomy without those markers would have recorded
|
|
154
|
+
silence as safety.
|
|
155
|
+
- **ADR 0016 and ADR 0043 are untouched.** This ADR adds a vocabulary for *describing* the boundary.
|
|
156
|
+
It moves no floor, adds no gate, and changes no enforcement.
|
|
157
|
+
- **A cost.** Hand-written verdicts do not regenerate. The map goes stale as line numbers drift, and
|
|
158
|
+
its own "keeping this true" section is the only mechanism keeping it honest. That is accepted: a
|
|
159
|
+
script can tell you where a gate is, never whether it belongs there.
|
|
160
|
+
|
|
161
|
+
## Alternatives considered
|
|
162
|
+
|
|
163
|
+
- **Generate the map from the route census.** Rejected. A census answers "where is the gate," which
|
|
164
|
+
is the easy half. It cannot answer "should it be there," and a generated document would imply it had.
|
|
165
|
+
- **Fold the verdicts into `canonical-permissions.md`.** Rejected. That file is a mirror of the
|
|
166
|
+
enforced model; mixing *judgments about* the model into the *description of* it is how a doc starts
|
|
167
|
+
reading as authority. Kept in `docs/governance/`, which is explicitly interpretive.
|
|
168
|
+
- **Let an `UNINTENDED-OVERTIGHT` verdict open a task automatically.** Rejected, and it is the same
|
|
169
|
+
hazard as §2 one step removed. An auto-filed task to lower a floor is a queued privilege change
|
|
170
|
+
nobody decided. A human reads the map and files what they choose.
|
|
171
|
+
- **Three verdicts instead of six** (intended / not-intended / owner-only). Rejected: it collapses
|
|
172
|
+
GAP into UNINTENDED-OVERTIGHT, which inverts the remedy. An unenforced wall needs a gate *added*;
|
|
173
|
+
an over-tight wall needs a floor *lowered*. Merging them produces exactly the wrong action half the
|
|
174
|
+
time.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 0159 — Merge-gate reconciliation: required checks match reality, CODEOWNERS becomes real, protection stays the owner's switch
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-08
|
|
5
|
+
- **Deciders:** jaxri (Archon), Claude
|
|
6
|
+
- **Track:** `internal` (build pipeline / governance)
|
|
7
|
+
- **Task:** [1002611](https://cloudbongos.com/builders#/task/1002611), under goal [1000024](https://cloudbongos.com/builders#/goal/1000024) (Build-pipeline integrity)
|
|
8
|
+
|
|
9
|
+
**Builds on:** [ADR 0042](<redacted>.md) (CI auto-merge; described Code-Owner review as a live control), [ADR 0058](<redacted>.md) (the gate-review phase plan — **unchanged**; its workflows remain planned), [ADR 0158](<redacted>.md) (the verdict taxonomy; findings D-12 and G-5 in the [choke-point map](../governance/<redacted>.md)).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
The unit CI lane now exists (task 1002610): every PR and every push to `main` runs the DB-free
|
|
16
|
+
suite + the architecture fitness gates as a status-check context named exactly `unit`. Before
|
|
17
|
+
that task, no workflow could report any of the three contexts `gating.js` hardcodes as required.
|
|
18
|
+
|
|
19
|
+
Two findings remained:
|
|
20
|
+
|
|
21
|
+
- **D-12** — `gating.js` (the owner kill-switch for branch protection) hardcoded
|
|
22
|
+
`required_status_checks` for `unit`, `trufflehog` and `gate-review`. Two of the three still
|
|
23
|
+
have no workflow. Running `gating.js on` would require contexts nothing can report, blocking
|
|
24
|
+
every merge indefinitely rather than gating them. The same misfire has a second shape: a
|
|
25
|
+
required context that *reports red on every PR* also blocks every merge — and the lane's
|
|
26
|
+
first-ever runs surfaced exactly that (three tests red in every clean environment, plus a
|
|
27
|
+
shallow-clone break in the lane itself).
|
|
28
|
+
- **G-5** — no CODEOWNERS existed anywhere in the tree, while ADR 0042:77 describes Code-Owner
|
|
29
|
+
review as a live control, `protected-surfaces.json` lists `CODEOWNERS` as a protected glob,
|
|
30
|
+
`publish-manifest.js` excludes it from the public mirror, and `gating.js` claims in its own
|
|
31
|
+
header to be "owner-gated in CODEOWNERS".
|
|
32
|
+
|
|
33
|
+
## Decision
|
|
34
|
+
|
|
35
|
+
1. **`DEFAULT_PROTECTION` requires only contexts that exist and can pass today — `[unit]`.**
|
|
36
|
+
The planned contexts return to the baked-in default when their workflows ship, not before.
|
|
37
|
+
|
|
38
|
+
2. **The future check names are fixed now, deliberately:** `secret-scan` and `gate-review` —
|
|
39
|
+
the names `adopt-preflight.js` (`CORE_EXPECTED_CHECKS`) and `init.js` already present as the
|
|
40
|
+
core's contract. `trufflehog` (the tool) stops appearing as a context name; a context is
|
|
41
|
+
named for what it asserts, not for the tool that asserts it, so the tool can be swapped
|
|
42
|
+
without a branch-protection edit.
|
|
43
|
+
|
|
44
|
+
3. **CODEOWNERS becomes a real root file**, scoped to `gate-review.js`'s `HARD_FLOOR_GLOBS`
|
|
45
|
+
(the surfaces whose auto-merge would disable the safety system itself) plus `gating.js`.
|
|
46
|
+
It is inert until branch protection is on — GitHub enforces it only via
|
|
47
|
+
`require_code_owner_reviews` — so shipping it changes nothing operationally today; it makes
|
|
48
|
+
the already-written descriptions true and arms the owner's switch.
|
|
49
|
+
|
|
50
|
+
4. **Branch protection stays OFF, and flipping it on remains the owner's explicit act**
|
|
51
|
+
(`node scripts/gds/gating.js on`). This task makes that switch safe to use; it does not use
|
|
52
|
+
it. (An ADR 0158 principle applied: a finding's remedy is never self-executing.)
|
|
53
|
+
|
|
54
|
+
5. **The unit lane is hardened so `unit` is actually requirable:** `fetch-depth: 0` (the
|
|
55
|
+
default depth-1 clone broke `tests/main_audit.mjs`, which resolves real first-parent ranges).
|
|
56
|
+
Of the three tests red in every clean environment, two — `platform_boot` and
|
|
57
|
+
`served_paths_shipped` — are parked in `KNOWN_FAILING` with per-test diagnoses in task
|
|
58
|
+
1002617; `publish_manifest` guards the public-mirror boundary, so it was **fixed here
|
|
59
|
+
instead of parked** (grader catch): its "must see ≥7 host tests" non-vacuity floor went
|
|
60
|
+
stale the moment ADR 0107 moved the host-module tests into `modules/<key>/tests/`, and the
|
|
61
|
+
floor is now a regex specimen self-test that cannot go stale with the module roster.
|
|
62
|
+
Parking is quarantine with a tracked exit, not acceptance: the gate must be green on a
|
|
63
|
+
good tree before it can be required on every tree.
|
|
64
|
+
|
|
65
|
+
## Consequences
|
|
66
|
+
|
|
67
|
+
- `gating.js on` now produces a protection config that gates instead of wedging. The owner can
|
|
68
|
+
flip it whenever they choose; nothing forces the flip.
|
|
69
|
+
- When the `secret-scan` or `gate-review` workflow lands, its author must add the context back
|
|
70
|
+
to `DEFAULT_PROTECTION` (the comment there says so) — until then the kill-switch's
|
|
71
|
+
`GATE_WORKFLOWS` toggles no-op gracefully on the absent files.
|
|
72
|
+
- CODEOWNERS and `HARD_FLOOR_GLOBS` are now two expressions of the same set and must be kept
|
|
73
|
+
aligned by hand when either changes; both files say so at the top.
|
|
74
|
+
- The two parked tests are the debt this ADR takes on; task 1002617 is the ledger entry.
|
|
75
|
+
- `audit-authorship.sh` allowlists CODEOWNERS: GitHub's format requires the literal
|
|
76
|
+
`@username`, which is repo-hosting identity (the provision-staging.sh precedent), not a
|
|
77
|
+
hardcoded builder identity.
|
|
78
|
+
|
|
79
|
+
## Rejected
|
|
80
|
+
|
|
81
|
+
- **Deleting `GATE_WORKFLOWS` / the gate-review references from gating.js** — the phantom-name
|
|
82
|
+
objection (the G-20 shape) loses to forward compatibility here: the switch handles absence
|
|
83
|
+
gracefully, ADR 0058's phase plan is unchanged, and removing the names would mean the
|
|
84
|
+
kill-switch silently stops covering the gate workflows the moment they ship.
|
|
85
|
+
- **Stopping describing CODEOWNERS instead of creating it** — the describing surfaces
|
|
86
|
+
(ADR 0042, the protected-surface registry, the mirror manifest, gating.js) all assume it as
|
|
87
|
+
the one human backstop in an auto-merging pipeline; watering four descriptions down is more
|
|
88
|
+
churn than making the one file real, and leaves the pipeline with no owner-review lever at all.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 0160 — The authoritative main audit gets a repo-verifiable caller
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-08
|
|
5
|
+
- **Deciders:** jaxri (Archon), Claude
|
|
6
|
+
- **Track:** `internal` (build pipeline / governance)
|
|
7
|
+
- **Task:** [1002612](https://cloudbongos.com/builders#/task/1002612), under goal [1000024](https://cloudbongos.com/builders#/goal/1000024) (Build-pipeline integrity)
|
|
8
|
+
|
|
9
|
+
**Builds on:** [ADR 0043](<redacted>.md) (designates the post-push audit the authoritative layer — **unchanged**), [ADR 0159](<redacted>.md) (the merge-gate reconciliation this is the detective-lane companion to), [ADR 0158](<redacted>.md) (findings G-3, G-8, S-1).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
`scripts/gds/main-audit.js` reads the real git history on `main` and the real builder ranks
|
|
16
|
+
and flags sub-Metic edits to permission-sensitive surfaces — the layer ADR 0043 calls
|
|
17
|
+
**authoritative**. Both of its declared invokers (`.claude/scheduled-tasks/main-audit/`,
|
|
18
|
+
`infra/deploy.sh`) have never existed in this repository (G-3). The honest limit S-1 stands:
|
|
19
|
+
the production droplet may run it from an untracked deploy script, which cannot be settled
|
|
20
|
+
from this checkout — so the remedy is a caller that **is** verifiable from the repo, not an
|
|
21
|
+
assumption about the droplet either way. Same shape, same pass (G-8):
|
|
22
|
+
`scripts/gds/audit-authorship.sh` — the no-hardcoded-Archon-identity enforcer named by
|
|
23
|
+
CLAUDE.md and canonical-permissions.md — also had no programmatic caller, and turned out to
|
|
24
|
+
be red with 340 pre-existing hits on untouched main (idea 1000653).
|
|
25
|
+
|
|
26
|
+
## Decision
|
|
27
|
+
|
|
28
|
+
1. **A `main-audit` workflow runs on every push to `main`** (plus `workflow_dispatch`),
|
|
29
|
+
auditing exactly the pushed range via `OTB_AUDIT_RANGE=before..after` from the push
|
|
30
|
+
event, falling back to the script's clamped first-parent window when `before` is a zero
|
|
31
|
+
or unknown SHA. It is a **detective lane, not a merge gate** — it must not join the
|
|
32
|
+
required PR contexts (`unit` / `secret-scan` / `gate-review`, ADR 0159).
|
|
33
|
+
|
|
34
|
+
2. **The roster comes from the public leaderboard when no session exists.**
|
|
35
|
+
`fetchRankMap` falls back to `GET /api/gds/public/leaderboard` — server-authoritative,
|
|
36
|
+
unauthenticated, and it carries exactly the two fields the audit keys on
|
|
37
|
+
(`github_login`, `rank` — login-only keying per the anti-aliasing rule). A builder
|
|
38
|
+
absent from the leaderboard resolves as *unresolved* ('review' finding), never as
|
|
39
|
+
falsely trusted.
|
|
40
|
+
|
|
41
|
+
3. **`--require-roster` makes an empty roster a loud DID-NOT-RUN (exit 2).** With no
|
|
42
|
+
ranks, every author is unresolved and a blocker can never fire — a green exit there is
|
|
43
|
+
a check that did not execute wearing the costume of a clean audit. The CI caller always
|
|
44
|
+
passes this flag; local best-effort reads keep the old warning behavior.
|
|
45
|
+
|
|
46
|
+
4. **The lane runs non-strict.** The script's own #863 calibration: a *resolved* sub-Metic
|
|
47
|
+
author on a protected path is the zero-false-positive blocker; an *unresolved* author
|
|
48
|
+
(the operator's display-name commits, deploy automation) is a review note. `--strict`
|
|
49
|
+
in CI would make the lane permanently red on routine operator commits — and a
|
|
50
|
+
permanently red detective lane rots into wallpaper.
|
|
51
|
+
|
|
52
|
+
5. **`audit-authorship.sh` gets its caller in the same workflow, as an advisory job**
|
|
53
|
+
(`continue-on-error: true`) — it is red on untouched main today, and enforcement
|
|
54
|
+
before the idea 1000653 triage would wedge the lane. Advisory makes the rot visible on
|
|
55
|
+
every push instead of nonexistent; flip it to enforcing when the triage lands.
|
|
56
|
+
|
|
57
|
+
## Consequences
|
|
58
|
+
|
|
59
|
+
- The authoritative layer now runs somewhere any repo reader can verify, on every push to
|
|
60
|
+
main. S-1 stays open exactly as written — this neither proves nor disproves a droplet
|
|
61
|
+
caller.
|
|
62
|
+
- The known-failing ratchet caught task 1002611's publish_manifest fix (the lane's first
|
|
63
|
+
red on main was `known_failing_ratchet` — the pairing working as designed): its entry is
|
|
64
|
+
removed and the ceiling ratchets 3 → 2. The two remaining entries now name task 1002617
|
|
65
|
+
as owner. Idea 1000542 (which owned the publish_manifest failure) is superseded — discard
|
|
66
|
+
at triage.
|
|
67
|
+
- A blocker finding on this lane is a *revert conversation*, not an automatic revert — the
|
|
68
|
+
ADR 0158 rule that a finding never executes its own remedy.
|
|
69
|
+
|
|
70
|
+
## Rejected
|
|
71
|
+
|
|
72
|
+
- **A GDS token secret for the authenticated roster route** — an owner dependency the
|
|
73
|
+
devcontainer lane already warned about ("half-wiring it would fail silently"); the public
|
|
74
|
+
surface carries the same two fields with no secret to rot.
|
|
75
|
+
- **`--strict` in CI** — see 4; permanent red is how a detective lane stops being read.
|
|
76
|
+
- **Wiring `audit-authorship.sh` as enforcing now** — 340 pre-existing hits would wedge
|
|
77
|
+
every push; visibility first, enforcement after triage.
|
package/docs/adr/README.md
CHANGED
|
@@ -224,5 +224,13 @@ This keeps the decision history honest and traceable.
|
|
|
224
224
|
| 0152 | [The landing gate is served by the **core app**, not by the front proxy — the cutover step of the landing migration ([task 1002411](https://example.com/builders#/task/1002411), L05 of goal 1000035). The reference instance's orb-gate front page was `file_server`'d by Caddy straight out of a checkout of an UNRELATED product repo (`root * <checkout>/brand/cloud-bongos`), so the landing had two sources of truth, no deploy path of its own, and a cross-product coupling the core could not express. [L01](https://example.com/builders#/task/1002407) had already ported the bundle into the core as `public-landing/` and [L02](https://example.com/builders#/task/1002408) had already taught `platform-server.js` to serve it (`/` -> 302 `/gate/`, `/gate/` serves the index raw, `express.static` covers the assets) — so by the time L05 came up, BOTH halves of its original plan ("give the landing (a) its own Caddy config and (b) its own deploy script") had dissolved. **Decision:** the app owns the landing end to end; the proxy drops `root` + `file_server` and keeps exactly three jobs on that surface — TLS, an optional pre-launch `basic_auth` lock, and the `@hidden` guard that keeps `gate/README.md` unreachable. No separate Caddy config to write (the resulting block IS the generic pure-proxy shape `provision.js`'s `caddyBlock()` already emits per instance, [ADR 0111](<redacted>.md) §4; host-specific hostnames/certs stay on the host per [ADR 0062](<redacted>.md) §7) and no separate deploy script to write (the landing ships INSIDE the pinned core package, so it rides the instance's existing pull-deploy — one artifact, one version, one path). Keeps `redir / /gate/ 302` because Caddy orders `redir` before `basic_auth`, so "/" behaves identically before and after, and the app issues the same 302 once the lock is lifted. Cost accepted: the landing now sits in the app's request path (a dead app = a dead front page — but the gate's whole purpose is to reach the app), and a landing tweak now needs a core release. Verified on the live instance: identical status codes across the apex and both vanity doors before/after, relative redirects (no origin leak), and a **severance test** — with the foreign repo's landing folder moved aside entirely the site still served the byte-identical gate, proving the dependency is cut rather than merely unused. Retiring the foreign copy is L06 ([task 1002412](https://example.com/builders#/task/1002412)).](<redacted>.md) | platform serving / instance infra |
|
|
225
225
|
| 0152 | [**Metic gains task abandonment, paired with an un-abandon at the same rank** (owner directive 2026-07-27: "Metics should also be able to abandon tasks"; [task 1002476](https://cloudbongos.com/builders#/task/1002476)). [ADR 0090](<redacted>.md) moved task authoring (create/edit/promote/deps/criteria) down to the trusted working rank but kept `POST /tasks/:id/abandon` Archon-only as "terminal scope removal" — so the rank that runs idea-triage, seeds the tasks, and shapes them still had to hand every duplicate/moot/superseded chore back to the owner to retire. That hand-back is janitorial, not adjudication. The real hazard was never the grant: abandon was **one-way through the API** (`/promote` took only backlog/blocked, `PATCH` refuses `status`), so a mis-retire needed hand-SQL on prod, and an abandoned task is invisible in every claimable list and every version rollup (migration 017 excludes it from `version_progress`) — nobody notices until the work silently doesn't happen. So the gate moves to `requireRank('metic','archon')` **and** `/promote` gains a third source state, `abandoned → backlog` (never straight to ready — a retired task re-enters un-workable and takes a second deliberate promote), backed by `db.restoreTask`, the mirror of `abandonTask`, which clears the `'Abandoned: <reason>'` stamp and only that. Unchanged: the shipped-task and active-claim refusals, idempotency, and the `audit_log` record of who retired what and who brought it back. The `EXPECTED_RANKS` lifecycle pin moves in lockstep (a *further* downgrade stays a hard CI fail); under [ADR 0151](<redacted>.md) the `task.abandon` floor moves archon→metic, landing as `governance_003` because the compat seed is run-once — purely additive, and the drift guard now compares the **union** of grant migrations to the catalog and fails loudly on a future revocation.](<redacted>.md) | GDS / permissions |
|
|
226
226
|
| 0153 | [**Sequential chain execution — one kickoff walks a dependency chain** ([task 1002484](https://cloudbongos.com/builders#/task/1002484)). [ADR 0015](<redacted>.md) made `dependencies[]` load-bearing and auto-promotes a task's dependents `backlog`→`ready` on ship — but nothing *picks the unblocked task up*, so on the live instance **146 of 303 open tasks sit behind a predecessor** and a 24-task planning seed needed 24 separate human kickoffs. The only "sequential" affordance was a clipboard prompt on the Tasks board (`<redacted>`), which is text rather than execution and cannot even select the gated successors, because they are still `backlog`. Requested independently by two builders (ideas 1000461, 1000398). **Decision:** a chain runner with **no schema change** (the polymorphic `dependencies` table + `task_is_fully_unblocked` + the promote-on-ship trigger already encode the order; a `chain_id`/queue column would be a second, drift-prone source of truth) and **no new claim guard** (one-active-claim-per-session is keyed on the *working tree*, so ship-before-next-claim is already the enforced shape — and batch-claiming a chain is impossible anyway, since successors fail `DEPS_NOT_SHIPPED` until their predecessor ships; the ship IS what unblocks the next step). Deterministic plumbing (`scripts/gds/sequence.js` — scope, topological order, halt matrix, all read off the task row) is split from judgement (`.claude/skills/builder-sequence/`, which defers to `/builder-claim` + `/dev` + `/builder-ship`). **Halt, do not skip** (owner decision): on `needs_migration`, `security_sensitive`, `protected_path`, `rank_floor`, `thin_spec`, `judgement_kind`, `not_claimable`, `deps_unmet`, `in_flight`, or `cycle`, the runner stops and reports with the task still claimed — because in a dependency graph a skipped task is a *predecessor*, so skipping strands everything behind it while the run keeps posting apparent progress. Not built: chains as a priced claimable unit on the board (idea 1000398), which is additive.](<redacted>.md) | GDS / methodology |
|
|
227
|
+
| 0155 | [**Adopting a private repo: widen the onboarding OAuth scope to `repo`** ([task 1002561](https://example.com/builders#/task/1002561)) — amends [ADR 0143](<redacted>.md) §1 by taking the exit 0143 itself named (“revisit to `repo` only if private support is needed”). Standing up a project on an EXISTING PRIVATE repo died at the provisioner’s scaffold push with GitHub `403 Write access to repository not granted`: `scopeForWebFlow` requested `public_repo`, which grants write to **public** repos only. Confirmed as scope and not credentials three ways — the live `github_oauth_tokens` row was `public_repo,read:user` and still VALID during the failure (a dead token 401s; this 403 text is GitHub’s canonical scope-insufficient reply); every greenfield project worked only because `createUserRepo` hardcodes `private:false`, so the platform had only ever pushed to repos it had just made public; and the picker already rendered a **“Private” badge** for a case `public_repo` made unreachable. Owner chose (B) widen-and-keep-pushing over (A) never-touch-the-user’s-repo (user runs `bongos init --adopt` + pushes; needs a deploy key for the instance to *run*) and (C) public-only, accepting the cost this reverses: the persisted token can now write to **every repo the user owns**. Containment from 0143 is unchanged and is what makes it tolerable — encrypted (secret-box), **1h TTL**, lazily swept, requested ONLY on the wizard’s `?repos=1` path (hall + CLI stay `read:user`), never stored when secret-box is off. Three guards ride along: (1) widening the *request* does not upgrade *stored* tokens, so new `grantedScopeCoversRepos()` gates `resolveRepoToken` and a surviving `public_repo` row takes the existing `repo_scope_required` (403) reconnect path instead of 200-ing while **silently omitting the user’s private repos** — it PARSES GitHub’s comma-separated grant rather than substring-matching, since `public_repo` *contains* “repo” and a naive match accepts exactly the stale token being guarded against; (2) `createUserRepo` keeps `private:false` — now a **product** choice (instances are open-source/capture-resistant by design), deliberately not a parameter, test-pinned so the widen can’t change what we create; (3) consent copy states the breadth in our own words first, since GitHub words `repo` as full control of all repositories. A central **GitHub App** (fine-grained `Contents:write` on only the selected repo) remains the principled fix and this makes its case stronger — a future ADR adopting it supersedes this. **This alone does not make adopt work:** provisioning is independently greenfield-only (`git init` a fresh tree + push to `main`, which an existing repo rejects as unrelated history) — [task 1002562](https://example.com/builders#/task/1002562) fixes that and, because the wider scope makes a mistaken `--force` a real data-loss route on a user’s repo, carries a test asserting the adopt push never forces; [1002563](https://example.com/builders#/task/1002563) verifies live.](<redacted>.md) | onboarding / auth / provisioning |
|
|
228
|
+
| 0156 | [**A builder's own display name wins, always** ([task 1002568](https://cloudbongos.com/builders#/task/1002568)). A builder renamed themselves in hall Settings, the save worked, and the name reverted to their GitHub account name at the next sign-in. `upsertBuilder` (`src/bongos/db-kernel.js`) is named for account creation but runs on **every authentication**, and its `ON CONFLICT (github_id) DO UPDATE` re-set `display_name = EXCLUDED.display_name` unconditionally — where `EXCLUDED` carries whatever the caller just read off GitHub. All four login paths feed it (`pollDeviceFlow`, `completeWebFlow`, `completeIdpFlow`, `completeDeviceExchange`), so CLI re-auth alone was enough to undo the save the same day. **Decision:** the row records *who last set the name*. A new core column `builders.display_name_source` (`'github' | 'user'`, migration `core_197`) is stamped `'user'` by `setBuilderDisplayName` — the only self-rename path — and the upsert's conflict branch guards the assignment with `CASE WHEN builders.display_name_source = 'user' THEN builders.display_name ELSE EXCLUDED.display_name END`. GitHub keeps seeding and re-syncing the name for everyone who never renamed; it never overwrites one the builder chose. `github_login`/`avatar_url` re-sync unchanged. **Rejected:** the one-line fix of deleting the assignment entirely — it freezes `display_name` at signup for *everyone*, so a GitHub rename (or a bare login seeded because the account had no GitHub display name) would never propagate again; that trades a visible bug affecting the few who renamed for a silent one affecting the many who never opened Settings. Not surfaced in `GET /me/display-name` or the OpenAPI spec (internal persistence state, no UI consumer). The federated hub's `platform_identity_accounts` needs no matching guard — the `CASE` runs regardless of what `EXCLUDED` carries, and that table is identity-only with no self-service editor. `tests/display_name.mjs` previously covered only the pure validator, which is why this shipped; it now reads the real SQL and asserts both directions.](<redacted>.md) | GDS / builder experience |
|
|
229
|
+
| 0157 | [**Archon is rank and identity only; the operational block delegates to Metic** ([task 1002584](https://cloudbongos.com/builders#/task/1002584), owner decision 2026-08-05). Archon had accumulated **50 route gates**, almost none about *authority* — reading the roster, listing dev boxes, viewing the audit log, adjudicating a vuln report, tearing down a droplet. Each arrived Archon-gated by [ADR 0016](<redacted>.md)'s fail-closed default for new privileged routes; nothing came back to lower them, so “fail closed” became “the owner is the only operator”. Generalises the one-power delegations of [ADR 0090](<redacted>.md) and [ADR 0152](<redacted>.md) instead of relitigating route by route. **20 permissions move archon→metic** — the governance pages (`/watch`, `/harbor`, `/gate`), session search, the dev-box + provisioning fleets, security adjudication, the audit log, roster reads, cross-builder memory reads, project curation, budgets, idea ratification, override decisions, release-on-behalf, criterion unsatisfy, goal reorder, version create, newcomer restock, peer-vote tally, Discord channel ops, autonomy precheck. **Only possible as a 1-file change** because BV1.R104/R105 had just migrated the gates to `requirePermission`, so the floor is declared once in `catalog.js` — the same decision was a 41-call-site diff a day earlier, and that is the concrete [ADR 0151](<redacted>.md) payoff. **13 stay Archon:** rank (`builder.rank.set`), identity (status/admission/session-revoke), and — refusing the literal reading of the directive — **four escalation keys** (`task.confirm.grade_bypass`, `gate_approval.decide`, `goal.scope.manage`, `governance.manage`) that form a chain to self-promotion: scope a goal into the permission core → claim a task there (protected paths already floor at *metic* per [ADR 0043](<redacted>.md)) → confirm past the failed grade → approve your own red CI gate → it lands on `main`. Moving all 20 would have made the delegation **self-reversible**, so keeping rank Archon-only depends on keeping those four Archon-only; any future delegation must re-run that check. Also held back: `version.close` (paired with rank as the powers that never delegate) and `dependency.cross_tier.manage` (`system: true` — clearing that flag to move it would widen what any CUSTOM role can hold, a second-order change nobody asked for). Mints **`gate_approval.read`** so the Gate page opens at Metic while approving stays Archon, and finishes the three reads R104/R105 skipped — otherwise the read would be **stricter than the write** (a Metic able to confirm a vuln report but not list one). Route pins move from ranks to `perm:` atoms so a floor change needs no pin edit. **Two flagged consequences:** `/security/docs` (attacker-usable exploit detail) is now Metic-readable, since it shares `security.report.adjudicate` — the one delegation widening who may *read* rather than *act*; and [ADR 0154](<redacted>.md)'s “the backward ratchet stays Archon” carve-out is superseded (satisfy/unsatisfy stay separate atoms, so confirming still never confers un-confirming). ADR 0016 is untouched end to end — floors move *inside* the boundary, never the enforcement point.](<redacted>.md) | governance / permissions |
|
|
230
|
+
| 0158 | [**The choke-point verdict taxonomy, and why a verdict never executes itself** ([task 1002598](https://cloudbongos.com/builders#/task/1002598)). [ADR 0157](<redacted>.md) delegated 20 permissions on one observation — each had been Archon-gated by [ADR 0016](<redacted>.md)'s fail-closed default, *never by a decision* — but applied that reasoning once, to a known backlog, without recording it as a **method**. Two things now make the question tractable: a **second Archon exists** (until 2026-08-06 "needs an Archon" and "needs Lars" were the same sentence, and a large share of past escalations reclassify on that distinction alone), and post-[ADR 0151](<redacted>.md) a floor is declared once, so classifying exhaustively is cheap. **Decision:** six ordered verdicts, first match wins — GAP · UNINTENDED-MISSING-SURFACE · DRIFT · STRUCTURAL · INTENDED · UNINTENDED-OVERTIGHT. Order is load-bearing: GAP leads because arguing a wall's rank while nothing enforces it is the costlier mistake, and UNINTENDED-OVERTIGHT is last because its remedy is the only one that *weakens* a boundary. DRIFT is never terminal — a DRIFT row must name what it collapses into. **The load-bearing rule: a verdict is advisory and never self-executing.** Recording UNINTENDED-OVERTIGHT is not authorization to lower a floor; that still needs an owner decision + `catalog.js` + a migration (the `governance_002` seed is run-once, so an edited `floor:` never reaches a migrated DB) + an ADR. A document that could move a floor by asserting a verdict would be privilege escalation through markdown — the audit describes, the database enforces. Every row carries a rigor marker (`obs` / `read` / **`absent`** / **`unver`**); the last two earn their own names because the two highest-severity findings in the first map are *absences* (a pre-push hook never once in git history; an "authoritative" audit script with no caller) and the sharpest structural one is an `unver` — the layer [ADR 0043](<redacted>.md) designates authoritative lives entirely outside the repository that documents it, so the trust model cannot be audited from the artifact that states it. Two methodological constraints, both learned the expensive way: **an Archon cannot audit the ownership walls** (`gateTaskOwnership` short-circuits on `rank === 'archon'` before the ownership lookup, so an Archon probe passes all 7 routes and observes nothing — which is why the single-Archon era *hid* a class of wall rather than merely queueing it), and **send two bodies per write** (16 of 29 in-handler-gated routes return 400/422 on `{}` before the authorization check, so a `{}`-only sweep concludes there are no in-handler walls). Produces [`docs/governance/<redacted>.md`](../governance/<redacted>.md). ADR 0016 and 0043 untouched — this adds vocabulary for *describing* the boundary; it moves no floor and changes no enforcement. **Rejected:** generating the map from the route census (a census answers "where is the gate", never "should it be there", and generating it would imply otherwise); folding verdicts into `canonical-permissions.md` (that file mirrors the enforced model — mixing judgments *about* it into the description *of* it is how a doc starts reading as authority); auto-filing a task on UNINTENDED-OVERTIGHT (a queued privilege change nobody decided); and collapsing to three verdicts (it merges GAP into UNINTENDED-OVERTIGHT, inverting the remedy — an unenforced wall needs a gate *added*, an over-tight one needs a floor *lowered*).](<redacted>.md) | governance / methodology |
|
|
231
|
+
|
|
232
|
+
| 0159 | [**Merge-gate reconciliation: required checks match reality, CODEOWNERS becomes real, protection stays the owner's switch** ([task 1002611](https://cloudbongos.com/builders#/task/1002611)). Findings D-12 + G-5 from the choke-point map: `gating.js` hardcoded three required status checks (`unit`, `trufflehog`, `gate-review`) of which two have never had a workflow — running `gating.js on` would have required contexts nothing can report, blocking every merge indefinitely instead of gating them — and CODEOWNERS was described as a live control by ADR 0042, `protected-surfaces.json`, `publish-manifest.js` and gating.js itself while not existing anywhere in the tree. Now that the `unit` lane exists (task 1002610), `DEFAULT_PROTECTION` requires ONLY `[unit]`; the future contexts are fixed as `secret-scan` + `gate-review` (the `CORE_EXPECTED_CHECKS` names — a context is named for what it asserts, not the tool asserting it) and return when their ADR 0058 workflows ship; CODEOWNERS becomes a real root file scoped to `gate-review.js`'s `HARD_FLOOR_GLOBS` + gating.js (inert until protection is on); branch protection itself stays OFF — flipping it remains the owner's explicit act, this only makes the switch safe. Rider: the unit lane is hardened so `unit` is actually requirable — `fetch-depth: 0` (depth-1 broke `tests/main_audit.mjs` on the lane's first run), two clean-env-red tests parked in `KNOWN_FAILING` with diagnoses + exit tracked in [task 1002617](https://cloudbongos.com/builders#/task/1002617), and `publish_manifest` (the public-mirror boundary test) fixed in-claim rather than parked — its ≥7-host-tests non-vacuity floor went stale when ADR 0107 moved host tests into `modules/<key>/tests/`, replaced by a regex specimen self-test. Rejected: deleting `GATE_WORKFLOWS`' forward-looking names (the switch handles absence gracefully; deleting them un-covers the workflows the moment they ship) and watering down the four CODEOWNERS descriptions instead of creating the one file.](<redacted>.md) | governance / build pipeline |
|
|
233
|
+
|
|
234
|
+
| 0160 | [**The authoritative main audit gets a repo-verifiable caller** ([task 1002612](https://cloudbongos.com/builders#/task/1002612)). Findings G-3 + G-8, honest limit S-1: `main-audit.js` — the post-push layer [ADR 0043](<redacted>.md) designates authoritative — had two declared invokers that never existed in this git history, and `audit-authorship.sh` (the no-hardcoded-Archon-identity enforcer) had no caller either and turned out red with 340 pre-existing hits on untouched main (idea 1000653). Now a `main-audit` workflow audits every push to `main` over exactly the pushed `before..after` range (clamped-window fallback); the roster comes from the public leaderboard when no session exists (server-authoritative, login-only keying, an absent builder resolves as unresolved — never falsely trusted); new `--require-roster` makes an empty roster a loud DID-NOT-RUN exit 2 (an audit that cannot fire a blocker must not exit green); the lane runs non-strict per the audit's own zero-false-positive calibration; and `audit-authorship.sh` rides as an advisory job until the 1000653 triage. Detective lane, not a merge gate — deliberately outside the required PR contexts. Rider: the known-failing ratchet caught task 1002611's publish_manifest fix exactly as designed — entry removed, ceiling ratcheted 3 → 2, remaining entries re-owned to [task 1002617](https://cloudbongos.com/builders#/task/1002617), idea 1000542 superseded. Rejected: a GDS token secret (silent-fail dependency the devcontainer lane warned about), `--strict` in CI (permanent red rots into wallpaper), enforcing authorship before triage.](<redacted>.md) | governance / build pipeline |
|
|
227
235
|
|
|
228
236
|
> ⚠️ **Numbering collision (known):** two ADRs share `0008`. The numbers were assigned in parallel sessions before the lock could be enforced. The files are kept under their existing filenames to preserve git history, but the table disambiguates them as `0008-a` / `0008-b`. New ADRs should resume at `0040` to avoid further collisions. (`0024` is also a real on-disk duplicate: the cloneable-repo/local-first-memory ADR and the multi-agent-system-architecture ADR were both numbered `0024` in parallel; both keep their filenames, disambiguated as `0024-a` / `0024-b` like `0008`. Likewise `0025` ended up with two on-disk files — the offsite-backup ADR and the structured-criterion↔task-link ADR — both kept under their existing filenames and disambiguated as `0025-a` / `0025-b`; separately, the BFG ADR had also claimed `0025` on 2026-05-30 and yielded to `0026`. A third happened 2026-06-06: `0036` was claimed in parallel by the builders-subdomain ADR ([#726](https://example.com/builders#/task/726)) and this Discord-channels ADR ([#727](https://example.com/builders#/task/727)); the Discord-channels ADR yielded to `0037`. A fourth landed 2026-06-10: `0044` was claimed in parallel by the palette-replacement ADR and the per-box game-preview ADR ([#897](https://example.com/builders#/task/897)); both keep their filenames, disambiguated as `0044-a` / `0044-b` like `0008`. A fifth was caught pre-land 2026-06-13: `0050` was claimed in parallel by the device-flow-admission ADR ([#1000](https://example.com/builders#/task/1000)) and the sandbox-for-everyone ADR ([#1001](https://example.com/builders#/task/1001)); the latter yielded to `0052` before merging, so there is no `0050` duplicate. A sixth was caught pre-land 2026-06-21: the tool-agnostic-design-layer ADR ([#1375](https://example.com/builders#/task/1375)) first collided at `0079` with the value-gated-autonomous-engagement ADR, then at `0080` with the llm-cache-api-transport ADR (both part of the 0075–0080 LLM-cost-reduction series, landed on `main` in parallel); it yielded to `0081` before merging, so there is no `0079`/`0080` design-layer duplicate. A seventh landed 2026-06-23: `0084` was claimed in parallel by the auto-derived-per-task-rank-floor ADR and the remove-default-spend-cap ADR (task 1499); the rank-floor ADR landed on `main` first, so the spend-cap ADR yielded to `0085`, leaving no `0084` duplicate. An eighth landed 2026-06-27: `0095` was claimed in parallel by the borrowed-memory-and-retrieval-concepts ADR (PR #459, landed first) and the cross-agent-context-management ADR ([#1651](https://example.com/builders#/task/1651), PR #460); both keep their filenames, disambiguated as `0095-a` / `0095-b` like `0008`. These two are companions — `0095-a` adopts the memory/retrieval concepts and explicitly defers the grep-vs-semantic question to `0095-b`.)
|