@bongos/core 1.24.64 → 1.24.66

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,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.24.64",
6
- "core_contract": "1.24.64",
7
- "source_commit": "3ec2f53ca824f8f5ae9954733449d50e6d771d78",
5
+ "core_version": "1.24.66",
6
+ "core_contract": "1.24.66",
7
+ "source_commit": "c8dd200682525465ab4c31d562c49ede1a515404",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-08T11:46:13.511Z",
9
+ "built_at": "2026-11-09T19:50:40.752Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 638,
13
13
  "agent_docs_stubbed": 32,
14
- "functional_verbatim": 3902,
14
+ "functional_verbatim": 3904,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 4573,
20
- "tree_sha256": "05be327dd13c886de8623f745d2aafc5a44e53b1474621c1d66528fb88bbc306",
19
+ "file_count": 4575,
20
+ "tree_sha256": "8863cadeb49c7cd719e61a10cdc6f7fb0e2452e1e6e52089b74b997125377f11",
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": "6f54bb50baf24667d580a0a1800893b4f274ec757b1766230ab2d99f80cf2e21"
3010
+ "sha256": "d9de836d931d1f89f45b5500fc6b7eceed36543deb0dc521decb3b1722074a69"
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": "12308c4d0a2f58fe874ef8b3ed4698709869a42c24c08d2b72011bd81c0ff160"
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": "ebf93f1762d8c434f4be706a146234bc6047b37e2c1ebcca6c961da817a57b24"
11905
+ "sha256": "6404eb74f3c63e5cbd0cc64d9a18d2a860b758a8077199241d508938f556ddb5"
11906
11906
  },
