@bongos/core 1.24.65 → 1.24.67

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 CHANGED
@@ -2,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.24.65",
6
- "core_contract": "1.24.65",
7
- "source_commit": "8f67a76402acd252e7d9997710389225189f1ff7",
5
+ "core_version": "1.24.67",
6
+ "core_contract": "1.24.67",
7
+ "source_commit": "cb3a73a80b0216905417a6379f4caf4f66a159c2",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-08T19:44:15.057Z",
9
+ "built_at": "2026-11-09T20:07:37.283Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 638,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 4575,
20
- "tree_sha256": "de673fb6edbef391285acd5a1ba81d227ff869398df36e3c4d06940dddae3ac8",
20
+ "tree_sha256": "ab4503425d89d4eec00873f68e4ed95088cb713b9816a7a0f3cc218da4f734d1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -3007,7 +3007,7 @@
3007
3007
  {
3008
3008
  "path": "docs/module-api-changelog.md",
3009
3009
  "mode": "0000644",
3010
- "sha256": "a661073ecdafcaa6ead7089b09758c4e3072ed1fba467670904b5ce73de426c9"
3010
+ "sha256": "b2ea544c7a4c86e2c83d079531d105a0f8ade34933bfe96929c5bd8256dcea5c"
3011
3011
  },
3012
3012
  {
3013
3013
  "path": "docs/modules-contract.md",
@@ -3262,7 +3262,7 @@
3262
3262
  {
3263
3263
  "path": "docs/recipes/ui-look-before-you-ship.md",
3264
3264
  "mode": "0000644",
3265
- "sha256": "c5d7971418fca5f6917e0e87e8decb8df2e54d51bdc7eb86ed415e9f7fa74ed3"
3265
+ "sha256": "691bbbf06c9622a91aa8b4f5d40a4be2e7198e9219a6a689bb7dd1ecb1430f94"
3266
3266
  },
3267
3267
  {
3268
3268
  "path": "docs/recipes/upgrading-the-core.md",
@@ -5757,7 +5757,7 @@
5757
5757
  {
5758
5758
  "path": "modules/government/protected-surfaces.json",
5759
5759
  "mode": "0000644",
5760
- "sha256": "d497234932a846f759f35e8f397f3a58284b85cced1a7e19f7419639ca573879"
5760
+ "sha256": "0b54c64b1515a2208a8094e80a891e99e374a99bc26e99f70c81d592ef125cbc"
5761
5761
  },
5762
5762
  {
5763
5763
  "path": "modules/government/ranks.js",
@@ -5852,7 +5852,7 @@
5852
5852
  {
5853
5853
  "path": "modules/grading/grader-no-artifact.js",
5854
5854
  "mode": "0000644",
5855
- "sha256": "4c66cba74d89e7f07f9984c0b846bff6210af7aa8a3a45219139246e4b975a22"
5855
+ "sha256": "21009dcce99c9a18abf5f7e40f68792ff42584eb2af5d0008df7f291487c62b1"
5856
5856
  },
5857
5857
  {
5858
5858
  "path": "modules/grading/grader-prompt.js",
@@ -11357,7 +11357,7 @@
11357
11357
  {
11358
11358
  "path": "modules/ui-design/kit/README.md",
11359
11359
  "mode": "0000644",
11360
- "sha256": "7e1d32a5383ee545d398dde7fdd142e80601a3458c4e35557958ee7a11a68246"
11360
+ "sha256": "a95397538d363364347d76a875663ffc9b36eb1cba03475f11bc175e2e2787fc"
11361
11361
  },
11362
11362
  {
11363
11363
  "path": "modules/ui-design/kit/check-mock.js",
@@ -11667,7 +11667,7 @@
11667
11667
  {
11668
11668
  "path": "modules/ui-design/kit/lib.js",
11669
11669
  "mode": "0000644",
11670
- "sha256": "ded9ff3491a264a9b08440874d7b4194b46a2c05b80986f648dabb3e52a8448c"
11670
+ "sha256": "e593169a194cd60838193df799dfb3b19ac72e92e2e6beadb3cd28128305abad"
11671
11671
  },
11672
11672
  {
11673
11673
  "path": "modules/ui-design/kit/png.js",
@@ -11902,12 +11902,12 @@
11902
11902
  {
11903
11903
  "path": "package-lock.json",
11904
11904
  "mode": "0000644",
11905
- "sha256": "b7ffb5c80ef9e08ddbe54c1e125fdbd2e92c2b864c1b2d320fcf83ba964c9cc2"
11905
+ "sha256": "02030268a28d3b76dc99198d41bf1de043460b72e8dedde3caa400b65c5ea4bf"
11906
11906
  },
11907
11907
  {
11908
11908
  "path": "package.json",
11909
11909
  "mode": "0000644",
11910
- "sha256": "482f515d87fb3ffc09c6971a26c5a7519551f6e0bd1816d0afd6c6cfb3e57448"
11910
+ "sha256": "f18b77185a5d2b924bf459cc5415880fea61d1118f9a358f0691b8d703565120"
11911
11911
  },
11912
11912
  {
11913
11913
  "path": "public-docs/index.html",
@@ -11927,7 +11927,7 @@
11927
11927
  {
11928
11928
  "path": "release-notes.json",
11929
11929
  "mode": "0000644",
11930
- "sha256": "be73c388b331c7077a4564866c27f60912c513843ffebc4c94c31a19aeee954b"
11930
+ "sha256": "576be7645a892c65c51e1ae3c8a17d7a5ecb2a2505cf9467dfff7a26b7aa5679"
11931
11931
  },
11932
11932
  {
11933
11933
  "path": "scripts/bongos/CLAUDE.md",
@@ -12197,7 +12197,7 @@
12197
12197
  {
12198
12198
  "path": "scripts/bongos/cli-lib.js",
12199
12199
  "mode": "0000644",
12200
- "sha256": "572a3d97e451df68412c5a40280ca58c6915e5b79a71d9a013522d51234a52c9"
12200
+ "sha256": "bbfd061637439ce86bd261fd39e243f1701247ea6669c7ae4775d1e59c94a3a9"
12201
12201
  },
12202
12202
  {
12203
12203
  "path": "scripts/bongos/client-baseurl-guard.js",
@@ -13317,7 +13317,7 @@
13317
13317
  {
13318
13318
  "path": "scripts/bongos/ship-grade-steps.js",
13319
13319
  "mode": "0000644",
13320
- "sha256": "4db93d8031f5912af2d1b4e4507680d6909ff4fa18b9485a451b340918d5cd79"
13320
+ "sha256": "83a0e5ce61257c8435a58cab07accf749570f8fa51db1b9c72aaff846ac10c02"
13321
13321
  },
13322
13322
  {
13323
13323
  "path": "scripts/bongos/ship-grade.js",
@@ -15612,7 +15612,7 @@
15612
15612
  {
15613
15613
  "path": "src/bongos/prelaunch-gate.js",
15614
15614
  "mode": "0000644",
15615
- "sha256": "dc9fcb627d1bfdc60e4f275521bab356e76a66202014be9ac9e388cf0a784717"
15615
+ "sha256": "a780f3d0d8b48c4b3c6e9a3fad4296cc31e1f006c5a0e7a0b62c51be793db186"
15616
15616
  },
15617
15617
  {
15618
15618
  "path": "src/bongos/prelaunch-switch.js",
@@ -15877,7 +15877,7 @@
15877
15877
  {
15878
15878
  "path": "src/module-api.js",
15879
15879
  "mode": "0000644",
15880
- "sha256": "1f78ed60de37fa15b7f57e5c19acfc0fb9bcab021e6cbd3a2ecc2044f9d76518"
15880
+ "sha256": "ab5adbd2f126b45470953b8c28545124554f69e04a1ba70494471d888288d509"
15881
15881
  },
15882
15882
  {
15883
15883
  "path": "src/module-loader/catalog.js",
@@ -16902,7 +16902,7 @@
16902
16902
  {
16903
16903
  "path": "tests/cli_sessions.mjs",
16904
16904
  "mode": "0000644",
16905
- "sha256": "5b76737b10b28866feb7be9195015e300cbc8cb23ead89ac9fc55dd5b52710dd"
16905
+ "sha256": "55d72cc2926ab8574e290ca16e40702759c8c6b0453a8ebd4aa9f09ae77f880d"
16906
16906
  },
16907
16907
  {
16908
16908
  "path": "tests/cli_surface.mjs",
@@ -17602,7 +17602,7 @@
17602
17602
  {
17603
17603
  "path": "tests/effective_visibility_predicate.mjs",
17604
17604
  "mode": "0000644",
17605
- "sha256": "7bd63d2a1f097fdde204803ec8b6fc0ae75843080af6d442c969ed02b7fb41dd"
17605
+ "sha256": "4a7bb44f07a87997830e44636490a4992d862d43b2620a2d583370869f6b477d"
17606
17606
  },
17607
17607
  {
17608
17608
  "path": "tests/env_knobs_neutral_prefix.mjs",
@@ -18437,7 +18437,7 @@
18437
18437
  {
18438
18438
  "path": "tests/government_protected_surfaces.mjs",
18439
18439
  "mode": "0000644",
18440
- "sha256": "3ecd666b5c5227295c04d781cfd554012ce3cabd38167c3947a86379f1b6004b"
18440
+ "sha256": "de07f6405cc18311d0e048efcbc450bb15b270387a9579b53811ae4ac506c7e0"
18441
18441
  },
18442
18442
  {
18443
18443
  "path": "tests/government_rank_sync.mjs",
@@ -18572,7 +18572,7 @@
18572
18572
  {
18573
18573
  "path": "tests/grade_unavailable_display.mjs",
18574
18574
  "mode": "0000644",
18575
- "sha256": "5842055bf9e1871b467ab682b24589f0c9523561090a026021cb898380508524"
18575
+ "sha256": "63135247609c11501f0d7af190343193a5dab334f7186cd05ab02fdbf1ea3644"
18576
18576
  },
18577
18577
  {
18578
18578
  "path": "tests/grader.mjs",
@@ -20697,7 +20697,7 @@
20697
20697
  {
20698
20698
  "path": "tests/prelaunch_gate.mjs",
20699
20699
  "mode": "0000644",
20700
- "sha256": "01cd4d1aa256c1d44fb2da98a4347aeb3e75e1aeae47f338d37f29ebec03e556"
20700
+ "sha256": "1eea91518ffdf65206d9b0a0094269a426268b0da4abb578a994150f7632ffd7"
20701
20701
  },
20702
20702
  {
20703
20703
  "path": "tests/preview_server.mjs",
@@ -21357,7 +21357,7 @@
21357
21357
  {
21358
21358
  "path": "tests/recall_hints.mjs",
21359
21359
  "mode": "0000644",
21360
- "sha256": "0ed0ecffcefe92b2b4db63e2c85858dcc7cb0c991a7453373cb7b54c523978a9"
21360
+ "sha256": "35af628eeda697d97bfaaba5052fc0f4928ae174ef04ec3afc7d3470dfc62dfe"
21361
21361
  },
21362
21362
  {
21363
21363
  "path": "tests/recent_shipped_window.mjs",
@@ -21787,7 +21787,7 @@
21787
21787
  {
21788
21788
  "path": "tests/session_start_freshness.mjs",
21789
21789
  "mode": "0000644",
21790
- "sha256": "d4c03dfb66690da1d870668e7b68c3bd0a35dc1a8a90986c6aad9586138ed1e1"
21790
+ "sha256": "acdee57f83af053e86d16ce9764647fe750ca33c7f24313a6e60b6ba9180dc49"
21791
21791
  },
21792
21792
  {
21793
21793
  "path": "tests/session_token_hash.mjs",
@@ -22567,12 +22567,12 @@
22567
22567
  {
22568
22568
  "path": "tests/ui_design_kit.mjs",
22569
22569
  "mode": "0000644",
22570
- "sha256": "74b63327d4042cf3be0f2abf5b0e7298319f6ed69ea488246e88e49923b26bc1"
22570
+ "sha256": "f5160521be5a2edecfe8997275cbbc65b8cf6ac8aac7cca2e90e23378d4e64f3"
22571
22571
  },
22572
22572
  {
22573
22573
  "path": "tests/ui_design_kit_fit.mjs",
22574
22574
  "mode": "0000644",
22575
- "sha256": "0f07a002d400c4e90ead7206334425bd80647a2cb844bbc9733758eb3f719a12"
22575
+ "sha256": "010a7b1427aa28d4807093f2ff31871058b366c46daafb1818cda2eee4f404cc"
22576
22576
  },
22577
22577
  {
22578
22578
  "path": "tests/ui_design_skills.mjs",
@@ -3384,5 +3384,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3384
3384
  landed since 1.24.63 with no explicit bump. run 42477114353. (task 1002620)
3385
3385
  1.24.65 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3386
3386
  landed since 1.24.64 with no explicit bump. run 42516347052. (task 1002620)
3387
+ 1.24.66 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3388
+ landed since 1.24.65 with no explicit bump. run 42686885661. (task 1002620)
3389
+ 1.24.67 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3390
+ landed since 1.24.66 with no explicit bump. run 42689728958. (task 1002620)
3387
3391
  ---------------------------------------------------------------------------
3388
3392
  ```
@@ -48,6 +48,7 @@ node modules/ui-design/kit/render.js --page / --style expedition # the sa
48
48
  | `horizontal-overflow` | `scrollWidth` wider than the viewport |
49
49
  | `text-fit` | a word of text more than 1px past its box (the padding box of its nearest non-inline ancestor), or an SVG `<text>` past the smallest `<rect>` in its own `<g>` that holds its start — a scroller, `text-overflow: ellipsis` and a visually-hidden label are skipped ([ADR 0329](../adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)) |
50
50
  | `sibling-overlap` | two in-flow, block-level children of one parent that both carry text overlapping by more than 2px each way — absolute, fixed, sticky and negative-margin layering is deliberate and skipped |
51
+ | `empty-graphic` | a shown `<use>` that paints nothing (its referenced SVG failed to parse, or the id is missing) or an `<img>` that did not decode or renders 0×0 — the logo that silently vanishes; a sprite sheet's own `<symbol>`/`<defs>`, a hidden graphic and a lazy image not yet loaded are skipped (task 1005104) |
51
52
  | `one-header-main-h1` | not exactly one *rendered* `<header>`, `<main>` and `<h1>` |
52
53
  | `controls-24px` | a visible control under 24px on either side (`ignoreControls` for the skip link and the like) |
53
54
  | `top-bar-80px` | a `<header>` taller than 80px |
@@ -39,6 +39,22 @@
39
39
  { "glob": "src/bongos/middleware/", "group": "authz", "why": "audit + any auth middleware" },
40
40
  { "glob": "src/bongos/routes/", "group": "authz", "why": "routes carry the requireRank gates" },
41
41
  { "glob": "modules/lifecycle/routes/", "group": "authz", "why": "BV1.R86: the lifecycle routes carved out of src/bongos/routes/ — the gates follow the routes" },
42
+ { "glob": "modules/lifecycle/db.js", "group": "authz", "why": "task 1004382. The lifecycle data-layer facade (BV1.R86): claimTask and the claim→ship→grade→credit state machine run through it. Its own header says it carries a duplicate of src/bongos/db.js's in-tx rank change, and src/bongos/db.js is protected for exactly that." },
43
+ { "glob": "modules/lifecycle/db-analytics.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
44
+ { "glob": "modules/lifecycle/db-claim-reads.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
45
+ { "glob": "modules/lifecycle/db-claims.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
46
+ { "glob": "modules/lifecycle/db-deps-criteria.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
47
+ { "glob": "modules/lifecycle/db-executor.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
48
+ { "glob": "modules/lifecycle/db-goals.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
49
+ { "glob": "modules/lifecycle/db-grade.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
50
+ { "glob": "modules/lifecycle/db-overrides.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
51
+ { "glob": "modules/lifecycle/db-rank-authz.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204): claimPermissionsFor, claimGate, deriveRequiredRank, singleRungPromotionError and _applyRankChangeTx — the claim gate and the rank-change rules. The authz-inventory tripwire in tests/government_protected_surfaces.mjs reds if one of them moves into an unprotected file." },
52
+ { "glob": "modules/lifecycle/db-relevance-flags.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
53
+ { "glob": "modules/lifecycle/db-shared.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
54
+ { "glob": "modules/lifecycle/db-ship.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
55
+ { "glob": "modules/lifecycle/db-tasks.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
56
+ { "glob": "modules/lifecycle/db-versions.js", "group": "authz", "why": "task 1004382. Carved out of lifecycle/db.js (task 1003204), which registering the facade alone would have left unprotected — the 1004024 carve rule: a protected file's <name>-*.js siblings are protected too." },
57
+ { "glob": "modules/lifecycle/claim-eligibility.js", "group": "authz", "why": "task 1004382. The QUEUE axis of the claim gate (ADR 0151, BV1.R106): permissionAllowsClaim and claimEligibilityAllows decide which tasks a builder's permissions let them claim at all." },
42
58
  { "glob": "src/bongos/routes.js", "group": "authz", "why": "the route MOUNT table (a new ungated route lands here)" },
43
59
  { "glob": "server.js", "group": "authz", "why": "the Express entrypoint — wires every router + host routing" },
44
60
  { "glob": "modules/government/", "group": "authz", "why": "the same authz core under its post-ADR-0174 name (goal 1000068, task 1003003). Listed BEFORE the folder moves, deliberately: `protected` is DERIVED from this registry, so a module key whose globs are absent here computes protected:false — which would silently drop the Archon gate on goal creation AND the pre-push block on these files for the whole window between the key landing and the folder arriving. The prospective-carve precedent is the modules/government/ entry directly above (ADR 0086 §4)." },
@@ -88,6 +104,16 @@
88
104
  { "glob": "scripts/bongos/main-audit.js", "group": "pipeline", "why": "the post-push audit — the authoritative substrate layer (self-protect)" },
89
105
  { "glob": "scripts/bongos/push-main.js", "group": "pipeline", "why": "the sanctioned main-push (carries the override)" },
90
106
  { "glob": "scripts/bongos/run-unit-tests.js", "group": "pipeline", "why": "the unit-gate test SELECTOR — muting tests here blunts the CI gate (ADR 0058)" },
107
+ { "glob": "scripts/bongos/fitness.js", "group": "pipeline", "why": "task 1004383. The architecture fitness gate CI runs on every merge, including the module-migration DDL check task 1003373 extends. scripts/gds/fitness.js is only a forwarder to it (already under scripts/gds/); the real gate sat unprotected." },
108
+ { "glob": "scripts/bongos/fitness-checks-error-envelope.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
109
+ { "glob": "scripts/bongos/fitness-checks-identity.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
110
+ { "glob": "scripts/bongos/fitness-checks-packaging.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
111
+ { "glob": "scripts/bongos/fitness-checks-route-pins.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
112
+ { "glob": "scripts/bongos/fitness-checks-vocabulary.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
113
+ { "glob": "scripts/bongos/fitness-checks-write-validation.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
114
+ { "glob": "scripts/bongos/fitness-lib.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
115
+ { "glob": "scripts/bongos/fitness-migration-contraction.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
116
+ { "glob": "scripts/bongos/fitness-ratchets.js", "group": "pipeline", "why": "task 1004383. A check file of the fitness CI gate, split out beside fitness.js; protected with it under the 1004024 carve rule, since muting a check here blunts the gate." },
91
117
  { "glob": "scripts/bongos/hard-floor.js", "group": "pipeline", "why": "task 1004859. The trust-boundary hard-floor list (self-protect). It moved here from scripts/bongos/gate-review.js when that never-run CI classifier was removed; publish-manifest.js and adopt-preflight.js read it." },
92
118
  { "glob": "scripts/bongos/setup.js", "group": "pipeline", "why": "/builder-setup — installs the pre-push hook" },
93
119
  { "glob": "scripts/bongos/install-git-hooks.js", "group": "pipeline", "why": "the hook installer (self-protect)" },
@@ -133,6 +133,8 @@ function aggregateShots(shotResults, meta = {}) {
133
133
  shot_index: s.signals?.shot_index ?? null,
134
134
  notes_md: s.notes_md,
135
135
  issues: s.issues || [],
136
+ exit_code: s.signals?.exit_code ?? null,
137
+ stderr_tail: s.signals?.stderr_tail || null,
136
138
  error:
137
139
  s.signals?.parse_error
138
140
  ? 'parse_error'
@@ -186,10 +188,26 @@ function aggregateShots(shotResults, meta = {}) {
186
188
  const errorCount = verdicts.filter((v) => v.error).length;
187
189
  const allPassed = N > 0 && passCount === N;
188
190
 
191
+ // task 1005101: EVERY shot errored → nothing was judged. This is the
192
+ // single-shot twin of the panel's #943 total outage (aggregatePanel), and it
193
+ // carries the same `panel_outcome: 'unavailable'` tag so ship.js prints the
194
+ // outage card instead of "the reviewers found problems in your work" — which
195
+ // is what a spend-limit or expired-sign-in exit printed before. passed stays
196
+ // false: an outage parks at completed for a re-grade or manual-confirm.
197
+ const allErrored = N > 0 && errorCount === N;
198
+
189
199
  let verdict;
190
200
  let passed;
191
201
  let aggregationKind;
192
- if (errorCount > 0) {
202
+ if (allErrored) {
203
+ passed = false;
204
+ const codes = [...new Set(verdicts.map((v) => v.exit_code).filter((c) => c != null))];
205
+ const tail = verdicts.map((v) => v.stderr_tail).find(Boolean);
206
+ const cause = [codes.length ? `exit ${codes.join('/')}` : null, tail ? `subprocess said: ${tail.trim().split('\n')[0]}` : null]
207
+ .filter(Boolean).join('; ');
208
+ verdict = `GRADER UNAVAILABLE — all ${N}/${N} shot(s) failed to run; no quality signal was produced. This is an infra/outage condition, NOT a quality rejection — manual-confirm available.${cause ? ` Diagnostic — ${cause}` : ''}`;
209
+ aggregationKind = 'unavailable';
210
+ } else if (errorCount > 0) {
193
211
  passed = false;
194
212
  verdict = `FAIL — ${errorCount}/${N} shot(s) errored`;
195
213
  aggregationKind = 'errored';
@@ -272,6 +290,15 @@ function aggregateShots(shotResults, meta = {}) {
272
290
  fail_count: failCount,
273
291
  error_count: errorCount,
274
292
  shot_aggregation: aggregationKind,
293
+ // task 1005101: the outage tag + the per-shot exit codes ship.js's outage
294
+ // card prints. Absent on any judged grade, so those keep their exact shape.
295
+ ...(allErrored ? {
296
+ panel_outcome: 'unavailable',
297
+ grader_unavailable_reason: 'all_shots_errored',
298
+ outage_shots: verdicts.map((v, i) => ({
299
+ kind: `shot ${v.shot_index ?? i}`, error: v.error, exit_code: v.exit_code, stderr_tail: v.stderr_tail,
300
+ })),
301
+ } : {}),
275
302
  ...meta,
276
303
  },
277
304
  notes_md: lines.join('\n'),
@@ -12,7 +12,7 @@ node modules/ui-design/kit/contrast.js --pack [look] | --url <url> [--glyphs sel
12
12
 
13
13
  A **look** is a branding pack from the module's style library (`modules/ui-design/styles/<name>/`, task 1003323): `STYLE=<name>` lays it over the instance's own pack through `GDS_BRANDING_FILE`, so any surface renders under any look with no page edit.
14
14
 
15
- Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row.
15
+ Two of the floors are about fit ([task 1004203](https://cloudbongos.com/builders#/task/1004203), [ADR 0329](../../../docs/adr/0329-the-rendered-fit-check-is-a-kit-floor-and-an-advisory-ship-check-row.md)): `text-fit` (text past its box, SVG text past its card) and `sibling-overlap` (text-bearing siblings on top of each other). `scripts/bongos/render-check.js` runs just those two over the pages a branch changed, and `/ship-check` shows them as an advisory row. A third in-page floor, `empty-graphic` ([task 1005104](https://cloudbongos.com/builders#/task/1005104)), reports a shown `<use>` or `<img>` that paints nothing — a referenced SVG that failed to parse leaves no console error and no failed request, so the logo vanished under ALL CLEAN.
16
16
 
17
17
  A surface describes itself in two JSON files beside it — `<page>.states.json` and `<page>.probes.json` — in the vocabularies `lib.js` defines and validates. A page served at the surface root (the hall's Home, `/builders/`) reads `index.states.json`. A file (or one state) may carry `"storage": { "session": {…}, "local": {…} }`, written into the browser before the page's own scripts run, so a first-visit question renders as a returning visitor sees it: every hall file seeds `hall-craft-asked` so the mode picker stays out of the shots ([task 1005080](https://cloudbongos.com/builders#/task/1005080)). A hall state that needs real data (a goal's detail, the Settings sections) passes against the hall-preview harness with `--base-in`/`--base-out`, one render at a time. The full loop, the file shapes and the failure table: [`docs/recipes/ui-look-before-you-ship.md`](../../../docs/recipes/ui-look-before-you-ship.md). Exit codes everywhere: 0 clean · 1 problems · 2 a broken file or usage · 3 no browser.
18
18
 
@@ -28,6 +28,7 @@ const FLOORS = Object.freeze([
28
28
  'horizontal-overflow', // documentElement.scrollWidth fits the viewport
29
29
  'text-fit', // no text runs past its box (HTML) or its card rect (SVG)
30
30
  'sibling-overlap', // no two in-flow, block-level, text-bearing siblings overlap
31
+ 'empty-graphic', // no shown <use> or <img> that paints nothing (a referenced SVG that failed to parse)
31
32
  'one-header-main-h1', // exactly one <header>, one <main>, one <h1>
32
33
  'controls-24px', // every visible control clears 24px on both sides
33
34
  'top-bar-80px', // the page's <header> is at most 80px tall
@@ -287,6 +288,25 @@ function factsInPage(spec) {
287
288
  }
288
289
  });
289
290
  out.textFit = textFit; out.overlaps = overlaps;
291
+ // The empty-graphic floor (task 1005104): a referenced SVG that fails to parse leaves its <use> empty, or its
292
+ // <img> with no natural size, and the logo vanishes with no console error and no failed request. A sprite's own
293
+ // <symbol>/<defs>, a 0x0 sprite sheet, a hidden graphic and a lazy image not yet loaded are not judged.
294
+ const ref = (el) => quote(String(el.getAttribute('href') || el.getAttribute('xlink:href') || el.getAttribute('src') || '').replace(/^data:([^,;]{0,24})[^]*?(#[\w-]*)?$/, 'data:$1…$2'));
295
+ const emptyGraphics = [];
296
+ qa('svg use').forEach((u) => {
297
+ if (u.closest('symbol, defs, mask, clipPath, pattern, marker') || !vis(u)) return;
298
+ const svg = u.ownerSVGElement; if (!svg) return;
299
+ const s = svg.getBoundingClientRect(); if (s.width < 1 || s.height < 1) return;
300
+ const r = u.getBoundingClientRect();
301
+ if (r.width < 1 || r.height < 1) emptyGraphics.push(`${where(u)} → "${ref(u)}" paints nothing`);
302
+ });
303
+ qa('img').forEach((im) => {
304
+ if (!vis(im) || (!im.complete && im.loading === 'lazy')) return;
305
+ const r = im.getBoundingClientRect();
306
+ if (im.complete && !im.naturalWidth) emptyGraphics.push(`${where(im)} → "${ref(im)}" did not decode`);
307
+ else if (r.width < 1 || r.height < 1) emptyGraphics.push(`${where(im)} → "${ref(im)}" renders ${Math.round(r.width)}x${Math.round(r.height)}`);
308
+ });
309
+ out.emptyGraphics = emptyGraphics;
290
310
  return out;
291
311
  }
292
312
  // Pure: compare collected facts against an expectation. Returns problem strings.
@@ -324,6 +344,7 @@ function auditFloors(facts, { width, mode } = {}) {
324
344
  const some = (list) => list.slice(0, 5).join(' | ') + (list.length > 5 ? ` (+${list.length - 5} more)` : '');
325
345
  if (facts.textFit && facts.textFit.length) p.push(`text-fit: ${some(facts.textFit)}`);
326
346
  if (facts.overlaps && facts.overlaps.length) p.push(`sibling-overlap: ${some(facts.overlaps)}`);
347
+ if (facts.emptyGraphics && facts.emptyGraphics.length) p.push(`empty-graphic: ${some(facts.emptyGraphics)}`);
327
348
  if (facts.headers !== 1 || facts.mains !== 1 || facts.h1s !== 1) p.push(`one-header-main-h1: header ${facts.headers} main ${facts.mains} h1 ${facts.h1s}`);
328
349
  if (facts.smallControls && facts.smallControls.length) p.push(`controls-24px: ${facts.smallControls.join(' | ')}`);
329
350
  if (facts.bar && facts.bar.h > 80) p.push(`top-bar-80px: header is ${facts.bar.h}px`);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.65",
3
+ "version": "1.24.67",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.65",
9
+ "version": "1.24.67",
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.24.65",
3
+ "version": "1.24.67",
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",
@@ -11365,5 +11365,45 @@
11365
11365
  "id": "1002985",
11366
11366
  "text": "The community page now has one test proving, on a real database, that hidden, private and new accounts never show on any leaderboard, search, crown or mover card, and that every week, month and year total is exact."
11367
11367
  }
11368
+ ],
11369
+ "1.24.66": [
11370
+ {
11371
+ "id": "1004382",
11372
+ "text": "The files that decide who may claim a task and how ranks change are now protected again: a builder below Metic can no longer edit them without review, and a new test fails if a future file split moves those rules somewhere unp"
11373
+ },
11374
+ {
11375
+ "id": "1005117",
11376
+ "text": "Signing in to a second Bongos site no longer signs you out of the first: each command now uses the sign-in for the site it is talking to."
11377
+ },
11378
+ {
11379
+ "id": "1005014",
11380
+ "text": "A worktree session now gets told its own branch and how far behind it is, instead of the main folder's — even when hooks fall back to running from the main folder."
11381
+ },
11382
+ {
11383
+ "id": "1005007",
11384
+ "text": "The privacy check that stops private accounts leaking now also catches database reads written in ordinary quotes, which it used to miss entirely."
11385
+ },
11386
+ {
11387
+ "id": "1004380",
11388
+ "text": "The pre-launch password page can no longer be used to crash the server: a wrong password with an accented letter used to take the whole site down; now it is just refused like any other wrong guess."
11389
+ },
11390
+ {
11391
+ "id": "1005104",
11392
+ "text": "The design checker now flags a logo or image that silently fails to show, instead of reporting everything clean."
11393
+ },
11394
+ {
11395
+ "id": "1005101",
11396
+ "text": "When the automatic reviewers could not run at all on a small change, Bongos now says it was an outage and names the cause, instead of telling the builder their work had problems."
11397
+ },
11398
+ {
11399
+ "id": "1004384",
11400
+ "text": "Sessions that start on a claimed task no longer receive raw idea titles from Discord: hint titles are now cleaned and boxed off as data, and outside ideas are left out, so a planted instruction can't steer the agent. The hints"
11401
+ }
11402
+ ],
11403
+ "1.24.67": [
11404
+ {
11405
+ "id": "1004383",
11406
+ "text": "The CI check that guards every merge is now protected, so a builder below Metic can no longer quietly switch its checks off. Protecting the credit-minting file was held back for the owner to decide, because it would lock the w"
11407
+ }
11368
11408
  ]
11369
11409
  }
@@ -54,7 +54,8 @@ const SESSION_READ_PATHS = SESSION_FILENAMES.flatMap((n) => ic.configReadPaths(n
54
54
  // default, so module-level consumers (reauth's Settings URL, the hall links
55
55
  // hallBase() prints) named the checkout's own instance instead of the one the
56
56
  // builder is signed into.
57
- const API_BASE = ic.resolveApiBase();
57
+ // Resolved from the session for the TARGET instance (task 1005117), not the bare active pointer.
58
+ const API_BASE = ic.resolveApiBase({ session: loadSessionSync() });
58
59
 
59
60
  // User-Agent for the GDS CLI's HTTP calls — the branding-derived neutral platform UA
60
61
  // from the single source, src/branding.js userAgent() (`<repo.name|envPrefix>-gds/1.0`,
@@ -251,7 +252,9 @@ async function migrateSessionFile(session) {
251
252
  }
252
253
  }
253
254
 
254
- async function loadSession() {
255
+ // The raw ACTIVE pointer — the single file every pre-store reader looked at, whatever instance it
256
+ // belongs to. saveSession archives THIS (it is what an outgoing sign-in would otherwise lose).
257
+ async function loadActiveSession() {
255
258
  // Try each read path in priority order (configured dir's bongos-session.json,
256
259
  // legacy otb dir, then each superseded filename across the same dirs). A hit on
257
260
  // anything OTHER than SESSION_PATH is migrated forward — SESSION_READ_PATHS[0]
@@ -267,6 +270,50 @@ async function loadSession() {
267
270
  return null;
268
271
  }
269
272
 
273
+ // The host this run TARGETS, or null when it names none (task 1005117). An explicit
274
+ // <PREFIX>_API_BASE wins (the operator is steering the base); otherwise a BRANDED checkout targets
275
+ // its own publicOrigin. A neutral checkout with no override targets nothing in particular, so it
276
+ // keeps reading the active pointer exactly as before.
277
+ // The env lookup honours an injected brand's own prefix, so a test can drive this in isolation.
278
+ function sessionTargetBase({ env = process.env, brand, hasInstanceConfig = defaultHasInstanceConfig } = {}) {
279
+ const b = brand || safeBranding();
280
+ const fromEnv = ic.resolveEnv('API_BASE', { env, prefix: ic.envPrefix(b) });
281
+ if (fromEnv) return fromEnv;
282
+ return brandedInstanceOrigin({ brand: b, hasInstanceConfig });
283
+ }
284
+
285
+ // Pick the session for the TARGET instance (task 1005117). The active pointer is ONE slot, so
286
+ // signing in to a second instance mid-run (toast.cloudbongos.com) rewrote it, and a run steered at
287
+ // the first then sent the second instance's token there — every claim and ship 401'd. When the
288
+ // target's host differs from the active session's and the per-instance store (below) holds a
289
+ // session for the target, that one wins. Otherwise the active session is returned unchanged —
290
+ // the backward compatibility with the single file, including a session from before the store.
291
+ //
292
+ // Split in two so the async path reads with fsp and the sync path with fs: targetStorePath() says
293
+ // WHICH store file to try (null = keep the active session), chooseSession() decides with what it read.
294
+ function targetStorePath(active, opts) {
295
+ const targetKey = sessionHostKey(sessionTargetBase(opts));
296
+ if (!targetKey) return null;
297
+ if (active && sessionHostKey(active.api_base) === targetKey) return null;
298
+ return path.join(sessionStoreDir(), `${targetKey}.json`);
299
+ }
300
+
301
+ function chooseSession(active, stored) {
302
+ return stored && stored.token ? stored : active;
303
+ }
304
+
305
+ function readJsonSync(p) {
306
+ try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return null; }
307
+ }
308
+
309
+ async function loadSession(opts) {
310
+ const active = await loadActiveSession();
311
+ const p = targetStorePath(active, opts);
312
+ if (!p) return active;
313
+ const stored = await readSessionFile(p).catch(() => null);
314
+ return chooseSession(active, stored);
315
+ }
316
+
270
317
  // ---- the shared session store (task 1003741, ADR 0269) ---------------------
271
318
  // ONE FILE PER INSTANCE, at a FIXED anchor — deliberately NOT configHome().
272
319
  //
@@ -336,7 +383,7 @@ async function saveSession(session) {
336
383
  await fsp.mkdir(dir, { recursive: true });
337
384
 
338
385
  try {
339
- const prev = await loadSession();
386
+ const prev = await loadActiveSession();
340
387
  const prevKey = prev && prev.token ? sessionHostKey(prev.api_base) : null;
341
388
  const nextKey = sessionHostKey(session && session.api_base);
342
389
  if (prevKey && prevKey !== nextKey) await writeSessionToStore(prev);
@@ -379,7 +426,7 @@ function listStoredSessions() {
379
426
  return out.sort((a, b) => a.host.localeCompare(b.host));
380
427
  }
381
428
 
382
- function loadSessionSync() {
429
+ function loadActiveSessionSync() {
383
430
  for (const p of SESSION_READ_PATHS) {
384
431
  if (!fs.existsSync(p)) continue;
385
432
  try {
@@ -391,6 +438,13 @@ function loadSessionSync() {
391
438
  return null;
392
439
  }
393
440
 
441
+ // Synchronous twin of loadSession — the session for the TARGET instance (task 1005117).
442
+ function loadSessionSync(opts) {
443
+ const active = loadActiveSessionSync();
444
+ const p = targetStorePath(active, opts);
445
+ return p ? chooseSession(active, readJsonSync(p)) : active;
446
+ }
447
+
394
448
  // ---- argv helpers (C11) ---------------------------------------------------
395
449
  // One home for the flag parsing every GDS CLI rolled by hand. Two contracts:
396
450
  // arg(name) → the flag's VALUE ('--out x' or '--out=x'), else null
@@ -564,6 +618,14 @@ function defaultHasInstanceConfig() {
564
618
  } catch { return false; }
565
619
  }
566
620
 
621
+ // THIS checkout's declared instance origin, or null for a neutral checkout / no publicOrigin. The
622
+ // one place both the session picker (sessionTargetBase) and the wrong-instance guard
623
+ // (detectInstanceMismatch) read it, so the two cannot drift (task 1005117).
624
+ function brandedInstanceOrigin({ brand = safeBranding(), hasInstanceConfig = defaultHasInstanceConfig } = {}) {
625
+ if (!hasInstanceConfig()) return null;
626
+ return (brand && brand.domains && brand.domains.publicOrigin) || null;
627
+ }
628
+
567
629
  // Host of an origin string, or null if unparseable. Comparing HOSTS (not full
568
630
  // origins) tolerates scheme / trailing-slash noise between branding + session.
569
631
  function instanceOriginHost(origin) {
@@ -586,8 +648,7 @@ function instanceOriginHost(origin) {
586
648
  function detectInstanceMismatch({ session, brand = safeBranding(), env = process.env, hasInstanceConfig = defaultHasInstanceConfig } = {}) {
587
649
  if (ic.resolveEnv('API_BASE', { env })) return { mismatch: false };
588
650
  if (!session || !session.api_base) return { mismatch: false };
589
- if (!hasInstanceConfig()) return { mismatch: false };
590
- const expected = brand && brand.domains && brand.domains.publicOrigin;
651
+ const expected = brandedInstanceOrigin({ brand, hasInstanceConfig });
591
652
  if (!expected) return { mismatch: false };
592
653
  const expectedHost = instanceOriginHost(expected);
593
654
  const actualHost = instanceOriginHost(session.api_base);
@@ -1184,6 +1245,8 @@ module.exports = {
1184
1245
  SESSION_READ_PATHS,
1185
1246
  loadSession,
1186
1247
  loadSessionSync,
1248
+ sessionTargetBase,
1249
+ targetStorePath,
1187
1250
  saveSession,
1188
1251
  sessionStoreDir,
1189
1252
  sessionHostKey,
@@ -294,7 +294,9 @@ async function gradeAndRecord({ taskId, task, summary, baseline, committedFiles,
294
294
  // spawned here) so formatGradeOutcome stays unit-testable without a subprocess.
295
295
  function formatOutageDiagnostic(g, cliFinding) {
296
296
  const out = [];
297
- const dead = (g.signals?.workers || []).filter((w) => w && w.error);
297
+ // task 1005101: a single-shot outage (trivial diff / no-artifact) has no
298
+ // panel workers; its dead shots ride signals.outage_shots in the same shape.
299
+ const dead = (g.signals?.workers || g.signals?.outage_shots || []).filter((w) => w && w.error);
298
300
  const detailed = dead.filter((w) => w.exit_code != null || w.stderr_tail);
299
301
  if (detailed.length) {
300
302
  out.push('');