@bongos/core 1.19.20 → 1.19.21
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 +27 -17
- package/docs/adr/0162-review-gates-retired-rank-consistent-ci-only.md +67 -0
- package/docs/adr/README.md +1 -0
- package/docs/canonical-permissions.md +8 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/bump-version.js +15 -1
- package/scripts/gds/gating.js +22 -14
- package/scripts/gds/init.js +1 -1
- package/src/module-api.js +3 -1
- package/tests/bump_version.mjs +42 -0
- package/tests/gating_review_retired.mjs +63 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.21",
|
|
6
|
+
"core_contract": "1.19.21",
|
|
7
|
+
"source_commit": "1bcacbc141d1103f3b8e89d9c3963369740c783d",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-11T00:39:38.782Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 285,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1204,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1510,
|
|
20
|
+
"tree_sha256": "1f7f270a9f0d217360d9126213d6c7d752039999b895385c114df75fde81f14f",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1264,10 +1264,15 @@
|
|
|
1264
1264
|
"mode": "0000644",
|
|
1265
1265
|
"sha256": "056633fe7521143b0e8ba89297426ffd7253deb9ae5aa2fad65c3dfbffddc44f"
|
|
1266
1266
|
},
|
|
1267
|
+
{
|
|
1268
|
+
"path": "docs/adr/0162-review-gates-retired-rank-consistent-ci-only.md",
|
|
1269
|
+
"mode": "0000644",
|
|
1270
|
+
"sha256": "f6176799c7731da3d4970b75e882e0ab88e31b1a412c87957b9a5085bfc799c7"
|
|
1271
|
+
},
|
|
1267
1272
|
{
|
|
1268
1273
|
"path": "docs/adr/README.md",
|
|
1269
1274
|
"mode": "0000644",
|
|
1270
|
-
"sha256": "
|
|
1275
|
+
"sha256": "b3ed6866c5042d2ee2f1c5680af8d6ef85d36c70a4f9e208eac2e5922461f6dc"
|
|
1271
1276
|
},
|
|
1272
1277
|
{
|
|
1273
1278
|
"path": "docs/api-reference.md",
|
|
@@ -1297,7 +1302,7 @@
|
|
|
1297
1302
|
{
|
|
1298
1303
|
"path": "docs/canonical-permissions.md",
|
|
1299
1304
|
"mode": "0000644",
|
|
1300
|
-
"sha256": "
|
|
1305
|
+
"sha256": "dd85972912629d370d8fc79eff1661001e6b4ba9abe9866181c14fb0782c3131"
|
|
1301
1306
|
},
|
|
1302
1307
|
{
|
|
1303
1308
|
"path": "docs/design-contract.md",
|
|
@@ -3997,12 +4002,12 @@
|
|
|
3997
4002
|
{
|
|
3998
4003
|
"path": "package-lock.json",
|
|
3999
4004
|
"mode": "0000644",
|
|
4000
|
-
"sha256": "
|
|
4005
|
+
"sha256": "a408615d881c51b175e64ca336475849090253394df3bc7e428bb0e28d79d40e"
|
|
4001
4006
|
},
|
|
4002
4007
|
{
|
|
4003
4008
|
"path": "package.json",
|
|
4004
4009
|
"mode": "0000644",
|
|
4005
|
-
"sha256": "
|
|
4010
|
+
"sha256": "e9e8deee79bedc058f9870901d7afdb266d99443d4877059ece4e9cbe4501e2f"
|
|
4006
4011
|
},
|
|
4007
4012
|
{
|
|
4008
4013
|
"path": "public-docs/index.html",
|
|
@@ -4502,7 +4507,7 @@
|
|
|
4502
4507
|
{
|
|
4503
4508
|
"path": "scripts/gds/bump-version.js",
|
|
4504
4509
|
"mode": "0000644",
|
|
4505
|
-
"sha256": "
|
|
4510
|
+
"sha256": "9709fcffab1d386c174666788687b1dfd974f141ab552ffd5641abd690259619"
|
|
4506
4511
|
},
|
|
4507
4512
|
{
|
|
4508
4513
|
"path": "scripts/gds/capture.js",
|
|
@@ -4692,7 +4697,7 @@
|
|
|
4692
4697
|
{
|
|
4693
4698
|
"path": "scripts/gds/gating.js",
|
|
4694
4699
|
"mode": "0000644",
|
|
4695
|
-
"sha256": "
|
|
4700
|
+
"sha256": "a304965e79b5ab717626bfb9c74358334576c69f5aefcefa059d46a092f46172"
|
|
4696
4701
|
},
|
|
4697
4702
|
{
|
|
4698
4703
|
"path": "scripts/gds/gds-db-backup.service",
|
|
@@ -4782,7 +4787,7 @@
|
|
|
4782
4787
|
{
|
|
4783
4788
|
"path": "scripts/gds/init.js",
|
|
4784
4789
|
"mode": "0000644",
|
|
4785
|
-
"sha256": "
|
|
4790
|
+
"sha256": "e9fbb7ae477a445252f1171851be93fca14f58e4906d030c93d09b37d517ac20"
|
|
4786
4791
|
},
|
|
4787
4792
|
{
|
|
4788
4793
|
"path": "scripts/gds/install-git-hooks.js",
|
|
@@ -5622,7 +5627,7 @@
|
|
|
5622
5627
|
{
|
|
5623
5628
|
"path": "src/module-api.js",
|
|
5624
5629
|
"mode": "0000644",
|
|
5625
|
-
"sha256": "
|
|
5630
|
+
"sha256": "81389daa63b2184235f45d0eb6d9aec17debbd00a9f7ffb74d7508953f0317ba"
|
|
5626
5631
|
},
|
|
5627
5632
|
{
|
|
5628
5633
|
"path": "src/module-loader/loader.js",
|
|
@@ -5997,7 +6002,7 @@
|
|
|
5997
6002
|
{
|
|
5998
6003
|
"path": "tests/bump_version.mjs",
|
|
5999
6004
|
"mode": "0000644",
|
|
6000
|
-
"sha256": "
|
|
6005
|
+
"sha256": "98ec16d513074b30de8cd897143e4a3df1b3e54132a09d015b99e13e870de384"
|
|
6001
6006
|
},
|
|
6002
6007
|
{
|
|
6003
6008
|
"path": "tests/cap_newcomer_tasks.mjs",
|
|
@@ -6359,6 +6364,11 @@
|
|
|
6359
6364
|
"mode": "0000644",
|
|
6360
6365
|
"sha256": "a5ca654f46407a3172cfa981f399c159c2769b440058eca509f568111614b00c"
|
|
6361
6366
|
},
|
|
6367
|
+
{
|
|
6368
|
+
"path": "tests/gating_review_retired.mjs",
|
|
6369
|
+
"mode": "0000644",
|
|
6370
|
+
"sha256": "53cd6df5521ee48909b217483163bdf52ecfcc9aa5267f0b8a213c2b4b0f76bc"
|
|
6371
|
+
},
|
|
6362
6372
|
{
|
|
6363
6373
|
"path": "tests/gen_freshness.mjs",
|
|
6364
6374
|
"mode": "0000644",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# ADR 0162 — Review gates retired: rank-consistent CI checks only on `main`
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-08-10
|
|
4
|
+
**Context:** Owner directive (jaxri, Archon), recorded live 2026-08-08–10; idea 1000657 → task 1002635. Amends ADR 0159 (which introduced CODEOWNERS) and the gating posture of ADR 0058/0042.
|
|
5
|
+
**Status:** Accepted. The live half (dropping `require_code_owner_reviews` from branch protection) was applied by the repo-admin account on 2026-08-10 and verified behaviorally (a probe PR touching a formerly-owned path read `mergeStateStatus: CLEAN` on a green `unit` check — PR 32, closed unmerged). This ADR lands the repo half so no re-arm path resurrects the gate.
|
|
6
|
+
|
|
7
|
+
## The incident that forced the decision
|
|
8
|
+
|
|
9
|
+
Wave 3 of the rank choke-point remediation built the merge gates inert (required `unit`
|
|
10
|
+
check, `gating.js`, a CODEOWNERS file scoped to the gate-review HARD_FLOOR_GLOBS); task
|
|
11
|
+
1002619 armed them. Within a day, the code-owner review requirement had stranded two
|
|
12
|
+
graded-PASS wave-4 ships (PR 28 sat `BLOCKED` behind a review only the `example-owner` account
|
|
13
|
+
could give) and then blocked the fix for the strand it caused (the ship.js ci-land fix,
|
|
14
|
+
task 1002629, and the release-cascade fix, task 1002636, both touch owned paths).
|
|
15
|
+
|
|
16
|
+
The mechanics are structural, not incidental. GitHub can only express the gate as
|
|
17
|
+
**accounts** — it has no concept of GDS ranks; PR authors cannot approve their own PRs;
|
|
18
|
+
and on this instance nearly every PR is authored by agent sessions running under the
|
|
19
|
+
owner-archon's daily login. So "code-owner review" degenerates into "one specific
|
|
20
|
+
GitHub account must click on every gate-surface PR," which is:
|
|
21
|
+
|
|
22
|
+
1. **Identity-pinned, not rank-based** — against the instance's governance principle
|
|
23
|
+
(ADR 0016; the no-hardcoded-Archon-identity rule).
|
|
24
|
+
2. **The opposite of the remediation's goal.** The owner's words: builders should have
|
|
25
|
+
*fewer* rank-related shipping problems, not more; a code-owners layer "can only
|
|
26
|
+
create shipping problems for builders."
|
|
27
|
+
|
|
28
|
+
## Decision
|
|
29
|
+
|
|
30
|
+
Review requirements on `main` are **retired permanently**:
|
|
31
|
+
|
|
32
|
+
- Branch protection carries **required status checks only** (today: `unit`; the
|
|
33
|
+
`secret-scan` / `gate-review` contexts join when their workflows ship — ADR 0058).
|
|
34
|
+
- `gating.js` `DEFAULT_PROTECTION` sets `required_pull_request_reviews: null`, and
|
|
35
|
+
`cmdOn` **normalizes both restore paths** (snapshot and default) to null — a snapshot
|
|
36
|
+
taken while the review gate was live must not resurrect it on an off/on cycle.
|
|
37
|
+
- The `CODEOWNERS` file is **deleted**. The `CODEOWNERS` *glob* stays in
|
|
38
|
+
`gate-review.js` `HARD_FLOOR_GLOBS`, `protected-surfaces.json`, and the publish
|
|
39
|
+
denylist — so reintroducing the file is itself a flagged gate-surface change, and the
|
|
40
|
+
hard-floor path list keeps protecting the same surfaces where it actually enforces
|
|
41
|
+
(the sandbox gate).
|
|
42
|
+
|
|
43
|
+
## What now guards the safety machinery
|
|
44
|
+
|
|
45
|
+
The compensating controls are all rank-consistent and automatic:
|
|
46
|
+
|
|
47
|
+
| Control | When it bites | Fails |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| required `unit` check (suite + fitness) | before every merge | closed |
|
|
50
|
+
| gate-review sandbox (task 1002607) | agent ship flow on hard-floor paths | closed |
|
|
51
|
+
| subagent grader panel | every GDS ship | closed |
|
|
52
|
+
| post-merge main-audit (task 1002612, every push) | after any merge, incl. a bypass | audit + revert |
|
|
53
|
+
|
|
54
|
+
## Consequences
|
|
55
|
+
|
|
56
|
+
- **Positive:** no human click anywhere in the land path; a graded-PASS ship lands on
|
|
57
|
+
CI alone; the wave-4/-5 strand class cannot recur; the gate posture matches the
|
|
58
|
+
rank-first governance principle.
|
|
59
|
+
- **Negative / accepted trade:** there is **no pre-merge human approval on
|
|
60
|
+
safety-machinery changes**. A compromised or prompt-injected session whose work
|
|
61
|
+
passes the unit check reaches `main`, and is caught *after* merge by the main-audit
|
|
62
|
+
rather than before. The owner chose this trade explicitly (2026-08-08, reaffirmed
|
|
63
|
+
2026-08-10) with the mitigations above in place.
|
|
64
|
+
- **Reversal path:** re-add a CODEOWNERS file, restore a `required_pull_request_reviews`
|
|
65
|
+
block in `gating.js`, remove the `cmdOn` normalization, and re-apply protection from
|
|
66
|
+
an admin account. All four are deliberate steps — the gate cannot come back by
|
|
67
|
+
accident, which is the point of the normalization.
|
package/docs/adr/README.md
CHANGED
|
@@ -233,5 +233,6 @@ This keeps the decision history honest and traceable.
|
|
|
233
233
|
|
|
234
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 |
|
|
235
235
|
| 0161 | [**Publish on merge: every merge to core `main` auto-publishes `@bongos/core`** (task 1002620). Supersedes ADR 0137's manual owner-gated publish after its own "when to revisit" clause fired twice in three weeks: 171 commits/10 days, then 104 commits/5 days, sat on `main` reachable by no pin while every layer reported success. A `publish` workflow rides behind the required `unit` check (`workflow_run`, success-gated): when HEAD's `CORE_VERSION` is already on npm, CI bumps a PATCH via the shape-preserving `bump-version.js` (+ changelog line; pinned by `tests/bump_version.mjs`) and pushes the carrier commit; `package-core --self-verify --publish --live` builds through the fail-closed no-leak gate; the release is tagged `core-v<v>`. A MINOR/MAJOR stays a human in-task edit — it decides what patch-channel subscribers receive unattended (ADR 0136), a call CI must not make. Token custody knowingly reverses (owner-accepted): `NPM_TOKEN` write + `RELEASE_PUSH_TOKEN` PAT as Actions secrets, mitigated by the lane shipping INERT behind the `PUBLISH_ON_MERGE=1` repo variable — an owner kill-switch symmetrical to `gating.js`. Idempotent by version-compare; npm duplicate rejection is the backstop; queued no-cancel concurrency collapses merge races.](<redacted>.md) | build pipeline / distribution |
|
|
236
|
+
| 0162 | [**Review gates retired: rank-consistent CI checks only on `main`** ([task 1002635](https://cloudbongos.com/builders#/task/1002635); owner directive, idea 1000657). Amends ADR 0159. Within a day of task 1002619 arming the merge gates, the code-owner review requirement had stranded two graded-PASS wave-4 ships (PR 28 sat BLOCKED behind a review only the repo-owner account could give) and then blocked the fixes for the strands it caused — structurally, not incidentally: GitHub can only express the gate as *accounts* (no rank concept, no self-approval), so on a solo-archon instance it degenerates into "one named login must click every gate-surface PR" — identity-pinned authority, against ADR 0016's rank principle and the remediation's own goal. Decision: branch protection carries required status checks ONLY; `gating.js` `DEFAULT_PROTECTION` sets `required_pull_request_reviews: null` and `cmdOn` normalizes BOTH restore paths so a pre-retirement snapshot cannot resurrect the gate (`tests/gating_review_retired.mjs` pins it with a counter-example); the CODEOWNERS *file* is deleted while its *glob* stays in the hard-floor lists so reintroducing it is itself gate-flagged. Accepted trade, chosen explicitly by the owner: no pre-merge human approval on safety-machinery changes — the unit check, the fail-closed gate-review sandbox, the grader panel, and the every-push main-audit are the net, catching a compromised session's merge after the fact rather than before.](<redacted>.md) | governance / build pipeline |
|
|
236
237
|
|
|
237
238
|
> ⚠️ **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`.)
|
|
@@ -250,11 +250,14 @@ Authority over *the code itself* rides the same ladder, enforced server-side fro
|
|
|
250
250
|
|
|
251
251
|
ADR 0016's per-request rank check covers `/api/gds/*` only. Two surfaces it does **not** cover —
|
|
252
252
|
`git push` to `main` and `ssh` to the droplet — were used in the Builder 25 incident to land an
|
|
253
|
-
un-graded tiering change straight on prod `main`.
|
|
254
|
-
branch protection
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
un-graded tiering change straight on prod `main`. **Since 2026-08-08 (task 1002619), `main` HAS
|
|
254
|
+
live branch protection: the required `unit` status check blocks every direct push, so all work
|
|
255
|
+
lands via PR.** The protection is *checks-only* — a code-owner review requirement was briefly
|
|
256
|
+
enforced and then deliberately retired by the owner (ADR 0162, task 1002635): gates are
|
|
257
|
+
rank-consistent CI checks, never a named human's approval. (The 2026-07-24 state — no protection
|
|
258
|
+
at all, PR #19 merged with zero checks — is preserved in ADR 0043 §"Branch-protection status" as
|
|
259
|
+
history.) `main-audit.js` remains the authoritative *post-merge* layer — it catches what any
|
|
260
|
+
pre-merge gate misses, independent of the builder's cooperation:
|
|
258
261
|
|
|
259
262
|
- A builder **below Metic** (`xenos`, `thetes`) may **not modify permission-sensitive paths** — the
|
|
260
263
|
rank/authz machinery, the ship/grade/deploy pipeline, DB `migrations/`, `infra/`, and the
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.21",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.21",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.21",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -72,5 +72,19 @@ function main() {
|
|
|
72
72
|
console.log(JSON.stringify({ from, to, applied: apply }));
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
// The commit subject THIS lane writes for its own bump commits — publish.yml's
|
|
76
|
+
// `git commit -m` mirrors it verbatim (tests/bump_version.mjs pins the two
|
|
77
|
+
// against each other). isReleaseCommit is the cascade terminator (task 1002636):
|
|
78
|
+
// publish.yml no-ops when current main HEAD matches, because everything at that
|
|
79
|
+
// HEAD was already published by the run that created the commit. Deliberately
|
|
80
|
+
// narrow — a HUMAN release commit ("release: core 1.19.2 — carrier for the
|
|
81
|
+
// init-blocker fixes") must NOT match: that version is new and still needs
|
|
82
|
+
// publishing.
|
|
83
|
+
const RELEASE_COMMIT_RE = /^release: core \d+\.\d+\.\d+ — CI auto-patch carrier \(publish-on-merge/;
|
|
84
|
+
|
|
85
|
+
function isReleaseCommit(subject) {
|
|
86
|
+
return RELEASE_COMMIT_RE.test(String(subject == null ? '' : subject));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { bumpPatch, CONST_RE, RELEASE_COMMIT_RE, isReleaseCommit };
|
|
76
90
|
if (require.main === module) main();
|
package/scripts/gds/gating.js
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
// scripts/gds/gating.js — owner kill-switch for main-branch merge gating.
|
|
3
3
|
//
|
|
4
4
|
// What "gating" means here — TWO independent levers, both toggled together:
|
|
5
|
-
// 1. GitHub branch protection on `main`: required status checks
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
5
|
+
// 1. GitHub branch protection on `main`: required status checks ONLY — no
|
|
6
|
+
// review requirement of any kind (ADR 0162, task 1002635: the owner
|
|
7
|
+
// retired the code-owner/human-approval layer after it stranded graded
|
|
8
|
+
// work; gates are rank-consistent CI checks, and the normalization in
|
|
9
|
+
// cmdOn keeps a pre-retirement snapshot from resurrecting it). The
|
|
10
|
+
// required contexts are ONLY checks that exist and can pass today —
|
|
11
|
+
// currently `unit` (the suite + fitness lane, task 1002610). The planned
|
|
12
|
+
// `secret-scan` and `gate-review` contexts (ADR 0058 phase 3;
|
|
13
|
+
// CORE_EXPECTED_CHECKS in adopt-preflight.js) join DEFAULT_PROTECTION
|
|
14
|
+
// when their workflows ship — requiring a context nothing reports blocks
|
|
15
|
+
// every merge indefinitely (D-12, ADR 0159).
|
|
12
16
|
// 2. The application-layer trust gates that run on every PR — the gate-review
|
|
13
17
|
// and grade-gate CI workflows (ADR 0058 phases 1-3; NOT YET IN THE TREE —
|
|
14
18
|
// workflowState() reports them 'absent' and the toggles below no-op until
|
|
@@ -17,7 +21,8 @@
|
|
|
17
21
|
// (it escalates gate-surface changes — infra/, migrations/, CODEOWNERS,
|
|
18
22
|
// scripts/gds plumbing — to owner approval). Branch protection alone does
|
|
19
23
|
// NOT neutralize these, which is why `off` also `gh workflow disable`s them.
|
|
20
|
-
// With gating ON,
|
|
24
|
+
// With gating ON, every PR — gate-surface or not — merges on the required
|
|
25
|
+
// status checks alone; no human approval is ever required (ADR 0162).
|
|
21
26
|
// With gating OFF, ordinary `gh pr merge N` (and every builder / autonomous
|
|
22
27
|
// session) merges with no checks and no approval. `unit` keeps running either
|
|
23
28
|
// way — a real safety net that forces no approval.
|
|
@@ -78,12 +83,11 @@ const DEFAULT_PROTECTION = {
|
|
|
78
83
|
],
|
|
79
84
|
},
|
|
80
85
|
enforce_admins: false,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
86
|
+
// ADR 0162 (task 1002635): review gates are RETIRED — the owner chose
|
|
87
|
+
// rank-consistent CI checks over human-approval gates after the code-owner
|
|
88
|
+
// requirement stranded graded-PASS work (PR 28). Never reintroduce a
|
|
89
|
+
// required_pull_request_reviews block here; cmdOn also normalizes snapshots.
|
|
90
|
+
required_pull_request_reviews: null,
|
|
87
91
|
restrictions: null,
|
|
88
92
|
};
|
|
89
93
|
|
|
@@ -302,6 +306,10 @@ function cmdOn(repo) {
|
|
|
302
306
|
putBody = DEFAULT_PROTECTION;
|
|
303
307
|
console.log('⚠ branch protection: no snapshot — restoring the baked-in known-good default.');
|
|
304
308
|
}
|
|
309
|
+
// ADR 0162 (task 1002635): review gates are retired. Normalize BOTH restore
|
|
310
|
+
// paths — a snapshot taken while the code-owner requirement was live would
|
|
311
|
+
// otherwise silently resurrect it on the next off/on cycle.
|
|
312
|
+
putBody = { ...putBody, required_pull_request_reviews: null };
|
|
305
313
|
putProtection(repo, putBody);
|
|
306
314
|
const after = getProtection(repo);
|
|
307
315
|
if (!after) fail('PUT returned but protection still reads as OFF — check `gh` auth / admin rights');
|
package/scripts/gds/init.js
CHANGED
|
@@ -362,7 +362,7 @@ function kickoffTasks(spec) {
|
|
|
362
362
|
title: 'Arm the merge gate before a second builder joins (ci deploy mode)',
|
|
363
363
|
kind: 'infra',
|
|
364
364
|
description: 'For a shared instance on ci deploy mode, enable branch protection on main + required checks ' +
|
|
365
|
-
'(unit / secret-scan / gate-review)
|
|
365
|
+
'(unit / secret-scan / gate-review), so untrusted builders cannot push straight to production. No review requirement — gates are CI checks, not human approvals (ADR 0162). Skippable for a solo/local instance.',
|
|
366
366
|
},
|
|
367
367
|
];
|
|
368
368
|
// Production topology is either AUTOMATED (the owner opted into provisioning — the
|
package/src/module-api.js
CHANGED
|
@@ -467,8 +467,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
467
467
|
// landed since 1.19.18 with no explicit bump. run 31346996715. (task 1002620)
|
|
468
468
|
// 1.19.20 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
469
469
|
// landed since 1.19.19 with no explicit bump. run 31347092915. (task 1002620)
|
|
470
|
+
// 1.19.21 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
471
|
+
// landed since 1.19.20 with no explicit bump. run 31446766597. (task 1002620)
|
|
470
472
|
// ---------------------------------------------------------------------------
|
|
471
|
-
const CORE_VERSION = '1.19.
|
|
473
|
+
const CORE_VERSION = '1.19.21'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
472
474
|
|
|
473
475
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
474
476
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/bump_version.mjs
CHANGED
|
@@ -69,3 +69,45 @@ test('the REAL src/module-api.js is bumpable right now (shape not drifted)', ()
|
|
|
69
69
|
assert.match(from, /^\d+\.\d+\.\d+$/);
|
|
70
70
|
assert.ok(text.includes(`const CORE_VERSION = '${to}';`));
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
// ---- isReleaseCommit: the cascade terminator (task 1002636) ----
|
|
74
|
+
//
|
|
75
|
+
// The bug: publish.yml's bump+publish push re-fired the workflow, which found
|
|
76
|
+
// the just-published version "taken" — the BUMP trigger, not a no-op — so every
|
|
77
|
+
// release armed the next one (core 1.19.6 → 1.19.20+ from two work merges).
|
|
78
|
+
// The terminator no-ops the run when main HEAD is this lane's own release
|
|
79
|
+
// commit. These tests pin the predicate to the EXACT subject the workflow
|
|
80
|
+
// writes (extracted from publish.yml, not retyped — the two can't drift apart)
|
|
81
|
+
// and pin that a HUMAN release commit never matches (skipping one would
|
|
82
|
+
// swallow its publish entirely).
|
|
83
|
+
|
|
84
|
+
const { isReleaseCommit, RELEASE_COMMIT_RE } = require(path.join(ROOT, 'scripts', 'gds', 'bump-version.js'));
|
|
85
|
+
const PUBLISH_YML = fs.readFileSync(path.join(ROOT, '.github', 'workflows', 'publish.yml'), 'utf8');
|
|
86
|
+
|
|
87
|
+
test('matches the exact -m subject publish.yml writes (extracted, not retyped)', () => {
|
|
88
|
+
const m = PUBLISH_YML.match(/git commit -m "([^"]+)"/);
|
|
89
|
+
assert.ok(m, 'publish.yml must contain the bump git commit -m literal');
|
|
90
|
+
const subject = m[1].replace('${TO}', '9.9.9');
|
|
91
|
+
assert.ok(isReleaseCommit(subject), `predicate must match the workflow's own subject: ${subject}`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('a HUMAN release commit never matches — skipping it would swallow its publish', () => {
|
|
95
|
+
assert.equal(isReleaseCommit('release: core 1.19.2 — carrier for the init-blocker fixes (task 1002624)'), false);
|
|
96
|
+
assert.equal(isReleaseCommit('release: core 2.0.0 — the doorway break (ADR 9999)'), false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('work-merge subjects, junk, and empties never match', () => {
|
|
100
|
+
assert.equal(isReleaseCommit('Merge pull request #30 from lars589/claude/cloudbongos-npm-registry-b9a57f'), false);
|
|
101
|
+
assert.equal(isReleaseCommit('fix(ship): protection-aware deploy mode'), false);
|
|
102
|
+
assert.equal(isReleaseCommit(''), false);
|
|
103
|
+
assert.equal(isReleaseCommit(null), false);
|
|
104
|
+
assert.equal(isReleaseCommit(undefined), false);
|
|
105
|
+
assert.equal(RELEASE_COMMIT_RE.source.startsWith('^'), true, 'anchored — a subject QUOTING a release message must not match');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('publish.yml wires the terminator: guard step present, every armed step gated', () => {
|
|
109
|
+
assert.ok(PUBLISH_YML.includes('id: relhead'), 'the Already released? guard step exists');
|
|
110
|
+
assert.match(PUBLISH_YML, /isReleaseCommit/, 'the guard delegates to bump-version.js — single source');
|
|
111
|
+
const gates = (PUBLISH_YML.match(/steps\.relhead\.outputs\.release_head == 'false'/g) || []).length;
|
|
112
|
+
assert.ok(gates >= 7, `every post-guard armed step must carry the gate (setup-node, install, resolve, bump, pack, publish, tag) — found ${gates}`);
|
|
113
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// tests/gating_review_retired.mjs — review gates stay retired (ADR 0162, task 1002635).
|
|
2
|
+
//
|
|
3
|
+
// The owner retired the code-owner/human-approval layer after it stranded
|
|
4
|
+
// graded-PASS work (PR 28) — gates are rank-consistent CI checks only. Two
|
|
5
|
+
// resurrection paths existed and both are pinned shut here:
|
|
6
|
+
// 1. gating.js DEFAULT_PROTECTION carrying a required_pull_request_reviews
|
|
7
|
+
// block (the baked-in re-arm), and
|
|
8
|
+
// 2. cmdOn replaying a pre-retirement SNAPSHOT whose put_body still holds
|
|
9
|
+
// require_code_owner_reviews: true.
|
|
10
|
+
// gating.js is a CLI (main() runs on require), so these are source-shape pins
|
|
11
|
+
// (the goal_advisory.mjs pattern), with a counter-example proving the checker
|
|
12
|
+
// catches the old shape.
|
|
13
|
+
//
|
|
14
|
+
// Run: node --test tests/gating_review_retired.mjs
|
|
15
|
+
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import { test } from 'node:test';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const GATING = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'gating.js'), 'utf8');
|
|
24
|
+
|
|
25
|
+
// The retirement invariant, as a checkable predicate over the source.
|
|
26
|
+
function reviewGateRetired(src) {
|
|
27
|
+
const defaultHasNull = /required_pull_request_reviews:\s*null/.test(src);
|
|
28
|
+
const noRequireTrue = !/require_code_owner_reviews:\s*true/.test(src);
|
|
29
|
+
const cmdOnNormalizes = /putBody\s*=\s*\{\s*\.\.\.putBody,\s*required_pull_request_reviews:\s*null\s*\}/.test(src);
|
|
30
|
+
return { defaultHasNull, noRequireTrue, cmdOnNormalizes, ok: defaultHasNull && noRequireTrue && cmdOnNormalizes };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('DEFAULT_PROTECTION requires no reviews and no code-owner block exists anywhere', () => {
|
|
34
|
+
const r = reviewGateRetired(GATING);
|
|
35
|
+
assert.ok(r.defaultHasNull, 'DEFAULT_PROTECTION must set required_pull_request_reviews: null');
|
|
36
|
+
assert.ok(r.noRequireTrue, 'no require_code_owner_reviews: true may exist in gating.js (ADR 0162)');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('cmdOn normalizes BOTH restore paths — a pre-retirement snapshot cannot resurrect the gate', () => {
|
|
40
|
+
const r = reviewGateRetired(GATING);
|
|
41
|
+
assert.ok(r.cmdOnNormalizes, 'cmdOn must overwrite required_pull_request_reviews to null before putProtection');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('the CODEOWNERS file is gone; its GLOB guards remain where they enforce', () => {
|
|
45
|
+
assert.ok(!fs.existsSync(path.join(ROOT, 'CODEOWNERS')), 'CODEOWNERS must not exist (ADR 0162)');
|
|
46
|
+
const gateReview = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'gate-review.js'), 'utf8');
|
|
47
|
+
assert.match(gateReview, /'CODEOWNERS'/, 'the HARD_FLOOR glob stays — reintroducing the file is itself gate-flagged');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('the checker actually CATCHES the pre-retirement shape (not a no-op)', () => {
|
|
51
|
+
const oldShape = `
|
|
52
|
+
const DEFAULT_PROTECTION = {
|
|
53
|
+
required_pull_request_reviews: {
|
|
54
|
+
dismiss_stale_reviews: false,
|
|
55
|
+
require_code_owner_reviews: true,
|
|
56
|
+
required_approving_review_count: 0,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
function cmdOn(repo) { putProtection(repo, putBody); }`;
|
|
60
|
+
const r = reviewGateRetired(oldShape);
|
|
61
|
+
assert.equal(r.ok, false, 'the old review-gated shape must fail the predicate');
|
|
62
|
+
assert.equal(r.noRequireTrue, false, 'require_code_owner_reviews: true must be the detected offender');
|
|
63
|
+
});
|