11907
11907
  {
11908
11908
  "path": "package.json",
11909
11909
  "mode": "0000644",
11910
- "sha256": "f878eabd1e4556e46155b760462e8f433a1472ecc771fed5638011a401844af6"
11910
+ "sha256": "e6fa2b150dfc6c0ebdd2fb524ba028608c04ff5d698ab8ab4be2cbc62a7c2501"
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": "c56e5afc1883d40326c974209ebfe30d603375a991532ae4775c55f1cda428df"
11930
+ "sha256": "f21fea4a4cb0dd1eceb683e196ac37110931e63de4290b1d99614d747f30fb78"
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",
@@ -13177,7 +13177,7 @@
13177
13177
  {
13178
13178
  "path": "scripts/bongos/run-unit-tests.js",
13179
13179
  "mode": "0000644",
13180
- "sha256": "be2eb571965101b309308d73d2bea556ea7a9ff9550ec24542df61b904646d60"
13180
+ "sha256": "85fc4fb8e3940c71b4f2eac604374b583bca16e6e590847d597cf9e59fbb44f7"
13181
13181
  },
13182
13182
  {
13183
13183
  "path": "scripts/bongos/runner-drift.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": "6937e1a1767458c2da6342b38b7631c63d87752688584ca9b5fa59cd94b9a026"
15880
+ "sha256": "66cdc6ea2874f48cf67645553922f875fb60cfc951e7f075ac58ce2ab9adb660"
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",
@@ -16974,6 +16974,11 @@
16974
16974
  "mode": "0000644",
16975
16975
  "sha256": "386bea21404a5921a450adf251bb9cb54694684941a5b3d3b000a266cc9d09f7"
16976
16976
  },
16977
+ {
16978
+ "path": "tests/community_proof_db.mjs",
16979
+ "mode": "0000644",
16980
+ "sha256": "a68d8274db242141006e661f3a5879efd769d1facc25fc5e2c362abe80a4084e"
16981
+ },
16977
16982
  {
16978
16983
  "path": "tests/community_search_route.mjs",
16979
16984
  "mode": "0000644",
@@ -17597,7 +17602,7 @@
17597
17602
  {
17598
17603
  "path": "tests/effective_visibility_predicate.mjs",
17599
17604
  "mode": "0000644",
17600
- "sha256": "7bd63d2a1f097fdde204803ec8b6fc0ae75843080af6d442c969ed02b7fb41dd"
17605
+ "sha256": "4a7bb44f07a87997830e44636490a4992d862d43b2620a2d583370869f6b477d"
17601
17606
  },
17602
17607
  {
17603
17608
  "path": "tests/env_knobs_neutral_prefix.mjs",
@@ -17769,6 +17774,11 @@
17769
17774
  "mode": "0000644",
17770
17775
  "sha256": "a86b984c420c260c5298b1fd15c48533a27a4ead3ad63543bdfa63ecd6b37949"
17771
17776
  },
17777
+ {
17778
+ "path": "tests/fixtures/privacy-matrix.mjs",
17779
+ "mode": "0000644",
17780
+ "sha256": "a540792ffd23206b4e37b9fca7b8498e55de853211f4714cad791af28029af9c"
17781
+ },
17772
17782
  {
17773
17783
  "path": "tests/fixtures/seed-tasks/bongos-v1-idea-routing.json",
17774
17784
  "mode": "0000644",
@@ -18427,7 +18437,7 @@
18427
18437
  {
18428
18438
  "path": "tests/government_protected_surfaces.mjs",
18429
18439
  "mode": "0000644",
18430
- "sha256": "3ecd666b5c5227295c04d781cfd554012ce3cabd38167c3947a86379f1b6004b"
18440
+ "sha256": "edbc428d065ead0a9fd9ba02218fa25f72f1646e594d01386f13256b0621490f"
18431
18441
  },
18432
18442
  {
18433
18443
  "path": "tests/government_rank_sync.mjs",
@@ -18562,7 +18572,7 @@
18562
18572
  {
18563
18573
  "path": "tests/grade_unavailable_display.mjs",
18564
18574
  "mode": "0000644",
18565
- "sha256": "5842055bf9e1871b467ab682b24589f0c9523561090a026021cb898380508524"
18575
+ "sha256": "63135247609c11501f0d7af190343193a5dab334f7186cd05ab02fdbf1ea3644"
18566
18576
  },
18567
18577
  {
18568
18578
  "path": "tests/grader.mjs",
@@ -20687,7 +20697,7 @@
20687
20697
  {
20688
20698
  "path": "tests/prelaunch_gate.mjs",
20689
20699
  "mode": "0000644",
20690
- "sha256": "01cd4d1aa256c1d44fb2da98a4347aeb3e75e1aeae47f338d37f29ebec03e556"
20700
+ "sha256": "1eea91518ffdf65206d9b0a0094269a426268b0da4abb578a994150f7632ffd7"
20691
20701
  },
20692
20702
  {
20693
20703
  "path": "tests/preview_server.mjs",
@@ -20697,7 +20707,7 @@
20697
20707
  {
20698
20708
  "path": "tests/privacy_matrix.mjs",
20699
20709
  "mode": "0000644",
20700
- "sha256": "a2592ce72e24d6bb5acee360594ae48b3b6d23ad93467953b4745d3eb552cfd9"
20710
+ "sha256": "7a9aea3dd6db1b8048b35cf64624553fa148a9cd03030fa336eb9453ba5068bb"
20701
20711
  },
20702
20712
  {
20703
20713
  "path": "tests/probe_cache.mjs",
@@ -21347,7 +21357,7 @@
21347
21357
  {
21348
21358
  "path": "tests/recall_hints.mjs",
21349
21359
  "mode": "0000644",
21350
- "sha256": "0ed0ecffcefe92b2b4db63e2c85858dcc7cb0c991a7453373cb7b54c523978a9"
21360
+ "sha256": "35af628eeda697d97bfaaba5052fc0f4928ae174ef04ec3afc7d3470dfc62dfe"
21351
21361
  },
21352
21362
  {
21353
21363
  "path": "tests/recent_shipped_window.mjs",
@@ -21777,7 +21787,7 @@
21777
21787
  {
21778
21788
  "path": "tests/session_start_freshness.mjs",
21779
21789
  "mode": "0000644",
21780
- "sha256": "d4c03dfb66690da1d870668e7b68c3bd0a35dc1a8a90986c6aad9586138ed1e1"
21790
+ "sha256": "acdee57f83af053e86d16ce9764647fe750ca33c7f24313a6e60b6ba9180dc49"
21781
21791
  },
21782
21792
  {
21783
21793
  "path": "tests/session_token_hash.mjs",
@@ -22557,12 +22567,12 @@
22557
22567
  {
22558
22568
  "path": "tests/ui_design_kit.mjs",
22559
22569
  "mode": "0000644",
22560
- "sha256": "74b63327d4042cf3be0f2abf5b0e7298319f6ed69ea488246e88e49923b26bc1"
22570
+ "sha256": "f5160521be5a2edecfe8997275cbbc65b8cf6ac8aac7cca2e90e23378d4e64f3"
22561
22571
  },
22562
22572
  {
22563
22573
  "path": "tests/ui_design_kit_fit.mjs",
22564
22574
  "mode": "0000644",
22565
- "sha256": "0f07a002d400c4e90ead7206334425bd80647a2cb844bbc9733758eb3f719a12"
22575
+ "sha256": "010a7b1427aa28d4807093f2ff31871058b366c46daafb1818cda2eee4f404cc"
22566
22576
  },
22567
22577
  {
22568
22578
  "path": "tests/ui_design_skills.mjs",
@@ -3382,5 +3382,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3382
3382
  landed since 1.24.62 with no explicit bump. run 42392141827. (task 1002620)
3383
3383
  1.24.64 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3384
3384
  landed since 1.24.63 with no explicit bump. run 42477114353. (task 1002620)
3385
+ 1.24.65 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
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)
3385
3389
  ---------------------------------------------------------------------------
3386
3390
  ```
@@ -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)." },
@@ -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.64",
3
+ "version": "1.24.66",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.64",
9
+ "version": "1.24.66",
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.64",
3
+ "version": "1.24.66",
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",
@@ -11359,5 +11359,45 @@
11359
11359
  "id": "1005177",
11360
11360
  "text": "The next version is scoped. The builders' hall becomes a game you are inside rather than a website you navigate: three characters to play as, a city that grows as the project grows, and work claimed as quest chains instead of"
11361
11361
  }
11362
+ ],
11363
+ "1.24.65": [
11364
+ {
11365
+ "id": "1002985",
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
+ }
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
+ }
11362
11402
  ]
11363
11403
  }
@@ -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,
@@ -88,6 +88,10 @@ const INTEGRATION = new Set([
88
88
  // year's baseline over 30 days, a quiet row's live total, hidden and private left out.
89
89
  // The DB-free sibling is community_movers.
90
90
  'community_movers_db',
91
+ // task 1002985: the community proof — one fixture per privacy-matrix row and one snapshot
92
+ // history through every community route on real SQL. Its DB-free sibling is the
93
+ // privacy-matrix proof, which must stay unnamed here (it asserts that it is).
94
+ 'community_proof_db',
91
95
  // task 1004936: the Market Watch tables, hub seed and read queries against real SQL. The
92
96
  // CHECKs, partial unique indexes and ON CONFLICT targets are what make the seed
93
97
  // idempotent and a resumed run unable to record an item twice. DB-free sibling: