@bongos/core 1.19.43 → 1.19.45

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/gds/package-core.js",
5
- "core_version": "1.19.43",
6
- "core_contract": "1.19.43",
7
- "source_commit": "05c2bc1f374ff18405ce4b57b143272651a69fc4",
5
+ "core_version": "1.19.45",
6
+ "core_contract": "1.19.45",
7
+ "source_commit": "290f271a1fb465184f1e30a82938dbb9fc0edd7f",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-11T17:22:46.634Z",
9
+ "built_at": "2026-08-11T17:49:40.375Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 290,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1213,
14
+ "functional_verbatim": 1214,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1524,
20
- "tree_sha256": "eb5d84a56e1d201e95d819069e5ded36815bf9fb42f87cc3e010519919ca6782",
19
+ "file_count": 1525,
20
+ "tree_sha256": "0722aab68f68291e412986ff75bc5c9e53914d42248afd4dd8f3fddef07c1d82",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1402,12 +1402,12 @@
1402
1402
  {
1403
1403
  "path": "docs/onboarding/diagrams/03-drachmae-karma.mmd",
1404
1404
  "mode": "0000644",
1405
- "sha256": "3cdfcee86d62a16c1a406dab7720eebf3eacc0fb603dc214e5520b2d826ffe97"
1405
+ "sha256": "2e43c5910ed036657a32faa1ec1af7f5bb1bfcbc5f743a276b44b3b8b8157e4e"
1406
1406
  },
1407
1407
  {
1408
1408
  "path": "docs/onboarding/diagrams/04-architecture.mmd",
1409
1409
  "mode": "0000644",
1410
- "sha256": "2b8f478a8435088ed836044c4817c1e7ebaf0bc7c19998f19f40e5325594fbc0"
1410
+ "sha256": "4af79f61f5b1871e9c2ffaa4cee703f681516f2720782b4db05b797993e0a293"
1411
1411
  },
