@bongos/core 1.24.70 → 1.24.71
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 +12 -12
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/bongos/provision-repo.js +117 -6
- package/src/module-api.js +1 -1
- package/tests/provision.mjs +132 -7
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.
|
|
6
|
-
"core_contract": "1.24.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.24.71",
|
|
6
|
+
"core_contract": "1.24.71",
|
|
7
|
+
"source_commit": "d91fd6264c096355cc456dca5bec432e8ef8bfab",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-11-09T21:
|
|
9
|
+
"built_at": "2026-11-09T21:26:26.243Z",
|
|
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": 4579,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "83d2c54f02336ac8a30a6bf1daed4f6f6eee25a1a3e97bebe530dea0e9c9e1d9",
|
|
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": "
|
|
3010
|
+
"sha256": "f442b5440a792bc95a16cd1e98dbf7f0b23443af30c1a5b2a97f1f417a57987e"
|
|
3011
3011
|
},
|
|
3012
3012
|
{
|
|
3013
3013
|
"path": "docs/modules-contract.md",
|
|
@@ -11902,12 +11902,12 @@
|
|
|
11902
11902
|
{
|
|
11903
11903
|
"path": "package-lock.json",
|
|
11904
11904
|
"mode": "0000644",
|
|
11905
|
-
"sha256": "
|
|
11905
|
+
"sha256": "1b83865826ab09ecc99321a94f17207dfab2b742b5631514d1f64a3cacc4bc83"
|
|
11906
11906
|
},
|
|
11907
11907
|
{
|
|
11908
11908
|
"path": "package.json",
|
|
11909
11909
|
"mode": "0000644",
|
|
11910
|
-
"sha256": "
|
|
11910
|
+
"sha256": "e6b806ae5ae808a230ee9a5959cec2ad5f60754e4495c20a867527568512b560"
|
|
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": "
|
|
11930
|
+
"sha256": "cd1552111a94f01b3cb28fb236a0e27db8a838f23e6304483d96705df51a199c"
|
|
11931
11931
|
},
|
|
11932
11932
|
{
|
|
11933
11933
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -13012,7 +13012,7 @@
|
|
|
13012
13012
|
{
|
|
13013
13013
|
"path": "scripts/bongos/provision-repo.js",
|
|
13014
13014
|
"mode": "0000644",
|
|
13015
|
-
"sha256": "
|
|
13015
|
+
"sha256": "acf44664b36c0c250af83bea5091a12cee503adfa0b8eb9d3460fb55b94c36f1"
|
|
13016
13016
|
},
|
|
13017
13017
|
{
|
|
13018
13018
|
"path": "scripts/bongos/provision-teardown.js",
|
|
@@ -15882,7 +15882,7 @@
|
|
|
15882
15882
|
{
|
|
15883
15883
|
"path": "src/module-api.js",
|
|
15884
15884
|
"mode": "0000644",
|
|
15885
|
-
"sha256": "
|
|
15885
|
+
"sha256": "ae5cf112e92bb2c07eebca2852cee61af771982450ae1bb70cb615d32c78c605"
|
|
15886
15886
|
},
|
|
15887
15887
|
{
|
|
15888
15888
|
"path": "src/module-loader/catalog.js",
|
|
@@ -20992,7 +20992,7 @@
|
|
|
20992
20992
|
{
|
|
20993
20993
|
"path": "tests/provision.mjs",
|
|
20994
20994
|
"mode": "0000644",
|
|
20995
|
-
"sha256": "
|
|
20995
|
+
"sha256": "fe8f7fbc739ae6dc0ea5b0475ca1416a3adca6f222d39f4cf0e35c02121b313d"
|
|
20996
20996
|
},
|
|
20997
20997
|
{
|
|
20998
20998
|
"path": "tests/provision_account_identity.mjs",
|
|
@@ -3392,5 +3392,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3392
3392
|
landed since 1.24.67 with no explicit bump. run 42692736161. (task 1002620)
|
|
3393
3393
|
1.24.70 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3394
3394
|
landed since 1.24.68 with no explicit bump. run 42699948615. (task 1002620)
|
|
3395
|
+
1.24.71 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3396
|
+
landed since 1.24.70 with no explicit bump. run 42702183401. (task 1002620)
|
|
3395
3397
|
---------------------------------------------------------------------------
|
|
3396
3398
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.71",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.71",
|
|
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.
|
|
3
|
+
"version": "1.24.71",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -11445,5 +11445,11 @@
|
|
|
11445
11445
|
"id": "1004949",
|
|
11446
11446
|
"text": "When a shell command was blocked by one hook and another hook was missing, the warning about the missing hook vanished. Now both messages show."
|
|
11447
11447
|
}
|
|
11448
|
+
],
|
|
11449
|
+
"1.24.71": [
|
|
11450
|
+
{
|
|
11451
|
+
"id": "1002450",
|
|
11452
|
+
"text": "New projects can no longer be born on an outdated or mislabelled copy of the core. When the hub's saved core file is stale or doesn't match the published one, provisioning now says so loudly and uses the current, published cor"
|
|
11453
|
+
}
|
|
11448
11454
|
]
|
|
11449
11455
|
}
|
|
@@ -533,7 +533,9 @@ function installedCoreTarball({ instanceRoot, fsImpl = fs } = {}) {
|
|
|
533
533
|
// nothing either way. The file: pin is kept for the reason in ADR 0168 — a child is born
|
|
534
534
|
// on the core its parent actually runs — not for access control.) Returns { dir, version, tarballName } or null
|
|
535
535
|
// (a file: pin, an uninstalled dep, or a source checkout). fs reads only → dry-run safe.
|
|
536
|
-
|
|
536
|
+
// `includeFilePins` (task 1002450): the stale-pin recovery below packs the core a
|
|
537
|
+
// file:-pinned hub has INSTALLED, so it must not skip the file: spec the default does.
|
|
538
|
+
function installedCorePackDir({ instanceRoot, fsImpl = fs, includeFilePins = false } = {}) {
|
|
537
539
|
const root = instanceRoot || process.env.BONGOS_INSTANCE_ROOT || process.cwd();
|
|
538
540
|
let pkg;
|
|
539
541
|
try { pkg = JSON.parse(fsImpl.readFileSync(path.join(root, 'package.json'), 'utf8')); }
|
|
@@ -541,7 +543,7 @@ function installedCorePackDir({ instanceRoot, fsImpl = fs } = {}) {
|
|
|
541
543
|
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
542
544
|
for (const name of ['@bongos/core', '@cloudbongos/core']) {
|
|
543
545
|
const spec = String(deps[name] || '').trim();
|
|
544
|
-
if (!spec || spec.startsWith('file:')) continue; // file: pins belong to installedCoreTarball
|
|
546
|
+
if (!spec || (spec.startsWith('file:') && !includeFilePins)) continue; // file: pins belong to installedCoreTarball
|
|
545
547
|
const dir = path.join(root, 'node_modules', ...name.split('/'));
|
|
546
548
|
try {
|
|
547
549
|
const installed = JSON.parse(fsImpl.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
@@ -568,13 +570,113 @@ function coreCheckoutHasGit({ repoRoot = REPO_ROOT, fsImpl = fs } = {}) {
|
|
|
568
570
|
// The core version THIS control plane runs — what the ladder below would vend: the
|
|
569
571
|
// pinned tarball's version, else the registry-installed package's, else the checkout's
|
|
570
572
|
// CORE_VERSION. fs reads only → dry-run safe.
|
|
573
|
+
// A pinned tarball whose label disagrees with coreV is STALE (task 1002450): the hall runs
|
|
574
|
+
// coreV, so that is its running version, not the leftover tarball's label.
|
|
571
575
|
function runningCoreVersion({ instanceRoot, coreV }) {
|
|
572
576
|
const pinned = installedCoreTarball({ instanceRoot });
|
|
573
|
-
if (pinned)
|
|
577
|
+
if (pinned) {
|
|
578
|
+
const pinnedV = pinnedTarballVersion(pinned);
|
|
579
|
+
return (coreV && pinnedV && pinnedV !== coreV) ? coreV : (pinnedV || coreV);
|
|
580
|
+
}
|
|
574
581
|
const packSrc = installedCorePackDir({ instanceRoot });
|
|
575
582
|
return packSrc ? packSrc.version : coreV;
|
|
576
583
|
}
|
|
577
584
|
|
|
585
|
+
// ── the stale-pin guard (task 1002450) ────────────────────────────────────────────
|
|
586
|
+
// Rung 1 below trusted a file:-pinned hub's own vendored tarball unconditionally ("the
|
|
587
|
+
// child pins the SAME core the hub runs"). Two ways that was false, both seen live: an
|
|
588
|
+
// out-of-band upgrade swaps node_modules/@bongos/core without rewriting vendor/*.tgz, so
|
|
589
|
+
// the label trails the core the hub RUNS; and the same label can name different bytes —
|
|
590
|
+
// an early instance was born on a 1.18.14 tarball 15 kB smaller than the published 1.18.14,
|
|
591
|
+
// missing the task-1002419 poller fix, so its first ship never left 'confirmed'. Quiet
|
|
592
|
+
// failure, so this guard is loud.
|
|
593
|
+
//
|
|
594
|
+
// The two sibling gaps are closed elsewhere, on purpose — tests/provision.mjs proves each:
|
|
595
|
+
// - PUBLISHING one version twice as different bytes: the npm registry refuses it (a
|
|
596
|
+
// version is immutable), and .github/workflows/publish.yml fails the run loudly on
|
|
597
|
+
// that refusal (task 1004886). What escaped was a LOCAL build carrying a published
|
|
598
|
+
// label — the vendoring this guard checks.
|
|
599
|
+
// - a hub BEHIND upstream: projectCoreDecision gives a new project the release when
|
|
600
|
+
// the runner is behind it (task 1004556), and that target short-circuits this ladder.
|
|
601
|
+
// The integrity read costs one `npm view` on the reuse path; projectCoreTarget already
|
|
602
|
+
// makes one per scaffold, and both fail open when the registry is unreachable.
|
|
603
|
+
|
|
604
|
+
// The version a core tarball's FILENAME carries — both scope aliases (bongos-core-*,
|
|
605
|
+
// cloudbongos-core-*), which upgrade.versionFromTgzPath (bongos-core only) would miss.
|
|
606
|
+
// Digits, dots and [A-Za-z0-9.+-] only, so the result is shell-safe. PURE.
|
|
607
|
+
function pinnedTarballVersion(p) {
|
|
608
|
+
const m = /(?:^|-)core-(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)\.tgz$/.exec(path.basename(String(p || '')));
|
|
609
|
+
return m ? m[1] : null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// A version that may enter a shell command, or null — the same character set as
|
|
613
|
+
// safeVersionLabel, the file's interpolation chokepoint. PURE.
|
|
614
|
+
function shellSafeVersion(v) {
|
|
615
|
+
const s = safeVersionLabel(v);
|
|
616
|
+
return s === '(unversioned)' ? null : s;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// npm's dist.integrity form for a file: sha512, base64. fs injectable for tests.
|
|
620
|
+
function tarballIntegrity(p, fsImpl = fs) {
|
|
621
|
+
return 'sha512-' + crypto.createHash('sha512').update(fsImpl.readFileSync(p)).digest('base64');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Is the pinned tarball the core this hub runs, AND the bytes the registry published
|
|
625
|
+
// under that label? → { verdict, pinnedV, reason }:
|
|
626
|
+
// 'stale-version' — its label is not coreV (the hub moved on, the tarball did not);
|
|
627
|
+
// 'stale-bytes' — the label matches but the registry's integrity for it differs
|
|
628
|
+
// (the charter case: same version, different artifact);
|
|
629
|
+
// 'unverifiable' — no coreV, an unparseable label, or no registry answer: reuse it
|
|
630
|
+
// (fail-open, never block a scaffold on a resolution gap) but SAY so;
|
|
631
|
+
// 'fresh' — both checks pass.
|
|
632
|
+
// The registry read goes through the injected exec, so a dry run never hits the network.
|
|
633
|
+
function checkPinnedTarball({ pinned, coreV, exec, fsImpl = fs }) {
|
|
634
|
+
const pinnedV = shellSafeVersion(pinnedTarballVersion(pinned));
|
|
635
|
+
if (!pinnedV) return { verdict: 'unverifiable', pinnedV, reason: 'no version in its filename' };
|
|
636
|
+
if (!coreV) return { verdict: 'unverifiable', pinnedV, reason: 'the running core version is unknown' };
|
|
637
|
+
if (pinnedV !== coreV) return { verdict: 'stale-version', pinnedV, reason: `it is ${pinnedV} but this control plane runs ${coreV}` };
|
|
638
|
+
const res = exec(`npm view ${CORE_PKG}@${pinnedV} dist.integrity`, { allowFail: true });
|
|
639
|
+
const published = String((res && res.ok !== false && res.stdout) || '').trim();
|
|
640
|
+
if (!/^sha512-/.test(published)) return { verdict: 'unverifiable', pinnedV, reason: `the registry gave no integrity for ${pinnedV}` };
|
|
641
|
+
let local;
|
|
642
|
+
try { local = tarballIntegrity(pinned, fsImpl); } catch { return { verdict: 'unverifiable', pinnedV, reason: 'it could not be read' }; }
|
|
643
|
+
if (local !== published) return { verdict: 'stale-bytes', pinnedV, reason: `its bytes are not the ${pinnedV} the registry published (a different build under the same version label)` };
|
|
644
|
+
return { verdict: 'fresh', pinnedV, reason: 'matches the running core and the published bytes' };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Where a stale pin goes instead. NEVER to the package-core.js rungs: on a file:-pinned
|
|
648
|
+
// consumer hub those abort on an empty core set and leave the new repo empty (task 1002275).
|
|
649
|
+
// stale-bytes → the registry's own bytes for that version (canonical by definition);
|
|
650
|
+
// stale-version → the core the hub has INSTALLED, when it is coreV; else the registry's coreV;
|
|
651
|
+
// neither → refuse, loudly: no project is born on a core nobody can name.
|
|
652
|
+
function recoverFromStalePin({ instanceRoot, exec, log, tag, coreV, check }) {
|
|
653
|
+
const fromRegistry = (raw) => {
|
|
654
|
+
const v = shellSafeVersion(raw); // interpolated into a shell command below
|
|
655
|
+
if (!v) return null;
|
|
656
|
+
const packDest = path.join(os.tmpdir(), `bongos-core-registry-${v}`);
|
|
657
|
+
exec(`mkdir -p ${packDest}`);
|
|
658
|
+
const res = exec(`npm pack ${CORE_PKG}@${v} --pack-destination ${packDest}`, { allowFail: true });
|
|
659
|
+
if (res && res.ok === false) { exec(`rm -rf ${packDest}`); return null; }
|
|
660
|
+
log(` ${tag} packed the published ${v} from the registry instead (task 1002450)`);
|
|
661
|
+
return { tgz: path.join(packDest, `bongos-core-${v}.tgz`), packDest };
|
|
662
|
+
};
|
|
663
|
+
if (check.verdict === 'stale-version') {
|
|
664
|
+
const installed = installedCorePackDir({ instanceRoot, includeFilePins: true });
|
|
665
|
+
const v = installed && shellSafeVersion(installed.version); // interpolated into a shell command below
|
|
666
|
+
if (v && v === coreV) {
|
|
667
|
+
const packDest = path.join(os.tmpdir(), `bongos-core-pack-${v}`);
|
|
668
|
+
log(` ${tag} packing the installed core this control plane runs (v${v}) instead (task 1002450)`);
|
|
669
|
+
exec(`mkdir -p ${packDest}`);
|
|
670
|
+
exec(`npm pack ${installed.dir} --pack-destination ${packDest}`);
|
|
671
|
+
return { tgz: path.join(packDest, installed.tarballName), packDest };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const v = check.verdict === 'stale-bytes' ? check.pinnedV : coreV;
|
|
675
|
+
const got = fromRegistry(v);
|
|
676
|
+
if (got) return got;
|
|
677
|
+
throw new Error(`refusing to give a new project a stale core: the pinned tarball ${check.reason}, and neither an installed core ${coreV} nor a published ${v} could be packed. Re-run \`bongos upgrade\` on this control plane so its vendor/ tarball matches the core it runs, then retry (task 1002450).`);
|
|
678
|
+
}
|
|
679
|
+
|
|
578
680
|
// Which core a hosted project is given (task 1004299, ADR 0361 D5). PURE. Since merges
|
|
579
681
|
// publish as candidates, the platform hall runs ahead of the registry's `latest` label by
|
|
580
682
|
// design — and the ladder below vends whatever the hall runs, so every new project would
|
|
@@ -617,7 +719,8 @@ function projectCoreTarget({ instanceRoot, coreV, exec, log, tag = '[core]' }) {
|
|
|
617
719
|
// project gets the released version packed from the registry, not this hall's core.
|
|
618
720
|
// 1. a `file:`-pinned control plane reuses its own pinned tarball (task 1002275 —
|
|
619
721
|
// rebuilding via package-core.js aborts with an EMPTY core set when this runner
|
|
620
|
-
// IS a consumer instance, leaving new repos empty)
|
|
722
|
+
// IS a consumer instance, leaving new repos empty) — unless checkPinnedTarball
|
|
723
|
+
// finds it STALE, when recoverFromStalePin replaces it (task 1002450);
|
|
621
724
|
// 2. a REGISTRY-pinned consumer (the post-1002621 mothership shape) packs the core
|
|
622
725
|
// it has INSTALLED — the child pins the SAME, CURRENT core the hub runs, ending
|
|
623
726
|
// the frozen-old-tarball births (task 1002706, gap 13) without requiring the
|
|
@@ -641,8 +744,14 @@ function resolveVendorableCoreTarball({ instanceRoot, repoRoot = REPO_ROOT, exec
|
|
|
641
744
|
}
|
|
642
745
|
const pinned = installedCoreTarball({ instanceRoot });
|
|
643
746
|
if (pinned) {
|
|
644
|
-
|
|
645
|
-
|
|
747
|
+
const check = checkPinnedTarball({ pinned, coreV, exec });
|
|
748
|
+
if (check.verdict === 'fresh' || check.verdict === 'unverifiable') {
|
|
749
|
+
if (check.verdict === 'unverifiable') log(` ${tag} WARNING: cannot verify the pinned core tarball ${path.basename(pinned)} — ${check.reason}; reusing it anyway (task 1002450)`);
|
|
750
|
+
log(` ${tag} reusing the pinned core tarball this control plane runs (${path.basename(pinned)}) — no source rebuild (task 1002275)`);
|
|
751
|
+
return { tgz: pinned, packDest: null };
|
|
752
|
+
}
|
|
753
|
+
log(` ${tag} STALE pinned core tarball ${path.basename(pinned)} — ${check.reason}. NOT vendoring it into a new project (task 1002450)`);
|
|
754
|
+
return recoverFromStalePin({ instanceRoot, exec, log, tag, coreV, check });
|
|
646
755
|
}
|
|
647
756
|
const packSrc = installedCorePackDir({ instanceRoot });
|
|
648
757
|
if (packSrc) {
|
|
@@ -1294,6 +1403,7 @@ module.exports = {
|
|
|
1294
1403
|
ownerLoginOf,
|
|
1295
1404
|
asPostgres,
|
|
1296
1405
|
buildCorePinRefreshCommit,
|
|
1406
|
+
checkPinnedTarball,
|
|
1297
1407
|
coreCheckoutHasGit,
|
|
1298
1408
|
dbCreateCmd,
|
|
1299
1409
|
dbName,
|
|
@@ -1318,6 +1428,7 @@ module.exports = {
|
|
|
1318
1428
|
migrateCmd,
|
|
1319
1429
|
onboardMode,
|
|
1320
1430
|
parseTargetRef,
|
|
1431
|
+
pinnedTarballVersion,
|
|
1321
1432
|
planCorePinRefresh,
|
|
1322
1433
|
refreshStandaloneCorePin,
|
|
1323
1434
|
resolveOwnerGithubToken,
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.71'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/provision.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { strict as assert } from 'node:assert';
|
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
17
18
|
import { join } from 'node:path';
|
|
18
19
|
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
@@ -905,12 +906,15 @@ t('installedCoreTarball: reuses a consumer instance file: core pin; null on a so
|
|
|
905
906
|
});
|
|
906
907
|
|
|
907
908
|
await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinned tarball — no package-core rebuild (task 1002275)', async () => {
|
|
908
|
-
// the control plane is itself a consumer instance: its package.json file:-pins the core
|
|
909
|
+
// the control plane is itself a consumer instance: its package.json file:-pins the core.
|
|
910
|
+
// Pinned to the REAL running CORE_VERSION — a fixed old literal would now be STALE
|
|
911
|
+
// (task 1002450) and exercise the recovery instead of the reuse this test is named for.
|
|
912
|
+
const coreV = P.coreVersionSafe();
|
|
909
913
|
const cp = mkdtempSync(join(tmpdir(), 'cptgz-cp-'));
|
|
910
914
|
mkdirSync(join(cp, 'vendor'));
|
|
911
|
-
const pinned = join(cp, 'vendor',
|
|
915
|
+
const pinned = join(cp, 'vendor', `bongos-core-${coreV}.tgz`);
|
|
912
916
|
writeFileSync(pinned, 'x');
|
|
913
|
-
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core':
|
|
917
|
+
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': `file:vendor/bongos-core-${coreV}.tgz` } }));
|
|
914
918
|
const base = mkdtempSync(join(tmpdir(), 'cptgz-standalone-')); // FRESH base → the scaffold proceeds
|
|
915
919
|
const origBase = P.CONFIG.standaloneBase;
|
|
916
920
|
P.CONFIG.standaloneBase = base;
|
|
@@ -931,6 +935,127 @@ await ta('scaffoldStandaloneRepo: on a consumer control plane it REUSES the pinn
|
|
|
931
935
|
}
|
|
932
936
|
});
|
|
933
937
|
|
|
938
|
+
// ── the stale-pin guard (task 1002450) ───────────────────────────────────────────────
|
|
939
|
+
// Rung 1 reused a file:-pinned hub's own tarball with no check it was still the core the
|
|
940
|
+
// hub runs — an early instance was born on a 1.18.14 tarball that was NOT the published
|
|
941
|
+
// 1.18.14 (missing the task-1002419 poller fix) and its first ship never left 'confirmed'.
|
|
942
|
+
// A stale pin must be replaced, loudly, and never by the package-core.js rungs (which
|
|
943
|
+
// abort on a consumer hub and leave the new repo empty — task 1002275).
|
|
944
|
+
const sha512 = (txt) => 'sha512-' + createHash('sha512').update(txt).digest('base64');
|
|
945
|
+
function filePinnedHub(label, bytes = 'x', { installed = null, scope = 'bongos' } = {}) {
|
|
946
|
+
const cp = mkdtempSync(join(tmpdir(), 'stalepin-'));
|
|
947
|
+
mkdirSync(join(cp, 'vendor'));
|
|
948
|
+
const file = `${scope}-core-${label}.tgz`;
|
|
949
|
+
writeFileSync(join(cp, 'vendor', file), bytes);
|
|
950
|
+
writeFileSync(join(cp, 'package.json'), JSON.stringify({ dependencies: { [`@${scope}/core`]: `file:vendor/${file}` } }));
|
|
951
|
+
if (installed) {
|
|
952
|
+
mkdirSync(join(cp, 'node_modules', '@bongos', 'core'), { recursive: true });
|
|
953
|
+
writeFileSync(join(cp, 'node_modules', '@bongos', 'core', 'package.json'), JSON.stringify({ name: '@bongos/core', version: installed }));
|
|
954
|
+
}
|
|
955
|
+
return { cp, pinned: join(cp, 'vendor', file) };
|
|
956
|
+
}
|
|
957
|
+
function resolveWith(cp, coreV, respond = () => ({ ok: true, stdout: '' })) {
|
|
958
|
+
const cmds = [], logs = [];
|
|
959
|
+
let res, err;
|
|
960
|
+
try {
|
|
961
|
+
res = P.resolveVendorableCoreTarball({ instanceRoot: cp, exec: (c, o) => { cmds.push(c); return respond(c, o); }, log: (m) => logs.push(m), coreV });
|
|
962
|
+
} catch (e) { err = e; }
|
|
963
|
+
return { ...res, cmds, logs, err };
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
t('pinnedTarballVersion: reads both scope aliases; null when the name carries no version', () => {
|
|
967
|
+
assert.equal(PR.pinnedTarballVersion('/x/vendor/bongos-core-1.18.14.tgz'), '1.18.14');
|
|
968
|
+
assert.equal(PR.pinnedTarballVersion('cloudbongos-core-2.0.0.tgz'), '2.0.0', 'the @cloudbongos alias the ARTIFACT regex in upgrade.js misses');
|
|
969
|
+
assert.equal(PR.pinnedTarballVersion('vendor/core.tgz'), null);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
t('stale label on a file:-pinned hub → packs the INSTALLED core it runs, loudly, never package-core.js (task 1002450)', () => {
|
|
973
|
+
const { cp, pinned } = filePinnedHub('1.18.14', 'x', { installed: '1.18.17' });
|
|
974
|
+
const r = resolveWith(cp, '1.18.17');
|
|
975
|
+
assert.ok(!r.err, String(r.err));
|
|
976
|
+
assert.ok(r.logs.some((l) => /STALE/.test(l) && /1\.18\.14/.test(l) && /1\.18\.17/.test(l)), `a loud warning naming both versions, got: ${r.logs.join(' | ')}`);
|
|
977
|
+
assert.notEqual(r.tgz, pinned, 'never vendors the stale tarball');
|
|
978
|
+
assert.ok(r.cmds.some((c) => c.startsWith('npm pack ') && c.includes(join(cp, 'node_modules', '@bongos', 'core'))), 'packs node_modules/@bongos/core despite the file: pin');
|
|
979
|
+
assert.equal(r.tgz, join(r.packDest, 'bongos-core-1.18.17.tgz'));
|
|
980
|
+
assert.ok(!r.cmds.some((c) => /package-core\.js/.test(c)), 'the empty-set abort path is never reached');
|
|
981
|
+
assert.equal(PR.runningCoreVersion({ instanceRoot: cp, coreV: '1.18.17' }), '1.18.17', 'the hall runs coreV, not the leftover label');
|
|
982
|
+
rmSync(cp, { recursive: true, force: true });
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
t('stale label and no installed match → the registry\'s coreV; no registry either → refuses loudly, never an empty repo (task 1002450)', () => {
|
|
986
|
+
const { cp } = filePinnedHub('1.18.14');
|
|
987
|
+
const ok = resolveWith(cp, '1.18.17');
|
|
988
|
+
assert.ok(ok.cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.17 ')), `packs the published coreV, got: ${ok.cmds.join(' | ')}`);
|
|
989
|
+
assert.equal(ok.tgz, join(ok.packDest, 'bongos-core-1.18.17.tgz'));
|
|
990
|
+
const down = resolveWith(cp, '1.18.17', (c) => (c.startsWith('npm pack ') ? { ok: false, stdout: '' } : { ok: true, stdout: '' }));
|
|
991
|
+
assert.ok(down.err && /refusing to give a new project a stale core/.test(down.err.message), 'a scaffold with nothing current to vendor is refused');
|
|
992
|
+
assert.ok(!down.cmds.some((c) => /package-core\.js/.test(c)));
|
|
993
|
+
rmSync(cp, { recursive: true, force: true });
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
t('same label, different bytes (the charter case) → the registry\'s own bytes for that version; matching bytes → reuse (task 1002450)', () => {
|
|
997
|
+
const { cp, pinned } = filePinnedHub('1.18.14', 'local-build');
|
|
998
|
+
const view = (published) => (c) => (c.startsWith('npm view @bongos/core@1.18.14 dist.integrity') ? { ok: true, stdout: published + '\n' } : { ok: true, stdout: '' });
|
|
999
|
+
const bad = resolveWith(cp, '1.18.14', view(sha512('published-build')));
|
|
1000
|
+
assert.ok(bad.logs.some((l) => /STALE/.test(l) && /same version label/.test(l)), `loud, got: ${bad.logs.join(' | ')}`);
|
|
1001
|
+
assert.ok(bad.cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.14 ')), 'repacks the canonical published artifact');
|
|
1002
|
+
assert.notEqual(bad.tgz, pinned);
|
|
1003
|
+
const good = resolveWith(cp, '1.18.14', view(sha512('local-build')));
|
|
1004
|
+
assert.equal(good.tgz, pinned, 'identical bytes → reuse');
|
|
1005
|
+
assert.equal(good.packDest, null);
|
|
1006
|
+
assert.ok(!good.logs.some((l) => /STALE|WARNING/.test(l)));
|
|
1007
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
t('unverifiable pin (no coreV, no registry answer) → reused, but SAID out loud; the cloudbongos alias is checked too (task 1002450)', () => {
|
|
1011
|
+
const { cp, pinned } = filePinnedHub('1.18.14');
|
|
1012
|
+
const noCore = resolveWith(cp, null);
|
|
1013
|
+
assert.equal(noCore.tgz, pinned, 'fail-open: a resolution gap never blocks a scaffold');
|
|
1014
|
+
assert.ok(noCore.logs.some((l) => /WARNING: cannot verify/.test(l)), 'but it is not silent');
|
|
1015
|
+
const offline = resolveWith(cp, '1.18.14');
|
|
1016
|
+
assert.equal(offline.tgz, pinned);
|
|
1017
|
+
assert.ok(offline.logs.some((l) => /WARNING: cannot verify/.test(l) && /no integrity/.test(l)));
|
|
1018
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1019
|
+
const alt = filePinnedHub('1.0.0', 'x', { scope: 'cloudbongos' });
|
|
1020
|
+
const r = resolveWith(alt.cp, '1.0.1');
|
|
1021
|
+
assert.ok(r.logs.some((l) => /STALE/.test(l)), 'a cloudbongos-core-* pin is not exempt from the label check');
|
|
1022
|
+
assert.notEqual(r.tgz, alt.pinned);
|
|
1023
|
+
rmSync(alt.cp, { recursive: true, force: true });
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
t('a stale pin\'s recovery never puts an unsafe version into a shell command (task 1002450)', () => {
|
|
1027
|
+
const { cp } = filePinnedHub('1.18.14');
|
|
1028
|
+
const r = resolveWith(cp, '1.18.17;touch pwn');
|
|
1029
|
+
assert.ok(!r.cmds.some((c) => /pwn/.test(c)), `no command carries the label, got: ${r.cmds.join(' | ')}`);
|
|
1030
|
+
assert.ok(r.err && /refusing to give a new project a stale core/.test(r.err.message), 'nothing safe to pack → refused');
|
|
1031
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1032
|
+
// the INSTALLED core's version (read off node_modules) is held to the same rule
|
|
1033
|
+
const inst = filePinnedHub('1.18.14', 'x', { installed: '1.18.17;touch pwn' });
|
|
1034
|
+
const r2 = resolveWith(inst.cp, '1.18.17;touch pwn');
|
|
1035
|
+
assert.ok(!r2.cmds.some((c) => /pwn/.test(c)), `no command carries the installed label, got: ${r2.cmds.join(' | ')}`);
|
|
1036
|
+
assert.ok(r2.err, 'refused');
|
|
1037
|
+
rmSync(inst.cp, { recursive: true, force: true });
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
t('a file:-pinned hub BEHIND the release gives the new project the release, not its own pin (hub-vs-upstream drift, tasks 1004556 + 1002450)', () => {
|
|
1041
|
+
const { cp, pinned } = filePinnedHub('1.18.14');
|
|
1042
|
+
const cmds = [];
|
|
1043
|
+
const exec = (c) => { cmds.push(c); return { ok: true, stdout: /dist-tags\.latest/.test(c) ? '1.18.17\n' : '' }; };
|
|
1044
|
+
const target = PR.projectCoreTarget({ instanceRoot: cp, coreV: '1.18.14', exec, log: () => {} });
|
|
1045
|
+
assert.deepEqual([target.version, target.source], ['1.18.17', 'released']);
|
|
1046
|
+
const r = P.resolveVendorableCoreTarball({ instanceRoot: cp, exec, log: () => {}, coreV: '1.18.14', target });
|
|
1047
|
+
assert.notEqual(r.tgz, pinned, 'the hub\'s own (older) pin is not vendored');
|
|
1048
|
+
assert.ok(cmds.some((c) => c.startsWith('npm pack @bongos/core@1.18.17 ')), 'the release is packed from the registry');
|
|
1049
|
+
rmSync(cp, { recursive: true, force: true });
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
t('publishing one version twice as different bytes is refused loudly by the publish workflow (the publish half of the build-identity guard, task 1002450)', () => {
|
|
1053
|
+
const wf = readFileSync(new URL('../.github/workflows/publish.yml', import.meta.url), 'utf8');
|
|
1054
|
+
assert.match(wf, /npm publish "\$TGZ"/, 'the workflow publishes to the npm registry, where a version is immutable');
|
|
1055
|
+
assert.match(wf, /cannot publish over\|previously published\|EPUBLISHCONFLICT/, 'it recognises the registry\'s refusal');
|
|
1056
|
+
assert.match(wf, /::error title=version already on the registry/, 'and fails the run loudly on it');
|
|
1057
|
+
});
|
|
1058
|
+
|
|
934
1059
|
// ── registry-pinned consumer control plane (task 1002706) ─────────────────────────
|
|
935
1060
|
// After the mothership's registry cutover (task 1002621) its package.json pins
|
|
936
1061
|
// "@bongos/core": "1.19.x" — NO file: pin — so installedCoreTarball returns null and,
|
|
@@ -1001,10 +1126,10 @@ t('resolveVendorableCoreTarball: the ladder picks file: pin → registry pack
|
|
|
1001
1126
|
// One helper per rung so the ONLY thing varying between cases is which shape the
|
|
1002
1127
|
// control-plane root presents. Asserting on the recorded commands is what proves the
|
|
1003
1128
|
// ORDER: a rung that fires early leaves the later rung's command unrun.
|
|
1004
|
-
const run = (instanceRoot, repoRoot) => {
|
|
1129
|
+
const run = (instanceRoot, repoRoot, coreV = '9.9.9') => {
|
|
1005
1130
|
const cmds = [];
|
|
1006
1131
|
const res = P.resolveVendorableCoreTarball({
|
|
1007
|
-
instanceRoot, repoRoot, exec: (c) => { cmds.push(c); }, log: () => {}, coreV
|
|
1132
|
+
instanceRoot, repoRoot, exec: (c) => { cmds.push(c); }, log: () => {}, coreV,
|
|
1008
1133
|
});
|
|
1009
1134
|
return { cmds, ...res };
|
|
1010
1135
|
};
|
|
@@ -1017,9 +1142,9 @@ t('resolveVendorableCoreTarball: the ladder picks file: pin → registry pack
|
|
|
1017
1142
|
mkdirSync(join(filePinned, 'vendor'));
|
|
1018
1143
|
writeFileSync(join(filePinned, 'vendor', 'bongos-core-1.2.3.tgz'), 'x');
|
|
1019
1144
|
writeFileSync(join(filePinned, 'package.json'), JSON.stringify({ dependencies: { '@bongos/core': 'file:vendor/bongos-core-1.2.3.tgz' } }));
|
|
1020
|
-
const r1 = run(filePinned, bareRoot);
|
|
1145
|
+
const r1 = run(filePinned, bareRoot, '1.2.3');
|
|
1021
1146
|
assert.equal(r1.tgz, join(filePinned, 'vendor', 'bongos-core-1.2.3.tgz'));
|
|
1022
|
-
assert.
|
|
1147
|
+
assert.ok(r1.cmds.every((c) => c.startsWith('npm view ')), 'rung 1 builds and packs nothing — it reuses bytes already on disk (only the read-only integrity check runs)');
|
|
1023
1148
|
|
|
1024
1149
|
// Rung 2 — a registry-installed core packs, still without touching package-core.js.
|
|
1025
1150
|
const reg = mkdtempSync(join(tmpdir(), 'rvct-reg-'));
|