@bongos/core 1.19.316 → 1.19.317
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 +91 -26
- package/modules/lifecycle/db-analytics.js +719 -0
- package/modules/lifecycle/db-claim-reads.js +165 -0
- package/modules/lifecycle/db-claims.js +934 -0
- package/modules/lifecycle/db-deps-criteria.js +441 -0
- package/modules/lifecycle/db-goals.js +763 -0
- package/modules/lifecycle/db-grade.js +233 -0
- package/modules/lifecycle/db-overrides.js +181 -0
- package/modules/lifecycle/db-rank-authz.js +446 -0
- package/modules/lifecycle/db-shared.js +115 -0
- package/modules/lifecycle/db-ship.js +994 -0
- package/modules/lifecycle/db-tasks.js +810 -0
- package/modules/lifecycle/db-versions.js +118 -0
- package/modules/lifecycle/db.js +167 -5564
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/coreB-carve-proof.sh +18 -9
- package/scripts/gds/fitness.js +34 -26
- package/scripts/gds/identity-strip.js +75 -0
- package/scripts/gds/provision.js +7 -3
- package/src/module-api.js +3 -1
- package/tests/auto_satisfy_criteria.mjs +3 -2
- package/tests/dependencies_migration.mjs +3 -2
- package/tests/fitness.mjs +43 -0
- package/tests/goal_abuse_matrix.mjs +3 -2
- package/tests/goal_advisory.mjs +3 -2
- package/tests/government_claim_eligibility.mjs +3 -1
- package/tests/government_rank_sync.mjs +9 -2
- package/tests/helpers.mjs +19 -0
- package/tests/migration_allocation.mjs +3 -1
- package/tests/publish_reconciler.mjs +3 -1
- package/tests/rank_gate.mjs +3 -1
- package/tests/reward_gate.mjs +3 -1
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.317",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.317",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.317",
|
|
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",
|
|
@@ -50,6 +50,13 @@
|
|
|
50
50
|
# FULL_SUITE=1 bash scripts/gds/coreB-carve-proof.sh # also run the full CI gate
|
|
51
51
|
# SKIP_SUITE=1 bash scripts/gds/coreB-carve-proof.sh # legs 1-4 only (fast)
|
|
52
52
|
|
|
53
|
+
# task 1003204 (goal 1000079): the lifecycle data layer is no longer one file —
|
|
54
|
+
# modules/lifecycle/db.js is a re-export facade over twelve db-*.js units. Every
|
|
55
|
+
# proof below that greps "the lifecycle db" must scan the FAMILY, or it silently
|
|
56
|
+
# stops covering code that merely moved between units (and can even pass off a
|
|
57
|
+
# doc-comment in the facade, which is exactly what happened before this fix).
|
|
58
|
+
LIFECYCLE_DB="modules/lifecycle/db.js $(ls modules/lifecycle/db-*.js 2>/dev/null | tr '\n' ' ')"
|
|
59
|
+
|
|
53
60
|
set -uo pipefail
|
|
54
61
|
cd "$(dirname "$0")/../.." # repo root (consistent with portability-smoke-*.sh)
|
|
55
62
|
|
|
@@ -191,10 +198,12 @@ grep -q "registerProvider('reward'" modules/economy/routes/reward.js 2>/dev/null
|
|
|
191
198
|
&& ok "economy registers the 'reward' provider at boot (modules/economy/routes/reward.js)" \
|
|
192
199
|
|| bad "economy does not registerProvider('reward')"
|
|
193
200
|
# the lifecycle db slice carved to modules/lifecycle/db.js at R86 — it resolves the reward port there.
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
# task 1003204 split that slice into db.js + db-*.js, so scan the whole family: the
|
|
202
|
+
# port call now lives in the unit that owns the transaction, not in the facade.
|
|
203
|
+
if grep -q "resolveOptional('reward')" $LIFECYCLE_DB 2>/dev/null; then
|
|
204
|
+
ok "the lifecycle resolves economy via the kernel port (seams.resolveOptional('reward') in the lifecycle db family)"
|
|
196
205
|
else
|
|
197
|
-
bad "the lifecycle does not resolve 'reward' via the seam in modules/lifecycle
|
|
206
|
+
bad "the lifecycle does not resolve 'reward' via the seam in the modules/lifecycle db family"
|
|
198
207
|
fi
|
|
199
208
|
# no core file requires the moved economy files directly (llm-pricing stays core).
|
|
200
209
|
if grep -rnE "require\(['\"][^'\"]*/(cost|streak|achievements|cost-summary-shape)['\"]\)" src/ server.js 2>/dev/null | grep -v node_modules | grep -v module-api | grep -v 'modules/economy' | grep -qv llm-pricing; then
|
|
@@ -352,11 +361,11 @@ grep -q "registerProvider('onboarding'" modules/onboarding/routes/onboarding.js
|
|
|
352
361
|
|| bad "onboarding does not registerProvider('onboarding')"
|
|
353
362
|
# the lifecycle's claim/ship carved to modules/lifecycle/db.js at R86 (it ticks the
|
|
354
363
|
# onboarding stage there); core auth.js (the auth callbacks) stays and resolves it too.
|
|
355
|
-
if grep -q "resolveOptional('onboarding')"
|
|
364
|
+
if grep -q "resolveOptional('onboarding')" $LIFECYCLE_DB 2>/dev/null \
|
|
356
365
|
&& grep -q "resolveOptional('onboarding')" src/bongos/auth.js 2>/dev/null; then
|
|
357
|
-
ok "the lifecycle + core resolve onboarding via the kernel port (resolveOptional('onboarding') in
|
|
366
|
+
ok "the lifecycle + core resolve onboarding via the kernel port (resolveOptional('onboarding') in the lifecycle db family + core auth.js)"
|
|
358
367
|
else
|
|
359
|
-
bad "the lifecycle or core does not resolve 'onboarding' via the seam (
|
|
368
|
+
bad "the lifecycle or core does not resolve 'onboarding' via the seam (lifecycle db family + auth.js)"
|
|
360
369
|
fi
|
|
361
370
|
# no core file requires a moved onboarding domain file directly. The match is
|
|
362
371
|
# scoped to the moved domain basenames; the onboarding CLIs (setup/onboarding-
|
|
@@ -590,10 +599,10 @@ if grep -qE "^async function (claimTask|shipTask|createTask|resolveClaim)\b" src
|
|
|
590
599
|
else
|
|
591
600
|
ok "the lifecycle state-machine SQL left src/bongos/db.js — the residue is the kernel identity/auth/twin slice (ADR 0093 §3)"
|
|
592
601
|
fi
|
|
593
|
-
if grep -qE "async function (claimTask|shipTask|createTask)\b"
|
|
594
|
-
ok "the lifecycle state-machine SQL now lives in modules/lifecycle
|
|
602
|
+
if grep -qE "async function (claimTask|shipTask|createTask)\b" $LIFECYCLE_DB 2>/dev/null; then
|
|
603
|
+
ok "the lifecycle state-machine SQL now lives in the modules/lifecycle db family (claim/ship/createTask/…)"
|
|
595
604
|
else
|
|
596
|
-
bad "the lifecycle state-machine SQL not found in modules/lifecycle
|
|
605
|
+
bad "the lifecycle state-machine SQL not found in the modules/lifecycle db family"
|
|
597
606
|
fi
|
|
598
607
|
# lifecycle registers the `lifecycle` port; core read routes + cascade-dispatch resolve it.
|
|
599
608
|
grep -q "registerProvider('lifecycle'" modules/lifecycle/routes/lifecycle.js 2>/dev/null \
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -971,8 +971,12 @@ const PROMPT_IDENTITY_LITERALS = [
|
|
|
971
971
|
// 1002201 sweep replaced that literal everywhere else. The straggler surfaced as a
|
|
972
972
|
// 401 that locked a builder out of re-authenticating — a valid token verified
|
|
973
973
|
// against the wrong host — and nothing in CI could see it (idea 1000564).
|
|
974
|
-
|
|
975
|
-
|
|
974
|
+
// Widened scripts/gds → ALL of scripts/ at task 1002756: migrate.sh's hardcoded
|
|
975
|
+
// founder DB default (task 1002755) survived every gate because the detector never
|
|
976
|
+
// saw the file — a gate that cannot see the file class the bug lives in is worse
|
|
977
|
+
// than no gate, because it is trusted.
|
|
978
|
+
const IDENTITY_SCAN_ROOTS = ['src/bongos', 'modules', 'scripts'];
|
|
979
|
+
const IDENTITY_SCAN_EXTRA_FILES = [];
|
|
976
980
|
// PERMANENT, COUNTED carve-outs for the scripts/gds root — {file: allowed hits}.
|
|
977
981
|
//
|
|
978
982
|
// Widening the root (task 1002427) surfaced 23 files. Task 1002428 swept them:
|
|
@@ -1010,7 +1014,15 @@ const IDENTITY_SCAN_EXTRA_FILES = ['scripts/gds/architect-audit.js'];
|
|
|
1010
1014
|
// units on that box invoke these by path. Re-pointing an instance's backups is
|
|
1011
1015
|
// deployment work, not a branding read. Their COUNT is still frozen here, so a
|
|
1012
1016
|
// NEW literal in any of them fails exactly like one in an unlisted file.
|
|
1017
|
+
// * fitness.js / package-core.js — THE DETECTORS THEMSELVES: Check 13's regexes
|
|
1018
|
+
// and package-core's PRIOR_INSTANCE_FLOOR name the brand because hunting it is
|
|
1019
|
+
// their job. * seed-v3-tasks.js — the seeded-task-prose rationale above. All
|
|
1020
|
+
// three passed pre-1002756 only by the old stripper's accidents (phantom
|
|
1021
|
+
// blocks + regex-blindness); now they are seen and counted.
|
|
1013
1022
|
const IDENTITY_SCAN_ALLOWED = {
|
|
1023
|
+
'scripts/gds/fitness.js': 2,
|
|
1024
|
+
'scripts/gds/package-core.js': 1,
|
|
1025
|
+
'scripts/gds/seed-v3-tasks.js': 14,
|
|
1014
1026
|
'scripts/gds/audit-replay.sh': 1,
|
|
1015
1027
|
'scripts/gds/db-backup-nightly.sh': 1,
|
|
1016
1028
|
'scripts/gds/db-backup-offsite.sh': 2,
|
|
@@ -1041,28 +1053,10 @@ const IDENTITY_SCAN_EXCLUDE = ['modules/game/'];
|
|
|
1041
1053
|
// Directories with no authored source to gate.
|
|
1042
1054
|
const IDENTITY_SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'generated', 'iterations', 'references']);
|
|
1043
1055
|
|
|
1044
|
-
//
|
|
1045
|
-
//
|
|
1046
|
-
//
|
|
1047
|
-
|
|
1048
|
-
let t = String(text == null ? '' : text);
|
|
1049
|
-
if (ext === '.html') return t.replace(/<!--[\s\S]*?-->/g, '');
|
|
1050
|
-
// Shell has no block comments, so it returns BEFORE the /* */ strip below (a
|
|
1051
|
-
// `"$TMP"/*` glob would otherwise eat real code). Only FULL-LINE `#` comments
|
|
1052
|
-
// (and the shebang) are stripped: cutting from an inline `#` would truncate
|
|
1053
|
-
// `${v#p}` / `$#` lines and could hide a literal sitting after them.
|
|
1054
|
-
if (ext === '.sh') return t.split(/\r?\n/).map((l) => (/^\s*#/.test(l) ? '' : l)).join('\n');
|
|
1055
|
-
t = t.replace(/\/\*[\s\S]*?\*\//g, ''); // block comments (js + css)
|
|
1056
|
-
// Split on /\r?\n/, NOT '\n' (task 1002428). On a CRLF checkout the old split
|
|
1057
|
-
// left a trailing \r on every line, and since `.` does not match \r the `.*$`
|
|
1058
|
-
// in the line-comment pattern could never reach end-of-string — so NO line
|
|
1059
|
-
// comment was stripped and every rationale comment naming the old brand was
|
|
1060
|
-
// scanned as if it were code. That silently broke this function's whole
|
|
1061
|
-
// contract ("a rationale comment naming the old brand never trips the scan")
|
|
1062
|
-
// on Windows, and made the check disagree with Linux CI on the same tree.
|
|
1063
|
-
if (ext === '.js') t = t.split(/\r?\n/).map((l) => l.replace(/(^|[^:])\/\/.*$/, '$1')).join('\n');
|
|
1064
|
-
return t;
|
|
1065
|
-
}
|
|
1056
|
+
// Comment stripping lives in identity-strip.js (task 1002756: extracted + replaced
|
|
1057
|
+
// the two-regex JS strip, which a `/*` inside a `//` comment could OPEN — it
|
|
1058
|
+
// silently hid ~330 provision.js lines from this gate). Re-exported for the tests.
|
|
1059
|
+
const { stripCommentsForIdentity } = require('./identity-strip');
|
|
1066
1060
|
|
|
1067
1061
|
// Pure: scan one file's comment-stripped source for a hardcoded host-brand literal.
|
|
1068
1062
|
// Returns violation strings. Exported for the "actually catches a re-hardcode" test.
|
|
@@ -1153,7 +1147,7 @@ function checkKernelPromptsNoHardcodedIdentity() {
|
|
|
1153
1147
|
hardFail: violations.length > 0,
|
|
1154
1148
|
violations,
|
|
1155
1149
|
warnings: [],
|
|
1156
|
-
note: `${scanned} portable source file(s) scanned (.js/.html/.css/.sh under src/bongos/** + modules/** + scripts
|
|
1150
|
+
note: `${scanned} portable source file(s) scanned (.js/.html/.css/.sh under src/bongos/** + modules/** + scripts/** (whole root, task 1002756), minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5). ${ratcheted} scripts/gds file(s) hold documented permanent carve-outs (real DNS zones, real doc filenames, operator runbook text, historical seeded prose) capped at a count they may not exceed — task 1002428.`,
|
|
1157
1151
|
};
|
|
1158
1152
|
}
|
|
1159
1153
|
|
|
@@ -1798,8 +1792,22 @@ const GOAL_VOCAB_SURFACE = [
|
|
|
1798
1792
|
// allowlist longer than the rule; scanning the LINES THAT NAME THE TABLE catches
|
|
1799
1793
|
// the only thing that can regress here — a membership query reaching for the old
|
|
1800
1794
|
// column — with no false positives to excuse.
|
|
1795
|
+
// task 1003204 (goal 1000079) split that 5k-line file into db.js (a re-export
|
|
1796
|
+
// facade) + twelve db-*.js units, so the membership SQL is no longer in db.js at
|
|
1797
|
+
// all. Enumerate the family from disk rather than naming one file: a line-scoped
|
|
1798
|
+
// scan costs nothing on the units that hold no membership SQL, and a future
|
|
1799
|
+
// re-carve cannot silently disarm the rule by moving the queries again.
|
|
1800
|
+
const LIFECYCLE_DB_FILES = (() => {
|
|
1801
|
+
const dir = path.join(CORE_ROOT, 'modules', 'lifecycle');
|
|
1802
|
+
try {
|
|
1803
|
+
return fs.readdirSync(dir)
|
|
1804
|
+
.filter((f) => f === 'db.js' || (f.startsWith('db-') && f.endsWith('.js')))
|
|
1805
|
+
.sort()
|
|
1806
|
+
.map((f) => `modules/lifecycle/${f}`);
|
|
1807
|
+
} catch { return ['modules/lifecycle/db.js']; }
|
|
1808
|
+
})();
|
|
1801
1809
|
const GOAL_VOCAB_LINE_SCOPED = [
|
|
1802
|
-
{ file
|
|
1810
|
+
...LIFECYCLE_DB_FILES.map((file) => ({ file, mustMention: /goal_members/ })),
|
|
1803
1811
|
// sso.js is the federation surface: only the lines that name the membership table
|
|
1804
1812
|
// or its upsert are in scope, so the file's other server-to-server prose stays out.
|
|
1805
1813
|
{ file: 'modules/platform-identity/routes/sso.js', mustMention: /project_memberships|upsertMembership/ },
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// identity-strip.js — comment stripping for fitness.js Check 13 (the hardcoded-
|
|
3
|
+
// identity gate), extracted in task 1002756. Strips comments so a rationale
|
|
4
|
+
// comment naming the old brand never trips the scan, WITHOUT ever letting a
|
|
5
|
+
// comment marker inside another construct change state.
|
|
6
|
+
//
|
|
7
|
+
// Per extension:
|
|
8
|
+
// .html — <!-- --> blocks removed.
|
|
9
|
+
// .sh — FULL-LINE `#` comments (and the shebang) only: cutting from an inline
|
|
10
|
+
// `#` would truncate `${v#p}` / `$#` lines and could hide a literal
|
|
11
|
+
// sitting after them. Shell has no block comments, so no /* */ pass (a
|
|
12
|
+
// `"$TMP"/*` glob would otherwise eat real code).
|
|
13
|
+
// .css — /* */ blocks removed (css has only block comments).
|
|
14
|
+
// .js — a single character-level pass (task 1002756). The old two-regex
|
|
15
|
+
// approach (block strip THEN line strip) could be OPENED by a `/*`
|
|
16
|
+
// sitting inside a `//` line comment — provision.js's
|
|
17
|
+
// `// … import <dir>/*.caddy` paired with a real `*/` 330 lines later
|
|
18
|
+
// and silently deleted everything between from the scanned text,
|
|
19
|
+
// hiding a genuine hardcoded literal in that window. The scanner
|
|
20
|
+
// tracks string state (' " ` — literals INSIDE strings are what the
|
|
21
|
+
// gate exists to catch, so string bodies are PRESERVED), comment
|
|
22
|
+
// state, and regex-literal state (a `//` or `/*` inside /…/ is not a
|
|
23
|
+
// comment; regex bodies are preserved). CRLF-safe by construction
|
|
24
|
+
// (task 1002428): newlines pass through untouched, and block-comment
|
|
25
|
+
// newlines are kept so reported line numbers hold.
|
|
26
|
+
const KEYWORDS_BEFORE_REGEX = new Set(['return', 'typeof', 'case', 'in', 'of', 'instanceof', 'new', 'delete', 'void', 'do', 'else', 'yield', 'await']);
|
|
27
|
+
|
|
28
|
+
function stripCommentsForIdentity(text, ext) {
|
|
29
|
+
const t = String(text == null ? '' : text);
|
|
30
|
+
if (ext === '.html') return t.replace(/<!--[\s\S]*?-->/g, '');
|
|
31
|
+
if (ext === '.sh') return t.split(/\r?\n/).map((l) => (/^\s*#/.test(l) ? '' : l)).join('\n');
|
|
32
|
+
if (ext === '.css') return t.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
33
|
+
let out = '';
|
|
34
|
+
const n = t.length;
|
|
35
|
+
let i = 0;
|
|
36
|
+
const regexCanFollow = () => {
|
|
37
|
+
const trimmed = out.replace(/[ \t]+$/, '');
|
|
38
|
+
if (trimmed === '' || /[\n(,=:[!&|?{};+\-*%~^<>]$/.test(trimmed)) return true;
|
|
39
|
+
const word = /[A-Za-z_$][\w$]*$/.exec(trimmed);
|
|
40
|
+
return !!(word && KEYWORDS_BEFORE_REGEX.has(word[0]));
|
|
41
|
+
};
|
|
42
|
+
while (i < n) {
|
|
43
|
+
const c = t[i], d = t[i + 1];
|
|
44
|
+
if (c === '/' && d === '/') { while (i < n && t[i] !== '\n') i++; continue; } // line comment
|
|
45
|
+
if (c === '/' && d === '*') { // block comment — keep newlines so line numbers hold
|
|
46
|
+
i += 2;
|
|
47
|
+
while (i < n && !(t[i] === '*' && t[i + 1] === '/')) { if (t[i] === '\n') out += '\n'; i++; }
|
|
48
|
+
i += 2; continue;
|
|
49
|
+
}
|
|
50
|
+
if (c === '\'' || c === '"' || c === '`') { // string — body PRESERVED (leaks live here)
|
|
51
|
+
const q = c; out += c; i++;
|
|
52
|
+
while (i < n && t[i] !== q) {
|
|
53
|
+
if (t[i] === '\\') { out += t[i] + (t[i + 1] || ''); i += 2; continue; }
|
|
54
|
+
out += t[i]; i++;
|
|
55
|
+
}
|
|
56
|
+
out += q; i++; continue;
|
|
57
|
+
}
|
|
58
|
+
if (c === '/' && regexCanFollow()) { // regex literal — body preserved, markers inert
|
|
59
|
+
out += '/'; i++;
|
|
60
|
+
let inClass = false;
|
|
61
|
+
while (i < n && (inClass || t[i] !== '/') && t[i] !== '\n') {
|
|
62
|
+
if (t[i] === '\\') { out += t[i] + (t[i + 1] || ''); i += 2; continue; }
|
|
63
|
+
if (t[i] === '[') inClass = true;
|
|
64
|
+
else if (t[i] === ']') inClass = false;
|
|
65
|
+
out += t[i]; i++;
|
|
66
|
+
}
|
|
67
|
+
if (t[i] === '/') { out += '/'; i++; }
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out += c; i++;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { stripCommentsForIdentity };
|
package/scripts/gds/provision.js
CHANGED
|
@@ -158,9 +158,11 @@ const CONFIG = {
|
|
|
158
158
|
// on the control plane, where the token + the packaging tools live. UNSET
|
|
159
159
|
// (default) = every step runs locally, byte-identical to today.
|
|
160
160
|
remoteHost: process.env.PROVISION_REMOTE_HOST || null,
|
|
161
|
-
// The co-hosting box's checkout dir — the
|
|
162
|
-
//
|
|
163
|
-
|
|
161
|
+
// The co-hosting box's checkout dir — the remote migrate's cwd. NO baked-in default
|
|
162
|
+
// (task 1002756: the founder-pinned path was an identity leak, and a silent wrong
|
|
163
|
+
// cwd aims mutating SSH steps at a dir that may not exist); remote mode requires it
|
|
164
|
+
// explicitly (guarded at the makeExec call), local mode never reads it.
|
|
165
|
+
remoteCwd: process.env.PROVISION_REMOTE_CHECKOUT || null,
|
|
164
166
|
// ssh options for the control-plane → co-hosting hop. BatchMode so a missing key
|
|
165
167
|
// fails fast (never hangs on a prompt); accept-new pins the host key on first
|
|
166
168
|
// connect (the operator can pre-seed known_hosts for strict pinning).
|
|
@@ -2441,6 +2443,8 @@ async function main() {
|
|
|
2441
2443
|
// local) for the box-mutating steps; controlExec is ALWAYS local — the token
|
|
2442
2444
|
// step (Cloudflare DNS hook) + the standalone repo scaffold run on the control
|
|
2443
2445
|
// plane where the token + packaging tools live (task 2065, ADR 0130).
|
|
2446
|
+
// Remote mode needs an explicit checkout dir (task 1002756 — no founder default).
|
|
2447
|
+
if (CONFIG.remoteHost && !CONFIG.remoteCwd) { console.error('PROVISION_REMOTE_HOST is set but PROVISION_REMOTE_CHECKOUT is not — name the co-hosting box\'s checkout dir (the old hardcoded default was removed, task 1002756).'); process.exit(2); }
|
|
2444
2448
|
const exec = makeExec({ apply, remoteHost: CONFIG.remoteHost, remoteCwd: CONFIG.remoteCwd, sshOpts: CONFIG.sshOpts });
|
|
2445
2449
|
const controlExec = makeExec({ apply });
|
|
2446
2450
|
const deps = {
|
package/src/module-api.js
CHANGED
|
@@ -1059,8 +1059,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1059
1059
|
// landed since 1.19.314 with no explicit bump. run 32668969037. (task 1002620)
|
|
1060
1060
|
// 1.19.316 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1061
1061
|
// landed since 1.19.315 with no explicit bump. run 32669347711. (task 1002620)
|
|
1062
|
+
// 1.19.317 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1063
|
+
// landed since 1.19.316 with no explicit bump. run 32669571610. (task 1002620)
|
|
1062
1064
|
// ---------------------------------------------------------------------------
|
|
1063
|
-
const CORE_VERSION = '1.19.
|
|
1065
|
+
const CORE_VERSION = '1.19.317'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1064
1066
|
|
|
1065
1067
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1066
1068
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
import { strict as assert } from 'node:assert';
|
|
35
35
|
import { createRequire } from 'node:module';
|
|
36
36
|
import { readFileSync } from 'node:fs';
|
|
37
|
-
import { makeRunner } from './helpers.mjs';
|
|
37
|
+
import { makeRunner, lifecycleDbSource } from './helpers.mjs';
|
|
38
38
|
|
|
39
39
|
process.env.NODE_ENV = 'test';
|
|
40
40
|
const require = createRequire(import.meta.url);
|
|
@@ -113,7 +113,8 @@ await test('EXEC: every write goes through the caller-supplied exec (so a ship t
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
await test('EXEC: shipTask passes its transaction CLIENT, and the reconciler passes the pool', () => {
|
|
116
|
-
|
|
116
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
117
|
+
const db = lifecycleDbSource();
|
|
117
118
|
// db.js is over the fitness oversize threshold, so the hook is ONE line there
|
|
118
119
|
// and its body lives in done-when.closeCompletedWorkForShip. What must hold is
|
|
119
120
|
// that the thing handed in is the txn client, not a pool.
|
|
@@ -13,7 +13,7 @@ import { strict as assert } from 'node:assert';
|
|
|
13
13
|
import { readFileSync } from 'node:fs';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { dirname, join } from 'node:path';
|
|
16
|
-
import { makeRunner } from './helpers.mjs';
|
|
16
|
+
import { makeRunner, lifecycleDbSource } from './helpers.mjs';
|
|
17
17
|
|
|
18
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
19
|
const repoRoot = join(__dirname, '..');
|
|
@@ -22,7 +22,8 @@ const read = (...p) => readFileSync(join(repoRoot, ...p), 'utf8');
|
|
|
22
22
|
const mig = read('migrations', '163_polymorphic_dependencies.sql');
|
|
23
23
|
const migN = mig.replace(/\s+/g, ' ');
|
|
24
24
|
// the task-dependency CRUD + the claim-gate slice carved to the lifecycle module at BV1.R86.
|
|
25
|
-
|
|
25
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
26
|
+
const dbSrc = lifecycleDbSource();
|
|
26
27
|
|
|
27
28
|
const { test, summary } = makeRunner();
|
|
28
29
|
|
package/tests/fitness.mjs
CHANGED
|
@@ -542,6 +542,49 @@ test('identity gate skips comments + instance-injected tokens (no false positive
|
|
|
542
542
|
assert.equal(scanPromptIdentity('x.html', '<title>{{worldName}} — the gate</title>').length, 0, 'a {{worldName}} token carries no literal');
|
|
543
543
|
});
|
|
544
544
|
|
|
545
|
+
// task 1002756 — the two holes that let migrate.sh's founder DB default survive:
|
|
546
|
+
// (1) the stripper could be OPENED by a `/*` inside a `//` line comment (a real
|
|
547
|
+
// `*/` hundreds of lines later then swallowed everything between — provision.js
|
|
548
|
+
// lost ~330 lines from the scanned text), and (2) whole file classes were never
|
|
549
|
+
// scanned. These lock the stripper; the root widening is pinned by the note test.
|
|
550
|
+
test('identity gate: a /* inside a line comment cannot open a phantom block (task 1002756)', () => {
|
|
551
|
+
const { scanPromptIdentity } = fitness;
|
|
552
|
+
const src = [
|
|
553
|
+
'// glob note: import <dir>/*.caddy', // the phantom opener shape
|
|
554
|
+
'const leak = "amazonprimea.example";', // must STILL be seen
|
|
555
|
+
'doThing();',
|
|
556
|
+
'/* a real block */ const ok = 1;',
|
|
557
|
+
].join('\n');
|
|
558
|
+
assert.equal(scanPromptIdentity('x.js', src).length, 1, 'the literal after a /*-bearing line comment must be caught');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test('identity gate: a // inside a block comment does not truncate the block', () => {
|
|
562
|
+
const { scanPromptIdentity } = fitness;
|
|
563
|
+
const src = [
|
|
564
|
+
'/* rationale: see https://amazonprimea.com — historical // note',
|
|
565
|
+
'still inside the block, amazonprimea again',
|
|
566
|
+
'*/',
|
|
567
|
+
'const clean = 1;',
|
|
568
|
+
].join('\n');
|
|
569
|
+
assert.equal(scanPromptIdentity('x.js', src).length, 0, 'comment prose naming the brand must not trip the scan');
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('identity gate: comment markers inside strings and regex literals are inert', () => {
|
|
573
|
+
const { scanPromptIdentity } = fitness;
|
|
574
|
+
// A string containing /* must not open a comment that hides a later literal…
|
|
575
|
+
assert.equal(scanPromptIdentity('x.js', 'const g = "src/*.js";\nconst leak = "amazonprimea";').length, 1);
|
|
576
|
+
// …a regex containing the brand is code and IS caught (the detectors carve out their own hits)…
|
|
577
|
+
assert.equal(scanPromptIdentity('x.js', 'const re = /amazonprimea/i;').length, 1);
|
|
578
|
+
// …and a division sign is not mistaken for a regex opener that swallows code.
|
|
579
|
+
assert.equal(scanPromptIdentity('x.js', 'const x = a / b / c;\nconst leak = "amazonprimea";').length, 1);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('identity gate scans the WHOLE scripts/ root, not just scripts/gds (task 1002756)', () => {
|
|
583
|
+
const r = fitness.checkKernelPromptsNoHardcodedIdentity();
|
|
584
|
+
assert.match(r.note, /scripts\/\*\* \(whole root/, 'note must document the widened root');
|
|
585
|
+
assert.equal(r.hardFail, false, 'live tree must be clean under the widened scan:\n' + r.violations.join('\n'));
|
|
586
|
+
});
|
|
587
|
+
|
|
545
588
|
// --- write-route validation build check (BV1.R12 / task 1999, ADR 0118) ---
|
|
546
589
|
|
|
547
590
|
test('write-route validation check: the live tree is clean (R11 sweep reached 100%)', () => {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
import { strict as assert } from 'node:assert';
|
|
47
47
|
import { createRequire } from 'node:module';
|
|
48
48
|
import { readFileSync } from 'node:fs';
|
|
49
|
-
import { makeRunner } from './helpers.mjs';
|
|
49
|
+
import { makeRunner, lifecycleDbSource } from './helpers.mjs';
|
|
50
50
|
|
|
51
51
|
process.env.NODE_ENV = 'test';
|
|
52
52
|
|
|
@@ -201,7 +201,8 @@ await test('[Q6] non-member on a public PROTECTED-scope goal → action=apply (s
|
|
|
201
201
|
assert.equal(r.action, 'apply', 'a protected-scope goal is never offered as open-join');
|
|
202
202
|
});
|
|
203
203
|
await test('[Q7] source contract: BOTH INSERT INTO claims sites run the extracted gate (batch cannot bypass)', () => {
|
|
204
|
-
|
|
204
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
205
|
+
const dbSrc = lifecycleDbSource();
|
|
205
206
|
const insertSites = dbSrc.match(/INSERT INTO claims \(/g) || [];
|
|
206
207
|
assert.equal(insertSites.length, 2, 'exactly two claim-insert sites (claimTask + claimTasksBatch) — a third needs the gate too');
|
|
207
208
|
const gateCalls = dbSrc.match(/await goalMembershipFailures\(/g) || [];
|
package/tests/goal_advisory.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { strict as assert } from 'node:assert';
|
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
import { readFileSync } from 'node:fs';
|
|
19
19
|
import http from 'node:http';
|
|
20
|
-
import { makeRunner } from './helpers.mjs';
|
|
20
|
+
import { makeRunner, lifecycleDbSource } from './helpers.mjs';
|
|
21
21
|
|
|
22
22
|
process.env.NODE_ENV = 'test';
|
|
23
23
|
|
|
@@ -25,7 +25,8 @@ const require = createRequire(import.meta.url);
|
|
|
25
25
|
const { adviseGoal, candidateGoals, generalGoalTitle, buildGoalAdvisory } = require('../modules/lifecycle/goal-advisory.js');
|
|
26
26
|
|
|
27
27
|
const tasksSrc = readFileSync(new URL('../modules/lifecycle/routes/tasks.js', import.meta.url), 'utf8');
|
|
28
|
-
|
|
28
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
29
|
+
const dbSrc = lifecycleDbSource();
|
|
29
30
|
const { test, summary } = makeRunner();
|
|
30
31
|
|
|
31
32
|
const V = 'BONGOS-V1';
|
|
@@ -37,6 +37,7 @@ const LIFECYCLE = path.join(ROOT, 'modules', 'lifecycle');
|
|
|
37
37
|
const eligibility = require(path.join(LIFECYCLE, 'claim-eligibility.js'));
|
|
38
38
|
const db = require(path.join(LIFECYCLE, 'db.js'));
|
|
39
39
|
const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
|
|
40
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
40
41
|
|
|
41
42
|
const { CLAIM_ANY, CLAIM_NEWCOMER, permissionAllowsClaim, claimEligibilityAllows } = eligibility;
|
|
42
43
|
|
|
@@ -147,7 +148,8 @@ test('the permission predicate cannot see requires_rank at all (structural)', ()
|
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
test('every claim path in db.js still gates on requires_rank as its own check', () => {
|
|
150
|
-
|
|
151
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
152
|
+
const src = lifecycleDbSource();
|
|
151
153
|
// claimTask, claimTasksBatch, validateClaimBatch — three call sites, three
|
|
152
154
|
// separate rankAllowsTask conjuncts. If a refactor ever folds the floor into the
|
|
153
155
|
// permission predicate, this count drops and the test reds.
|
|
@@ -18,6 +18,7 @@ import { createRequire } from 'node:module';
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
21
22
|
|
|
22
23
|
const require = createRequire(import.meta.url);
|
|
23
24
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -189,8 +190,14 @@ test('the writer runs on the CALLER\'s transaction client when given one', () =>
|
|
|
189
190
|
});
|
|
190
191
|
|
|
191
192
|
test('both rank-change twins rotate the role inside their transaction', () => {
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
// task 1003204: the lifecycle twin moved out of db.js into db-rank-authz.js
|
|
194
|
+
// when the monolith was carved up. Read the whole lifecycle family so the
|
|
195
|
+
// assertion follows the code instead of a filename.
|
|
196
|
+
const twins = [
|
|
197
|
+
['src/bongos/db.js', fs.readFileSync(path.join(ROOT, 'src/bongos/db.js'), 'utf8')],
|
|
198
|
+
['modules/lifecycle/db-*.js', lifecycleDbSource()],
|
|
199
|
+
];
|
|
200
|
+
for (const [rel, src] of twins) {
|
|
194
201
|
const body = src.slice(src.indexOf('async function _applyRankChangeTx'));
|
|
195
202
|
const upd = body.indexOf('UPDATE builders SET rank');
|
|
196
203
|
const sync = body.indexOf('await syncGovernanceRankRole(client');
|
package/tests/helpers.mjs
CHANGED
|
@@ -123,3 +123,22 @@ export function requireHostModule(require, spec, note) {
|
|
|
123
123
|
throw err;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
// lifecycleDbSource — the LIFECYCLE data layer as one source string.
|
|
128
|
+
//
|
|
129
|
+
// task 1003204 (goal 1000079) split the 5,748-line modules/lifecycle/db.js into
|
|
130
|
+
// db.js (a pure re-export facade) + twelve cohesive db-*.js units. A pile of
|
|
131
|
+
// tests assert things about "the data layer" by regexing its source — that the
|
|
132
|
+
// claim paths gate on requires_rank, that a literal appears in exactly one
|
|
133
|
+
// place, that dependency CRUD points at the right table. Those assertions were
|
|
134
|
+
// always about the LAYER, not about one filename, so they read the whole family
|
|
135
|
+
// here. Reading the family (rather than repointing each test at whichever db-*
|
|
136
|
+
// unit happens to hold the code today) also means a future re-carve moves code
|
|
137
|
+
// between units without silently disarming the assertion.
|
|
138
|
+
export function lifecycleDbSource(modulesDir = path.join(ROOT, 'modules')) {
|
|
139
|
+
const dir = path.join(modulesDir, 'lifecycle');
|
|
140
|
+
const files = fs.readdirSync(dir)
|
|
141
|
+
.filter((f) => f === 'db.js' || (f.startsWith('db-') && f.endsWith('.js')))
|
|
142
|
+
.sort();
|
|
143
|
+
return files.map((f) => fs.readFileSync(path.join(dir, f), 'utf8')).join('\n');
|
|
144
|
+
}
|
|
@@ -28,6 +28,7 @@ import { readFileSync } from 'node:fs';
|
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
29
|
import { dirname, join } from 'node:path';
|
|
30
30
|
import { createRequire } from 'node:module';
|
|
31
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
31
32
|
|
|
32
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
34
|
const repoRoot = join(__dirname, '..');
|
|
@@ -49,7 +50,8 @@ async function test(name, fn) {
|
|
|
49
50
|
const read = (...p) => readFileSync(join(repoRoot, ...p), 'utf8');
|
|
50
51
|
const migrationSql = read('migrations', '081_tasks_needs_migration.sql');
|
|
51
52
|
// the tasks db slice + the tasks route carved to modules/lifecycle/ at BV1.R86.
|
|
52
|
-
|
|
53
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
54
|
+
const dbSrc = lifecycleDbSource();
|
|
53
55
|
const routesSrc = read('modules', 'lifecycle', 'routes', 'tasks.js');
|
|
54
56
|
const captureSrc = read('scripts', 'gds', 'capture.js');
|
|
55
57
|
|
|
@@ -16,6 +16,7 @@ import { createRequire } from 'node:module';
|
|
|
16
16
|
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
18
|
const reconciler = require('../modules/lifecycle/publish-reconciler.js');
|
|
19
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
19
20
|
|
|
20
21
|
let passed = 0;
|
|
21
22
|
let failed = 0;
|
|
@@ -1647,7 +1648,8 @@ test('vector 2: shipped_at is stamped ONLY by shipTask, and shipTask requires co
|
|
|
1647
1648
|
const url = await import('node:url');
|
|
1648
1649
|
const path = await import('node:path');
|
|
1649
1650
|
const ROOT = path.dirname(path.dirname(url.fileURLToPath(import.meta.url)));
|
|
1650
|
-
|
|
1651
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
1652
|
+
const dbSrc = lifecycleDbSource();
|
|
1651
1653
|
|
|
1652
1654
|
// Every statement that writes shipped_at, ignoring SELECT projections and comments.
|
|
1653
1655
|
const writes = [...dbSrc.matchAll(/shipped_at\s*=\s*now\(\)/g)];
|
package/tests/rank_gate.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const require = createRequire(import.meta.url);
|
|
|
26
26
|
const auth = require('../src/bongos/auth.js');
|
|
27
27
|
const db = require('../modules/lifecycle/db.js');
|
|
28
28
|
const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
|
|
29
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
29
30
|
|
|
30
31
|
let passed = 0;
|
|
31
32
|
let failed = 0;
|
|
@@ -319,7 +320,8 @@ console.log('\nlistClaimableTasks SQL rank-tier CASE (regression: thetes drift):
|
|
|
319
320
|
|
|
320
321
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
321
322
|
// listClaimableTasks carved to the lifecycle module at BV1.R86 — read the SQL there.
|
|
322
|
-
|
|
323
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
324
|
+
const dbSrc = lifecycleDbSource();
|
|
323
325
|
|
|
324
326
|
t('every live rank appears in the claimable CASE at its REQUIRES_RANK_TIER value', () => {
|
|
325
327
|
// Isolate the listClaimableTasks function body so we don't match CASE
|
package/tests/reward_gate.mjs
CHANGED
|
@@ -25,6 +25,7 @@ const db = require('../modules/lifecycle/db.js');
|
|
|
25
25
|
// the SAME singleton object db.js captured, so mutating its .query stubs the INSERT
|
|
26
26
|
// createTask runs — no database needed.
|
|
27
27
|
const sharedPool = require('../src/module-api.js').pool;
|
|
28
|
+
import { lifecycleDbSource } from './helpers.mjs';
|
|
28
29
|
|
|
29
30
|
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
30
31
|
const root = join(__dir, '..');
|
|
@@ -132,7 +133,8 @@ t('null task is a no-op (no throw, no assignment)', () => {
|
|
|
132
133
|
console.log('\nenforcement points (source-wiring — AUTO-ASSIGN):');
|
|
133
134
|
|
|
134
135
|
const tasksRouteSrc = readFileSync(join(root, 'modules/lifecycle/routes/tasks.js'), 'utf8');
|
|
135
|
-
|
|
136
|
+
// task 1003204: the lifecycle data layer is db.js + db-*.js now — read the family.
|
|
137
|
+
const dbSrc = lifecycleDbSource();
|
|
136
138
|
const migration178 = readFileSync(join(root, 'migrations/178_reward_gate_auto_assign_revert_autopromote.sql'), 'utf8');
|
|
137
139
|
|
|
138
140
|
t('promote route AUTO-ASSIGNS then proceeds (no 409 refusal for missing reward)', () => {
|