1412
1412
  {
1413
1413
  "path": "docs/onboarding/diagrams/05-versioning.mmd",
@@ -1422,7 +1422,7 @@
1422
1422
  {
1423
1423
  "path": "docs/onboarding/diagrams/assertions.json",
1424
1424
  "mode": "0000644",
1425
- "sha256": "d7a8120a3a2ad20f1be87da516a3f475b03b2a1f961499f7cbb8d77a1a621886"
1425
+ "sha256": "2716fcbc113ef046610023ff9bfefa57fe6564ead685cc3cf507f3d893ff0cd5"
1426
1426
  },
1427
1427
  {
1428
1428
  "path": "docs/onboarding/diagrams/puppeteer-config.json",
@@ -2912,7 +2912,7 @@
2912
2912
  {
2913
2913
  "path": "modules/grading/grader.js",
2914
2914
  "mode": "0000644",
2915
- "sha256": "3d16940e2bb354bc029893abc5cdd1a3e4868d51c65e5fed22cabf6610c69533"
2915
+ "sha256": "8073955da873ca3fa779ef614368af52e9f0be93b800f0590f07d34fceca5545"
2916
2916
  },
2917
2917
  {
2918
2918
  "path": "modules/grading/module.json",
@@ -4032,12 +4032,12 @@
4032
4032
  {
4033
4033
  "path": "package-lock.json",
4034
4034
  "mode": "0000644",
4035
- "sha256": "db2480e2132dffe5cc11ccdcf5871afaeabd1fe07fcacd68acf85179ad5169f6"
4035
+ "sha256": "b2fc08b1d386c093629982f52a456c2b79c9f3824a22a696ed6fdb37a9073def"
4036
4036
  },
4037
4037
  {
4038
4038
  "path": "package.json",
4039
4039
  "mode": "0000644",
4040
- "sha256": "29562210449cebade43e9b7a27da435784bdabd3313f6b41e05ff0059f0bd2d3"
4040
+ "sha256": "d955953100c62b9b03bec90b70122f0a35d18bed91e60af2ae78f0573509a852"
4041
4041
  },
4042
4042
  {
4043
4043
  "path": "public-docs/index.html",
@@ -4667,7 +4667,7 @@
4667
4667
  {
4668
4668
  "path": "scripts/gds/diagram-facts.js",
4669
4669
  "mode": "0000644",
4670
- "sha256": "8c6d81c0495d05833bf5cb3d0be8ff6a29fabc24231206391f36b0c85f36b121"
4670
+ "sha256": "fe57f283f06985234123a46b5ae055f4ecd9df26834cb99e58228a5cb649e0aa"
4671
4671
  },
4672
4672
  {
4673
4673
  "path": "scripts/gds/discipline-modes.json",
@@ -5657,7 +5657,7 @@
5657
5657
  {
5658
5658
  "path": "src/module-api.js",
5659
5659
  "mode": "0000644",
5660
- "sha256": "ec430cc3f3ca679123babbd62b5e512bc542ac4c0ff28d3970450d226180c9f9"
5660
+ "sha256": "ef3fa14314f284d7e93062ddc0b9ee7a046885bb13b20e41f62c7ef66eb29a24"
5661
5661
  },
5662
5662
  {
5663
5663
  "path": "src/module-loader/loader.js",
@@ -6259,6 +6259,11 @@
6259
6259
  "mode": "0000644",
6260
6260
  "sha256": "c8547f65d1cff189f88afa528489f11585385f7a5cee868ed2fb7e474b47b614"
6261
6261
  },
6262
+ {
6263
+ "path": "tests/diagram_facts_offset.mjs",
6264
+ "mode": "0000644",
6265
+ "sha256": "36a3bad951c6bfb4ee7b9230a5e6114095bb3b94f1f0019ec5c61c5168dee537"
6266
+ },
6262
6267
  {
6263
6268
  "path": "tests/diff_derived_signals.mjs",
6264
6269
  "mode": "0000644",
@@ -1,13 +1,13 @@
1
1
  ---
2
- title: Drachmae & karma — where each comes from, where each goes (AUTO-GENERATED from live facts — do not hand-edit; see scripts/gds/gen-diagrams.js)
2
+ title: Credits & karma — where each comes from, where each goes (AUTO-GENERATED from live facts — do not hand-edit; see scripts/gds/gen-diagrams.js)
3
3
  ---
4
4
  flowchart LR
5
- subgraph CURRENCY["DRACHMAE (the builder reward — equity / reputation, a stake, not a spendable currency)"]
5
+ subgraph CURRENCY["CREDITS (the builder reward — equity / reputation, a stake, not a spendable currency)"]
6
6
  direction TB
7
7
  D_TOKEN["Ship a SESSION — the main reward\nround(true_cost_usd × 1.20) cents — cost-plus on real token spend\n(LIVE: priced server-side from token counts; ADR 0054, idempotent per session)"]
8
- D_SHIP["Confirm a TASK\ncredits_reward × kind multiplier (0.8–1.5×)\n+ 25% idea-promotion bonus to the capturer\n(LIVE: ADR 0023)"]
8
+ D_SHIP["SHIP a TASK — books when the code lands on main, not at confirm\ncredits_reward × kind multiplier (0.8–1.5×)\n+ 25% idea-promotion bonus to the capturer\n(LIVE: ADR 0023; pay-on-land ADR 0120; withheld entirely on a cost-plus-only instance, ADR 0146)"]
9
9
  D_ACH["Unlock an achievement\nachievements.credit_bonus (LIVE: 9 seeded)\n+ small net-negative-ship bonus"]
10
- D_BOUNTY["Red-team / security bounty\ndrachmae payout by severity\n(rate card LIVE: R65 #284 — src/bongos/security.js + shown on /builders;\nawarding still PLANNED: R67 #286 + vulnerability_reports R63 #282)"]
10
+ D_BOUNTY["Red-team / security bounty\ncredits payout by severity\n(rate card LIVE: R65 #284 — src/bongos/security.js + shown on /builders;\nawarding still PLANNED: R67 #286 + vulnerability_reports R63 #282)"]
11
11
 
12
12
  D_BAL["builders.total_credits\n(running balance per builder)"]
13
13
 
@@ -16,7 +16,7 @@ flowchart LR
16
16
  D_ACH --> D_BAL
17
17
  D_BOUNTY -.PLANNED.-> D_BAL
18
18
 
19
- D_SPEND["Spent on: nothing yet.\nDrachmae are EQUITY / reputation — a stake, not a\nspendable currency. They persist losslessly across exit (migration 025)."]
19
+ D_SPEND["Spent on: nothing yet.\nCredits are EQUITY / reputation — a stake, not a\nspendable currency. They persist losslessly across exit (migration 025)."]
20
20
  D_BAL --> D_SPEND
21
21
  end
22
22
 
@@ -32,6 +32,6 @@ flowchart LR
32
32
  K_BAL --> K_SURF
33
33
  end
34
34
 
35
- note1["Drachmae measure OUTPUT (work shipped) — most of it now a cost-plus reward on real token spend.\nKarma measures PEER TRUST (how others rate your work); it does NOT gate rank promotions.\nBoth are reputation; neither is a spendable currency today.\nSeparately, an Archon can set a per-builder monthly budget cap to limit SPEND — but builders are uncapped by default (src/bongos/cost.js)."]
35
+ note1["Credits measure OUTPUT (work shipped) — most of it now a cost-plus reward on real token spend.\nKarma measures PEER TRUST (how others rate your work); it does NOT gate rank promotions.\nBoth are reputation; neither is a spendable currency today.\nSeparately, an Archon can set a per-builder monthly budget cap to limit SPEND — but builders are uncapped by default (src/bongos/cost.js)."]
36
36
  CURRENCY --- note1
37
37
  KARMA --- note1
@@ -13,25 +13,26 @@ flowchart TB
13
13
  CODE["the code — git clone + per-builder worktrees (.claude/worktrees/*)\nfor sub-Metic builders it lives ONLY here · CLAUDE.md is DESCRIPTIVE only"]
14
14
  LOCAL["local ~/.claude memory\nMAP = MEMORY.md — a one-line index loaded EVERY session\n(a guard + auto-tidy keep it light; ADR 0074)\n+ drawer of full notes, loaded only on demand"]
15
15
  ORIGIN["credential-less git origin\n(NO GitHub push secret on the box — by design)"]
16
- BOXTOK["box-scoped GDS token\n(deny-by-default: bootstrap endpoints only;\nrejected for branch upload → re-auth first — LIVE)"]
16
+ BOXTOK["box-scoped Bongos token\n(deny-by-default: bootstrap endpoints only;\nrejected for branch upload → re-auth first — LIVE)"]
17
17
  PREVIEW["sandbox preview server on :3000\n→ a per-builder sandbox URL (LIVE)"]
18
18
  SWITCH["on / off + browser terminal\n(desktop app LIVE · ttyd-over-tunnel LIVE)"]
19
19
  end
20
20
 
21
- subgraph SERVER["THE SERVER — amazonprimea.com (the authority AND production)"]
22
- API["/api/gds/* (same Node process as the game)"]
21
+ subgraph SERVER["THE INSTANCE SERVER — localhost:3000 (the authority AND production)"]
22
+ API["/api/bongos/* (same Node process as the app)"]
23
+ CORE["@bongos/core — the build platform, VENDORED as a pinned dependency\n(ADR 0108: central updates are a version-pin bump via `bongos upgrade`, not a merge)"]
23
24
  DB["Postgres — tasks · claims · versions · builders.rank\ncredit_log · grades · idea_inbox · builder_memory\n(KING for work-state + permissions; CLAUDE.md only describes it)"]
24
25
  RANK["auth.requireRank — checked per request, NO caching\n(ADR 0016: a demotion takes effect on the next request)"]
25
- SRVMEM["server-side builder_memory store\nLIVE: per-builder · isolated · Archon-audited forensic read\nyour memory follows you across machines & worktrees\n/recall fetches only the relevant subset (ADR 0060)\n(R54 / R55 / R56 / R57 — all shipped)"]
26
+ SRVMEM["server-side builder_memory store\nLIVE: per-builder · isolated · forensic read at Metic+ (ADR 0157)\nyour memory follows you across machines & worktrees\n/recall fetches only the relevant subset (ADR 0060)\n(R54 / R55 / R56 / R57 — all shipped)"]
26
27
  PUSHTOK["server-held push credential\n(GitHub App, short-lived tokens — the box never holds it)"]
28
+ DEPLOY["auto-pull deploy (ADR 0129)\na timer pulls THIS instance repo every ~2 min →\nnpm ci if the pin changed → migrate → regenerate docs → restart → healthz"]
27
29
  end
28
30
 
29
- subgraph GH["GITHUB — the code repository (github.com/lars589/amazonprimea)"]
30
- MAIN["branch-protected main\n(no direct push; PR + required checks only)"]
31
- ACTIONS["GitHub Actions — deploy-prod.yml"]
31
+ subgraph GH["GITHUB — the instance's own repo (github.com/lars589/cloud-bongos)"]
32
+ MAIN["branch-protected main\n(no direct push; PR + required checks only)\nholds config + the pinned @bongos/core — NOT the platform source"]
32
33
  end
33
34
 
34
- GAME["the live game — amazonprimea.com\n(same server · same Node process as /api/gds/*)"]
35
+ LIVEAPP["the live app — localhost:3000\n(same server · same Node process as /api/bongos/*)"]
35
36
 
36
37
  APP -->|"1 · toggle box on / open terminal"| SWITCH
37
38
  SWITCH --> WORKER
@@ -39,13 +40,14 @@ flowchart TB
39
40
  WORKER -->|"3 · re-auth in browser"| API
40
41
  WORKER -->|"4 · upload branch as a git bundle"| API
41
42
  API -->|"5 · push + open PR + auto-merge (LIVE)"| MAIN
42
- MAIN -->|"6 · merge triggers deploy (LIVE)"| ACTIONS
43
- ACTIONS -->|"7 · deploy (no droplet key on any box)"| GAME
43
+ MAIN -->|"6 · the instance auto-pulls its own main"| DEPLOY
44
+ DEPLOY -->|"7 · migrate · regenerate docs · restart"| LIVEAPP
45
+ CORE --- API
44
46
  PUSHTOK -.-> API
45
47
  ORIGIN -. "direct push blocked" .-> MAIN
46
48
  API -. "claim / ship / cost" .-> WORKER
47
49
  LOCAL -->|"sync on ship (push)"| SRVMEM
48
50
  SRVMEM -->|"pull on session start"| LOCAL
49
51
 
50
- NEXT["The whole box → production path is LIVE and hardware-verified end-to-end (1027 · 1028)."]
51
- GAME --- NEXT
52
+ NEXT["The whole box → production path is LIVE: a credential-less box uploads its branch, the server merges it, and the instance auto-pulls + redeploys — hardware-verified end-to-end (1027 · 1028)."]
53
+ LIVEAPP --- NEXT
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "architecture": {
109
109
  "diagram": "04-architecture",
110
- "_comment": "Pieces of 04-architecture gated on a backing GDS task. A piece is LIVE iff its task is 'shipped'; live_tasks must ALL be shipped, pending_tasks must NOT yet be. Rebuilt by scripts/gds/gen-diagrams.js in lockstep with the diagram, so this is the drift checker's backstop. The device/box/server/GitHub topology is structural (not task-gated); these are the LIVE/PLANNED markers. MEMORY layer (ADR 0024): 273 schema / 274 sync-on-ship / 275 pull-on-start / 276 isolation. DEV BOX + box→prod: 904/774 (on-off + browser terminal), 897 (sandbox preview), 919 (scoped box token), 1025 (server-mediated publish), 859 (CI deploy), 1028 (GitHub App push credential), 1027 (end-to-end hardware-verify). Supersedes the old server_repo_memory + dev_box slices.",
110
+ "_comment": "Pieces of 04-architecture gated on a backing GDS task. A piece is LIVE iff its task is 'shipped'; live_tasks must ALL be shipped, pending_tasks must NOT yet be. Rebuilt by scripts/gds/gen-diagrams.js in lockstep with the diagram, so this is the drift checker's backstop. The device/box/server/GitHub topology is structural (not task-gated); these are the LIVE/PLANNED markers. MEMORY layer (ADR 0024): 273 schema / 274 sync-on-ship / 275 pull-on-start / 276 isolation. DEV BOX + box→prod: 904/774 (on-off + browser terminal), 897 (sandbox preview), 919 (scoped box token), 1025 (server-mediated publish), 1028 (GitHub App push credential), 1027 (end-to-end hardware-verify). The deploy leg is the standalone AUTO-PULL (ADR 0108/0129), not a task-gated GitHub-Actions CI job, so 859 is intentionally absent. Supersedes the old server_repo_memory + dev_box slices.",
111
111
  "live_tasks": [
112
112
  {
113
113
  "id": 273,
@@ -123,7 +123,7 @@
123
123
  },
124
124
  {
125
125
  "id": 276,
126
- "label": "R57 — per-builder isolation + Archon audited forensic read"
126
+ "label": "R57 — per-builder isolation + forensic read at Metic+ (ADR 0157)"
127
127
  },
128
128
  {
129
129
  "id": 904,
@@ -145,10 +145,6 @@
145
145
  "id": 1025,
146
146
  "label": "server-mediated branch publish (box uploads bundle, server pushes)"
147
147
  },
148
- {
149
- "id": 859,
150
- "label": "CI self-deploy (Actions deploys main to the droplet)"
151
- },
152
148
  {
153
149
  "id": 1027,
154
150
  "label": "hardware-verify the box-to-prod flow end-to-end"
@@ -23,8 +23,9 @@
23
23
  //
24
24
  // ## What's new in v4
25
25
  //
26
- // 3. WORKER PANEL. gradeSubagent runs DEFAULT_PANEL (narc + quality) in
27
- // parallel; aggregatePanel applies the gate. Each worker has its own
26
+ // 3. WORKER PANEL. gradeSubagent runs DEFAULT_PANEL in parallel (narc +
27
+ // quality at v4; hacker + efficiency joined in Phase 2.1/2.2 — 4 workers
28
+ // today); aggregatePanel applies the gate. Each worker has its own
28
29
  // prompt, parser, scoring, and per-worker pass/concerns/fail verdict.
29
30
  // 4. STRUCTURED VERDICTS. pass | concerns | fail (not just pass/fail).
30
31
  // 'concerns' falls through to the manual-confirm path the same way
@@ -42,8 +43,10 @@
42
43
  // gradeSubagent({task, valueSummary, diff, changedFiles, diffStats,
43
44
  // workers?, builderModel, runOpts})
44
45
  // → end-to-end panel grade-result. workers? defaults to DEFAULT_PANEL
45
- // (narc + quality). Wraps subprocess failures so resolveClaim/applyGrade
46
- // can always write task_grades.
46
+ // (narc + quality + hacker + efficiency — the full 4-worker panel, so an
47
+ // honest ship always clears hasPanelEvidence's ≥3-worker floor). Wraps
48
+ // subprocess failures so resolveClaim/applyGrade can always write
49
+ // task_grades.
47
50
  //
48
51
  // aggregatePanel(workerResults, meta)
49
52
  // → final grade-result from per-worker WorkerResults. Applies the
@@ -1606,8 +1609,11 @@ function graderUnavailableResult({ reason, note, meta = {} } = {}) {
1606
1609
  // End-to-end v4 grader. Runs the worker panel through the cross-family
1607
1610
  // grader model and returns the panel-aggregated grade-result.
1608
1611
  //
1609
- // workers: defaults to DEFAULT_PANEL (narc + quality). An array of worker
1610
- // kinds (strings) like ['narc', 'quality']. Phase 2.1/2.2 will extend.
1612
+ // workers: defaults to DEFAULT_PANEL (narc + quality + hacker + efficiency —
1613
+ // the full 4-worker panel since Phase 2.1/2.2). An array of worker kinds
1614
+ // (strings) like ['narc', 'quality']. A default-panel run always satisfies
1615
+ // hasPanelEvidence's ≥3-worker floor, so an honest unanimous zero-issue
1616
+ // PASS is waived, never manual-confirm-gated (task 1002664 / ADR 0164).
1611
1617
  // builderModel: the model the builder used. Defaults to ASSUMED_BUILDER_MODEL
1612
1618
  // (Opus, per CLAUDE.md §2). Drives selectGraderModel — workers pick the
1613
1619
  // cross-family alternative.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.43",
3
+ "version": "1.19.45",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.43",
9
+ "version": "1.19.45",
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.43",
3
+ "version": "1.19.45",
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",
@@ -149,9 +149,11 @@ async function liveVersions() {
149
149
  // the two diagrams reference (authenticated, since /tasks/:id is not public) and
150
150
  // the count of seeded achievements (for 03's "N seeded" label).
151
151
  //
152
- // The task ids below are the LIVE GDS ids (verified 2026-05-30), NOT the seed
153
- // refs — the V3 renumber (migration 021) made seed-ref R##s diverge from live
154
- // ids in a couple of places. In particular the bounty-AWARDING gate is the
152
+ // The task ids below are the ORIGIN instance's live GDS ids (verified
153
+ // 2026-05-30), NOT the seed refs — the V3 renumber (migration 021) made
154
+ // seed-ref R##s diverge from live ids in a couple of places (so a source_ref
155
+ // lookup would be unreliable). On an instance that imported the origin backlog
156
+ // under new ids they resolve through the IMPORT_ID_OFFSETS probe below. In particular the bounty-AWARDING gate is the
155
157
  // "auto-confirmation flow that awards drachmae" task (#286 / live ref R67), not
156
158
  // the security-ADR-stub task that the old hand-authored .mmd loosely called
157
159
  // "R68" (#287). Each entry documents the piece it drives.
@@ -242,31 +244,56 @@ function repoSlug(repo) {
242
244
  return 'the project repo';
243
245
  }
244
246
 
247
+ // An instance that IMPORTED the origin backlog renumbers the origin tasks by a
248
+ // uniform offset (the reference import renumbered +1000000: origin 276 → live
249
+ // 1000276), so the origin ids above 404 there and every conceptual regen
250
+ // silently degraded to the null sentinel (task 1002667). Probe each id at these
251
+ // offsets, origin-first; the first offset that resolves LOCKS for the rest of
252
+ // the bundle — a uniform renumber can't mix offsets, and locking means a
253
+ // coincidental id collision on a foreign instance degrades to the sentinel
254
+ // instead of blending statuses from two different task sets.
255
+ const IMPORT_ID_OFFSETS = [0, 1_000_000];
256
+
245
257
  // Fetch live status for each conceptual task id. Authenticated (allow-unauth so
246
258
  // a 401 returns a sentinel instead of exiting the whole process via cli-lib's
247
- // guidance path). Returns null on ANY failure (no session, network, non-200) so
248
- // the generator can SKIP regenerating 03/04 rather than emit wrong data.
249
- async function conceptualFacts({ repo, migs }) {
259
+ // guidance path). Ids resolve through the IMPORT_ID_OFFSETS probe but the
260
+ // returned map stays keyed by the ORIGIN ids, so the templates, assertions.json,
261
+ // and the drift checker all keep one stable key set. Returns null on ANY failure
262
+ // (no session, network, non-200) so the generator can SKIP regenerating 03/04
263
+ // rather than emit wrong data.
264
+ async function conceptualFacts({ repo, migs, api }) {
250
265
  // Reuse the caller's migrations read when provided (gatherFacts already reads
251
266
  // them once); only fall back to a fresh read for standalone callers.
252
267
  migs = migs || readMigrations(repo);
253
- const { cliClient } = require('./cli-lib');
254
- // allow-unauth so a 401 returns a sentinel instead of exiting the whole
255
- // process via cli-lib's guidance path (see the loop's degrade-to-null below).
256
- const api = await cliClient({ allowUnauthenticated: true });
268
+ if (!api) {
269
+ const { cliClient } = require('./cli-lib');
270
+ // allow-unauth so a 401 returns a sentinel instead of exiting the whole
271
+ // process via cli-lib's guidance path (see the loop's degrade-to-null below).
272
+ api = await cliClient({ allowUnauthenticated: true });
273
+ }
257
274
  // 03's pieces + 04-architecture's memory chain + its dev-box/ship-flow pieces
258
275
  // share one fetch + one taskStatus map; any unreachable id degrades the WHOLE
259
276
  // conceptual bundle (so 03 + 04 skip together rather than emit partial data).
260
277
  const ALL_TASK_IDS = { ...CONCEPTUAL_TASK_IDS, ...DEV_BOX_TASK_IDS };
261
278
  const ids = Object.keys(ALL_TASK_IDS).map(Number);
262
279
  const status = {};
280
+ let lockedOffset = null;
263
281
  for (const id of ids) {
264
282
  let s = null;
265
- try {
266
- const r = await api.tasks.getTasksId({ id });
267
- if (r.ok) s = (r.data.task || r.data).status || null;
268
- } catch (_) {
269
- s = null;
283
+ const offsets = lockedOffset === null ? IMPORT_ID_OFFSETS : [lockedOffset];
284
+ for (const off of offsets) {
285
+ try {
286
+ const r = await api.tasks.getTasksId({ id: id + off });
287
+ if (r.ok) {
288
+ // A 200 means this id exists at this offset — accept it and stop
289
+ // probing (a missing status field still degrades below).
290
+ s = (r.data.task || r.data).status || null;
291
+ if (s !== null) lockedOffset = off;
292
+ break;
293
+ }
294
+ } catch (_) {
295
+ s = null;
296
+ }
270
297
  }
271
298
  if (s === null) {
272
299
  // Any unreachable id degrades the whole conceptual bundle — partial data
@@ -315,5 +342,6 @@ module.exports = {
315
342
  conceptualFacts,
316
343
  CONCEPTUAL_TASK_IDS,
317
344
  DEV_BOX_TASK_IDS,
345
+ IMPORT_ID_OFFSETS,
318
346
  gatherFacts,
319
347
  };
package/src/module-api.js CHANGED
@@ -513,8 +513,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
513
513
  // landed since 1.19.41 with no explicit bump. run 31515200576. (task 1002620)
514
514
  // 1.19.43 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
515
515
  // landed since 1.19.42 with no explicit bump. run 31517196176. (task 1002620)
516
+ // 1.19.44 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
517
+ // landed since 1.19.43 with no explicit bump. run 31518312009. (task 1002620)
518
+ // 1.19.45 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
519
+ // landed since 1.19.44 with no explicit bump. run 31519500533. (task 1002620)
516
520
  // ---------------------------------------------------------------------------
517
- const CORE_VERSION = '1.19.43'; // CI auto-patch carrier (ADR 0161); see changelog above
521
+ const CORE_VERSION = '1.19.45'; // CI auto-patch carrier (ADR 0161); see changelog above
518
522
 
519
523
  // A namespaced logger so a module's log lines are attributable + consistent.
520
524
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,71 @@
1
+ // tests/diagram_facts_offset.mjs — conceptualFacts import-offset probe (task 1002667).
2
+ //
3
+ // The conceptual onboarding diagrams (03/04) gate each piece on an ORIGIN
4
+ // task id. An instance that imported the origin backlog renumbers those tasks
5
+ // by a uniform offset (+1000000 on the reference import), so the ids must
6
+ // resolve through the IMPORT_ID_OFFSETS probe — keyed BACK to the origin ids —
7
+ // or every conceptual regen silently degrades to null and 03/04 never update.
8
+ //
9
+ // Run: node --test tests/diagram_facts_offset.mjs
10
+
11
+ import { strict as assert } from 'node:assert';
12
+ import { test } from 'node:test';
13
+ import { createRequire } from 'node:module';
14
+ import { fileURLToPath } from 'node:url';
15
+ import path from 'node:path';
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const REPO = path.join(__dirname, '..');
20
+ const facts = require(path.join(REPO, 'scripts/gds/diagram-facts.js'));
21
+
22
+ const ORIGIN_IDS = Object.keys({
23
+ ...facts.CONCEPTUAL_TASK_IDS,
24
+ ...facts.DEV_BOX_TASK_IDS,
25
+ }).map(Number);
26
+
27
+ // Mock API: resolves any id present in `rows` ({id → status}); 404s the rest.
28
+ const mockApi = (rows) => ({
29
+ tasks: {
30
+ getTasksId: async ({ id }) => {
31
+ if (id in rows) return { ok: true, data: { task: { id: String(id), status: rows[id] } } };
32
+ return { ok: false, status: 404, data: { error: 'not found' } };
33
+ },
34
+ },
35
+ });
36
+
37
+ test('origin instance: ids resolve directly, map keyed by origin ids', async () => {
38
+ const rows = Object.fromEntries(ORIGIN_IDS.map((id) => [id, 'shipped']));
39
+ const c = await facts.conceptualFacts({ repo: REPO, migs: [], api: mockApi(rows) });
40
+ assert.ok(c, 'bundle resolved, not degraded');
41
+ for (const id of ORIGIN_IDS) assert.equal(c.taskStatus[id], 'shipped');
42
+ });
43
+
44
+ test('imported instance (+1000000): ids resolve at the offset, map still keyed by origin ids', async () => {
45
+ const rows = Object.fromEntries(ORIGIN_IDS.map((id) => [id + 1_000_000, 'shipped']));
46
+ const c = await facts.conceptualFacts({ repo: REPO, migs: [], api: mockApi(rows) });
47
+ assert.ok(c, 'bundle resolved through the offset probe');
48
+ for (const id of ORIGIN_IDS) {
49
+ assert.equal(c.taskStatus[id], 'shipped', `origin id ${id} keyed in the returned map`);
50
+ assert.ok(!(id + 1_000_000 in c.taskStatus), 'no offset-id keys leak into the map');
51
+ }
52
+ });
53
+
54
+ test('offset locks on first hit: a coincidental origin-id row cannot blend task sets', async () => {
55
+ // Everything lives at +1000000, but one LATER id also exists bare with a
56
+ // conflicting status. Once the first id locks the offset, the bare row must
57
+ // be ignored — the +1000000 row's status wins.
58
+ const rows = Object.fromEntries(ORIGIN_IDS.map((id) => [id + 1_000_000, 'shipped']));
59
+ const later = ORIGIN_IDS[ORIGIN_IDS.length - 1];
60
+ rows[later] = 'backlog';
61
+ const c = await facts.conceptualFacts({ repo: REPO, migs: [], api: mockApi(rows) });
62
+ assert.ok(c, 'bundle resolved');
63
+ assert.equal(c.taskStatus[later], 'shipped', 'locked offset wins over the coincidental bare id');
64
+ });
65
+
66
+ test('unresolvable id degrades the whole bundle to null (unchanged sentinel contract)', async () => {
67
+ const rows = Object.fromEntries(ORIGIN_IDS.map((id) => [id + 1_000_000, 'shipped']));
68
+ delete rows[ORIGIN_IDS[0] + 1_000_000];
69
+ const c = await facts.conceptualFacts({ repo: REPO, migs: [], api: mockApi(rows) });
70
+ assert.equal(c, null);
71
+ });