@consiliency/agent-board-schema 1.28.0 → 1.38.0
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/fixtures/already-applied.json +40 -0
- package/migrations/20260910000100_agent_board_ceiling_lock_comment_truth.sql +89 -0
- package/migrations/20260911000100_agent_board_ledger_revoked_board_creation.sql +330 -0
- package/migrations/20260912000100_agent_board_receipt_subject_gate.sql +348 -0
- package/migrations/20260913000100_agent_board_task_thread_lock_order.sql +292 -0
- package/migrations/20260914000100_agent_board_claim_subject_gate.sql +500 -0
- package/migrations/20260915000100_agent_board_fedsession_stored_identity_gate.sql +353 -0
- package/migrations/20260916000100_agent_board_federated_identity_injective_slug.sql +1646 -0
- package/migrations/20260917000100_agent_board_invite_no_downgrade.sql +398 -0
- package/migrations/20260918000100_agent_board_signed_write_enforced_repoint.sql +139 -0
- package/migrations/20260919000100_agent_board_signed_write_model_key.sql +107 -0
- package/package.json +1 -1
- package/schema-manifest.json +93 -3
- package/src/deployment-drift-script.test.mjs +424 -0
- package/src/deployment-drift.test.mjs +301 -0
- package/src/tooling.test.mjs +83 -2
- package/tests/agent_board_atomic_fleet_onboarding.test.sql +144 -29
- package/tests/agent_board_bundle_endpoint_compat.test.sql +73 -18
- package/tests/agent_board_claim_subject_gate.test.sql +1025 -0
- package/tests/agent_board_consent_substrate.test.sql +8 -1
- package/tests/agent_board_enroll_resumption.test.sql +7 -1
- package/tests/agent_board_federated_identity_injective_slug.test.sql +388 -0
- package/tests/agent_board_fedsession.test.sql +17 -2
- package/tests/agent_board_fedsession_stored_identity_gate.test.sql +606 -0
- package/tests/agent_board_fk_lock_edges.test.sql +422 -0
- package/tests/agent_board_invite_no_downgrade.test.sql +689 -0
- package/tests/agent_board_ledger_authority.test.sql +142 -71
- package/tests/agent_board_receipt_subject_gate.test.sql +624 -0
- package/tests/agent_board_signed_write_enforced.test.sql +366 -0
- package/tests/agent_board_signer_lifecycle.test.sql +23 -6
- package/tests/agent_board_source_strip_helper.test.sql +256 -1
- package/tests/agent_board_task_thread_lock_order.test.sql +337 -0
- package/tests/agent_board_write_enforcement_class.test.sql +1016 -0
- package/tests/catalog/p_metadata_parameters.json +12 -0
- package/tests/fixtures/isolation-substrate.sql +1 -1
- package/tests/fixtures/test-prereqs.sql +442 -11
- package/tests/isolation/expected/ledger_authority_revoke.out +219 -1
- package/tests/isolation/expected/task_thread_lock_order.out +203 -0
- package/tests/isolation/specs/enroll_identity_board_owner_lock_order.spec +5 -4
- package/tests/isolation/specs/fedsession_provider_absent_owner_lock.spec +4 -4
- package/tests/isolation/specs/ledger_authority_revoke.spec +115 -0
- package/tests/isolation/specs/task_thread_lock_order.spec +197 -0
- package/tests/seeds/20260916000100_agent_board_federated_identity_injective_slug.sql.pre.sql +196 -0
package/schema-manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema": "agent_board_schema_manifest.v1",
|
|
3
3
|
"interface": "IF-0-MBPLANE-BUNDLE-1",
|
|
4
4
|
"package": "@consiliency/agent-board-schema",
|
|
5
|
-
"packageVersion": "1.
|
|
5
|
+
"packageVersion": "1.38.0",
|
|
6
6
|
"schemaVersion": 8,
|
|
7
7
|
"minClientVersion": "1.1.0",
|
|
8
8
|
"source": {
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"usage": "upsert (retention/monitoring defaults; on conflict do nothing)"
|
|
47
47
|
}
|
|
48
48
|
],
|
|
49
|
-
"migrationCount":
|
|
50
|
-
"aggregateSha256": "
|
|
49
|
+
"migrationCount": 137,
|
|
50
|
+
"aggregateSha256": "bfe0b978234464938428a66199b75d2f8d028706517b4674f9ae11cae34c52d1",
|
|
51
51
|
"migrations": [
|
|
52
52
|
{
|
|
53
53
|
"order": 0,
|
|
@@ -1191,6 +1191,96 @@
|
|
|
1191
1191
|
"sourceRef": "message-board",
|
|
1192
1192
|
"bytes": 67493,
|
|
1193
1193
|
"sha256": "7522e32c61f8845e511255d94d273d98eb7b72b2f906976c3a2494629927462d"
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
"order": 127,
|
|
1197
|
+
"id": "20260910000100_agent_board_ceiling_lock_comment_truth",
|
|
1198
|
+
"filename": "20260910000100_agent_board_ceiling_lock_comment_truth.sql",
|
|
1199
|
+
"origin": "message-board-forward",
|
|
1200
|
+
"sourceRef": "message-board",
|
|
1201
|
+
"bytes": 7087,
|
|
1202
|
+
"sha256": "b8ddc658be347d9e7207111968d0b8de7a88759cb3cf181f61c4ef306086f384"
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
"order": 128,
|
|
1206
|
+
"id": "20260911000100_agent_board_ledger_revoked_board_creation",
|
|
1207
|
+
"filename": "20260911000100_agent_board_ledger_revoked_board_creation.sql",
|
|
1208
|
+
"origin": "message-board-forward",
|
|
1209
|
+
"sourceRef": "message-board",
|
|
1210
|
+
"bytes": 22749,
|
|
1211
|
+
"sha256": "4291af2bd6c93b7d9f8ec596f41dc62edf941ea10a51529df04e223d1cbc50c5"
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
"order": 129,
|
|
1215
|
+
"id": "20260912000100_agent_board_receipt_subject_gate",
|
|
1216
|
+
"filename": "20260912000100_agent_board_receipt_subject_gate.sql",
|
|
1217
|
+
"origin": "message-board-forward",
|
|
1218
|
+
"sourceRef": "message-board",
|
|
1219
|
+
"bytes": 19033,
|
|
1220
|
+
"sha256": "1083f7720ccab9c9b422415f2a8eb743de5d84b0ab8d8689059bd355514e9aa9"
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
"order": 130,
|
|
1224
|
+
"id": "20260913000100_agent_board_task_thread_lock_order",
|
|
1225
|
+
"filename": "20260913000100_agent_board_task_thread_lock_order.sql",
|
|
1226
|
+
"origin": "message-board-forward",
|
|
1227
|
+
"sourceRef": "message-board",
|
|
1228
|
+
"bytes": 17738,
|
|
1229
|
+
"sha256": "259a05c4a6603518ab449e799fcb235b0e16a6650954cb2db19b436c0a7a1e83"
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
"order": 131,
|
|
1233
|
+
"id": "20260914000100_agent_board_claim_subject_gate",
|
|
1234
|
+
"filename": "20260914000100_agent_board_claim_subject_gate.sql",
|
|
1235
|
+
"origin": "message-board-forward",
|
|
1236
|
+
"sourceRef": "message-board",
|
|
1237
|
+
"bytes": 34342,
|
|
1238
|
+
"sha256": "88207b2e3f28ec8022ad4ca921721ebba877668042c66b5ac74c85e4c8cefc21"
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
"order": 132,
|
|
1242
|
+
"id": "20260915000100_agent_board_fedsession_stored_identity_gate",
|
|
1243
|
+
"filename": "20260915000100_agent_board_fedsession_stored_identity_gate.sql",
|
|
1244
|
+
"origin": "message-board-forward",
|
|
1245
|
+
"sourceRef": "message-board",
|
|
1246
|
+
"bytes": 18615,
|
|
1247
|
+
"sha256": "5a8f4abb822e30c2a695a310bb1742f2565bb2afd0b6a06cad43fcbdd106ad5e"
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
"order": 133,
|
|
1251
|
+
"id": "20260916000100_agent_board_federated_identity_injective_slug",
|
|
1252
|
+
"filename": "20260916000100_agent_board_federated_identity_injective_slug.sql",
|
|
1253
|
+
"origin": "message-board-forward",
|
|
1254
|
+
"sourceRef": "message-board",
|
|
1255
|
+
"bytes": 84162,
|
|
1256
|
+
"sha256": "d9f05e18094954f2e4ee32bf7b129f0a9ffc39bc800a5efa9cf7938e1555c8dc"
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
"order": 134,
|
|
1260
|
+
"id": "20260917000100_agent_board_invite_no_downgrade",
|
|
1261
|
+
"filename": "20260917000100_agent_board_invite_no_downgrade.sql",
|
|
1262
|
+
"origin": "message-board-forward",
|
|
1263
|
+
"sourceRef": "message-board",
|
|
1264
|
+
"bytes": 24929,
|
|
1265
|
+
"sha256": "c205a9a679dca059979972b49c89c3b1b3b8f8e2039bd7675a10578f49f9b35a"
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
"order": 135,
|
|
1269
|
+
"id": "20260918000100_agent_board_signed_write_enforced_repoint",
|
|
1270
|
+
"filename": "20260918000100_agent_board_signed_write_enforced_repoint.sql",
|
|
1271
|
+
"origin": "message-board-forward",
|
|
1272
|
+
"sourceRef": "message-board",
|
|
1273
|
+
"bytes": 9965,
|
|
1274
|
+
"sha256": "f76dd01744029bfd0088bd4f6cf6fa5cb93e3c9b7fa5559ad83857cf7f4eb8ce"
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
"order": 136,
|
|
1278
|
+
"id": "20260919000100_agent_board_signed_write_model_key",
|
|
1279
|
+
"filename": "20260919000100_agent_board_signed_write_model_key.sql",
|
|
1280
|
+
"origin": "message-board-forward",
|
|
1281
|
+
"sourceRef": "message-board",
|
|
1282
|
+
"bytes": 7204,
|
|
1283
|
+
"sha256": "464ae39acbd96330c5ad1c753fa1204ca290baa0d6f82d7d30d0ead4d2944d37"
|
|
1194
1284
|
}
|
|
1195
1285
|
],
|
|
1196
1286
|
"excludedBridge": {
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
// Tests for the SHIPPED deployment-drift SCRIPT — scripts/check-deployment-drift.mjs.
|
|
2
|
+
//
|
|
3
|
+
// WHY THIS FILE EXISTS. deployment-drift.test.mjs covers `evaluate()`, the decision. It
|
|
4
|
+
// does not load the script at all, and an audit measured the consequence: flipping the
|
|
5
|
+
// final line's exit mapping from `UNKNOWN -> 2` to `UNKNOWN -> 0` left the whole suite
|
|
6
|
+
// green, and deleting `makeScrub` entirely left it green. The exit mapping is the property
|
|
7
|
+
// the PR exists for and the scrubber is its security property; both were pinned by nothing.
|
|
8
|
+
//
|
|
9
|
+
// So these tests run the real script as a SUBPROCESS and assert its exit code and its
|
|
10
|
+
// output. Nothing here reimplements it.
|
|
11
|
+
//
|
|
12
|
+
// HERMETIC BY CONSTRUCTION. The script shells out to `npm view` and `git`, and fetches
|
|
13
|
+
// over the network. All three are controlled, and none of them reaches outside this box:
|
|
14
|
+
// - `npm` and `git` are shell shims on a prepended PATH. They also append their argv to
|
|
15
|
+
// a log, which is how the `-L` range below is asserted.
|
|
16
|
+
// - `globalThis.fetch` is replaced by a preloaded module (`--import`), so no request is
|
|
17
|
+
// ever issued. No real project ref is ever named.
|
|
18
|
+
// The tests therefore do not depend on network reachability, on npm auth, or on the
|
|
19
|
+
// clone depth of the checkout running them.
|
|
20
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
21
|
+
import { spawnSync } from "node:child_process";
|
|
22
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync, chmodSync, existsSync } from "node:fs";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { dirname, join, resolve } from "node:path";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
+
|
|
27
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const ROOT = resolve(HERE, "..", "..", ".."); // packages/agent-board-schema/src -> repo root
|
|
29
|
+
const SCRIPT = join(ROOT, "scripts", "check-deployment-drift.mjs");
|
|
30
|
+
const PKG_JSON = join(ROOT, "packages", "agent-board-schema", "package.json");
|
|
31
|
+
const MANIFEST = join(ROOT, "packages", "agent-board-schema", "schema-manifest.json");
|
|
32
|
+
|
|
33
|
+
const MERGED = JSON.parse(readFileSync(PKG_JSON, "utf8")).version;
|
|
34
|
+
const MANIFEST_JSON = JSON.parse(readFileSync(MANIFEST, "utf8"));
|
|
35
|
+
|
|
36
|
+
// A version several minors BELOW merged, derived so this does not rot when merged bumps.
|
|
37
|
+
function minorsBelow(v, n) {
|
|
38
|
+
const [maj, min, pat] = v.split(".").map(Number);
|
|
39
|
+
return `${maj}.${Math.max(0, min - n)}.${pat}`;
|
|
40
|
+
}
|
|
41
|
+
function minorsAbove(v, n) {
|
|
42
|
+
const [maj, min, pat] = v.split(".").map(Number);
|
|
43
|
+
return `${maj}.${min + n}.${pat}`;
|
|
44
|
+
}
|
|
45
|
+
const FRESH_BUMP = new Date(Date.now() - 3_600_000).toISOString();
|
|
46
|
+
// Postgres renders timestamptz with a space and a two-digit offset; the script's own
|
|
47
|
+
// parser exists for that shape, so the fixture uses it rather than an ISO string.
|
|
48
|
+
const pgStamp = (msAgo) => new Date(Date.now() - msAgo).toISOString().replace("T", " ").replace(/Z$/, "+00");
|
|
49
|
+
// A record that agrees with the repo on every field the check compares.
|
|
50
|
+
const consistentRow = (over = {}) => ({
|
|
51
|
+
package_version: MERGED,
|
|
52
|
+
schema_version: MANIFEST_JSON.schemaVersion,
|
|
53
|
+
bundle_digest: MANIFEST_JSON.aggregateSha256,
|
|
54
|
+
installed_at: pgStamp(1_800_000), // 30 minutes ago — after FRESH_BUMP
|
|
55
|
+
...over,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// A canary that is NOT a credential: it never authenticated anything and is not a real
|
|
59
|
+
// token shape. The 20-character prefix is the thing an audit saw printed verbatim.
|
|
60
|
+
const CANARY = "canary_token_shape_A_not_a_real_secret_value";
|
|
61
|
+
const CANARY_PREFIX = CANARY.slice(0, 20); // "canary_token_shape_A"
|
|
62
|
+
|
|
63
|
+
let SHIMS;
|
|
64
|
+
let ARGV_LOG;
|
|
65
|
+
let PRELOAD;
|
|
66
|
+
|
|
67
|
+
beforeAll(() => {
|
|
68
|
+
SHIMS = mkdtempSync(join(tmpdir(), "drift-shims-"));
|
|
69
|
+
ARGV_LOG = join(SHIMS, "argv.log");
|
|
70
|
+
|
|
71
|
+
// One arg per line, `--` between invocations, so an assertion can read exact argv rather
|
|
72
|
+
// than a space-joined approximation.
|
|
73
|
+
const logArgv = 'printf -- "--\\n" >> "$DRIFT_ARGV_LOG"; for a in "$@"; do printf "%s\\n" "$a" >> "$DRIFT_ARGV_LOG"; done';
|
|
74
|
+
|
|
75
|
+
writeFileSync(
|
|
76
|
+
join(SHIMS, "npm"),
|
|
77
|
+
`#!/bin/sh\n${logArgv}\n[ -n "\${FAKE_NPM_VERSION:-}" ] || exit 1\nprintf "%s\\n" "$FAKE_NPM_VERSION"\n`,
|
|
78
|
+
);
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(SHIMS, "git"),
|
|
81
|
+
`#!/bin/sh\n${logArgv}\ncase " $* " in\n *" rev-parse "*) printf "%s\\n" "\${FAKE_SHALLOW:-false}" ;;\n *" log "*) [ -n "\${FAKE_BUMP_ISO:-}" ] || exit 1; printf "%s\\n" "$FAKE_BUMP_ISO" ;;\n *) exit 1 ;;\nesac\n`,
|
|
82
|
+
);
|
|
83
|
+
chmodSync(join(SHIMS, "npm"), 0o755);
|
|
84
|
+
chmodSync(join(SHIMS, "git"), 0o755);
|
|
85
|
+
|
|
86
|
+
// No request leaves this machine.
|
|
87
|
+
//
|
|
88
|
+
// The throwing modes carry their probe EARLY in the string, so a passing redaction
|
|
89
|
+
// assertion cannot be truncation in disguise. The RESOLVING modes exist because a review
|
|
90
|
+
// found every mode here threw: `readRunning`'s success mapping and its no-record branch
|
|
91
|
+
// were unreachable from any test, so the only board outcomes ever exercised were the
|
|
92
|
+
// error ones. A stub that can only fail cannot pin what happens when the call works.
|
|
93
|
+
PRELOAD = join(SHIMS, "no-network.mjs");
|
|
94
|
+
writeFileSync(
|
|
95
|
+
PRELOAD,
|
|
96
|
+
[
|
|
97
|
+
'const mode = process.env.FAKE_FETCH_MODE || "";',
|
|
98
|
+
"const respond = (status, body) => ({",
|
|
99
|
+
" ok: status >= 200 && status < 300,",
|
|
100
|
+
" status,",
|
|
101
|
+
" json: async () => body,",
|
|
102
|
+
"});",
|
|
103
|
+
"globalThis.fetch = async () => {",
|
|
104
|
+
' if (mode === "board-row") return respond(200, [JSON.parse(process.env.FAKE_BOARD_ROW)]);',
|
|
105
|
+
' if (mode === "board-no-record") return respond(200, []);',
|
|
106
|
+
' if (mode === "board-http-error") return respond(401, { message: "unauthorized" });',
|
|
107
|
+
' if (mode === "echo-token-prefix") {',
|
|
108
|
+
" throw new Error(`connect ${String(process.env.SUPABASE_ACCESS_TOKEN).slice(0, 20)} ECONNREFUSED upstream`);",
|
|
109
|
+
" }",
|
|
110
|
+
' if (mode === "foreign-github") throw new Error("upstream gho_OTHERPARTYCRED_9999 rejected while proxying");',
|
|
111
|
+
' if (mode === "foreign-unknown-shape") throw new Error("upstream zt9f3k1QweRtyUiOpAsDf rejected while proxying");',
|
|
112
|
+
' if (mode === "foreign-vendor-only") throw new Error("upstream xoxb-SLACKBOTTOKENEXAMPLE rejected while proxying");',
|
|
113
|
+
' if (mode === "foreign-aws-secret") throw new Error("upstream wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY rejected");',
|
|
114
|
+
// The seat's measured over-redaction: a URL inside the RESPONSE text is eaten by the
|
|
115
|
+
// generic layer, because a path has the same shape as a base64 secret. Note the ref
|
|
116
|
+
// embedded here differs from the one passed on the command line, so an assertion
|
|
117
|
+
// about the endpoint cannot be satisfied by this string surviving.
|
|
118
|
+
' if (mode === "http-500-with-url") {',
|
|
119
|
+
' throw new Error("HTTP 500 from https://api.supabase.com/v1/projects/some-other-ref-in-the-message/database/query");',
|
|
120
|
+
" }",
|
|
121
|
+
' throw new Error("network disabled in test");',
|
|
122
|
+
"};",
|
|
123
|
+
"",
|
|
124
|
+
].join("\n"),
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
afterAll(() => {
|
|
129
|
+
if (SHIMS) rmSync(SHIMS, { recursive: true, force: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function runScript(args = [], env = {}) {
|
|
133
|
+
const r = spawnSync(process.execPath, [SCRIPT, ...args], {
|
|
134
|
+
cwd: ROOT,
|
|
135
|
+
encoding: "utf8",
|
|
136
|
+
timeout: 60_000,
|
|
137
|
+
env: {
|
|
138
|
+
...process.env,
|
|
139
|
+
PATH: `${SHIMS}:${process.env.PATH}`,
|
|
140
|
+
NODE_OPTIONS: `--import ${pathToFileURL(PRELOAD).href}`,
|
|
141
|
+
DRIFT_ARGV_LOG: ARGV_LOG,
|
|
142
|
+
FAKE_BUMP_ISO: FRESH_BUMP,
|
|
143
|
+
FAKE_SHALLOW: "false",
|
|
144
|
+
FAKE_FETCH_MODE: "",
|
|
145
|
+
SUPABASE_ACCESS_TOKEN: CANARY,
|
|
146
|
+
...env,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return { ...r, out: `${r.stdout ?? ""}${r.stderr ?? ""}` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// The exit mapping. This is the whole point of the change, and it was pinned by nothing:
|
|
154
|
+
// `UNKNOWN -> 0` left all 38 evaluate() tests green.
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
describe("the shipped script's exit mapping", () => {
|
|
157
|
+
it("exits 0 when merged and published agree — OK is reachable", () => {
|
|
158
|
+
const r = runScript([], { FAKE_NPM_VERSION: MERGED });
|
|
159
|
+
expect(r.status).toBe(0);
|
|
160
|
+
expect(r.out).toMatch(/no drift/);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("exits 1 on real drift — DRIFT is not folded into success", () => {
|
|
164
|
+
const r = runScript([], { FAKE_NPM_VERSION: minorsBelow(MERGED, 10) });
|
|
165
|
+
expect(r.status).toBe(1);
|
|
166
|
+
expect(r.out).toMatch(/DRIFT/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("exits 2 when a leg cannot be determined — UNKNOWN is NOT a pass", () => {
|
|
170
|
+
// npm shim exits non-zero: the published version is undeterminable.
|
|
171
|
+
const r = runScript([], { FAKE_NPM_VERSION: "" });
|
|
172
|
+
expect(r.status).toBe(2);
|
|
173
|
+
expect(r.out).toMatch(/UNKNOWN/);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("exits 2, not 0, when the board leg cannot be read", () => {
|
|
177
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project"], {
|
|
178
|
+
FAKE_NPM_VERSION: MERGED,
|
|
179
|
+
FAKE_FETCH_MODE: "network disabled",
|
|
180
|
+
});
|
|
181
|
+
expect(r.status).toBe(2);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("rejects a non-numeric threshold with exit 2 rather than passing everything", () => {
|
|
185
|
+
const r = runScript(["--max-days", "not-a-number"], { FAKE_NPM_VERSION: MERGED });
|
|
186
|
+
expect(r.status).toBe(2);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("refuses to date the bump on a shallow clone, so an unpublished gap is UNKNOWN", () => {
|
|
190
|
+
// FAKE_SHALLOW was never true in any test, so the shallow-clone refusal the previous
|
|
191
|
+
// round added was driven by nothing. Making isShallowClone() return false
|
|
192
|
+
// unconditionally reds this: the shimmed bump date would come back fresh and a
|
|
193
|
+
// one-version gap would report OK / exit 0.
|
|
194
|
+
const r = runScript([], { FAKE_NPM_VERSION: minorsBelow(MERGED, 1), FAKE_SHALLOW: "true" });
|
|
195
|
+
expect(r.status).toBe(2);
|
|
196
|
+
expect(r.out).toMatch(/bump date could not be determined/);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// The board leg. Every mocked fetch used to THROW, so `readRunning`'s success mapping and
|
|
202
|
+
// its no-record branch were unreachable from any test and only the error outcomes were
|
|
203
|
+
// ever exercised.
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
describe("the board leg, when the query actually succeeds", () => {
|
|
206
|
+
const boardArgs = ["--board", "fixture-ref-not-a-real-project"];
|
|
207
|
+
|
|
208
|
+
it("maps a consistent install record to OK — the success path is reachable at all", () => {
|
|
209
|
+
const r = runScript(boardArgs, {
|
|
210
|
+
FAKE_NPM_VERSION: MERGED,
|
|
211
|
+
FAKE_FETCH_MODE: "board-row",
|
|
212
|
+
FAKE_BOARD_ROW: JSON.stringify(consistentRow()),
|
|
213
|
+
});
|
|
214
|
+
expect(r.status).toBe(0);
|
|
215
|
+
expect(r.out).toMatch(/record consistent/);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("treats an empty result set as NO install record — DRIFT, not an empty pass", () => {
|
|
219
|
+
const r = runScript(boardArgs, { FAKE_NPM_VERSION: MERGED, FAKE_FETCH_MODE: "board-no-record" });
|
|
220
|
+
expect(r.status).toBe(1);
|
|
221
|
+
expect(r.out).toMatch(/NO install record/);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("treats a non-2xx response as UNKNOWN, not as an absent record", () => {
|
|
225
|
+
const r = runScript(boardArgs, { FAKE_NPM_VERSION: MERGED, FAKE_FETCH_MODE: "board-http-error" });
|
|
226
|
+
expect(r.status).toBe(2);
|
|
227
|
+
expect(r.out).toMatch(/HTTP 401/);
|
|
228
|
+
// The status-code path names the endpoint too. Without this, only the exception path's
|
|
229
|
+
// `endpointOf` was pinned and this one could lose it silently.
|
|
230
|
+
expect(r.out).toContain("POST /v1/projects/fixture-ref-not-a-real-project/database/query");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("reports a board AHEAD of merged as DRIFT, end to end — it exits 1, not 0", () => {
|
|
234
|
+
// The round-3 reproduction: a board running a version main does not have, with a
|
|
235
|
+
// digest and schema_version that MATCH, reported ok and exited 0.
|
|
236
|
+
const r = runScript(boardArgs, {
|
|
237
|
+
FAKE_NPM_VERSION: MERGED,
|
|
238
|
+
FAKE_FETCH_MODE: "board-row",
|
|
239
|
+
FAKE_BOARD_ROW: JSON.stringify(consistentRow({ package_version: minorsAbove(MERGED, 1) })),
|
|
240
|
+
});
|
|
241
|
+
expect(r.status).toBe(1);
|
|
242
|
+
expect(r.out).toMatch(/AHEAD of merged/);
|
|
243
|
+
expect(r.out).not.toMatch(/record consistent/);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// The scrubber, end to end through the shipped script. Deleting `makeScrub` left the
|
|
249
|
+
// previous suite green.
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
describe("credentials never reach the output", () => {
|
|
252
|
+
it("redacts a TRUNCATED echo of the token we hold, not just an exact match", () => {
|
|
253
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
254
|
+
FAKE_NPM_VERSION: MERGED,
|
|
255
|
+
FAKE_FETCH_MODE: "echo-token-prefix",
|
|
256
|
+
});
|
|
257
|
+
expect(r.out).not.toContain(CANARY);
|
|
258
|
+
expect(r.out).not.toContain(CANARY_PREFIX);
|
|
259
|
+
// Proves redaction did the work, not the 200-character truncation.
|
|
260
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
261
|
+
expect(r.status).toBe(2);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// The probe from the original finding. NOTE, because a commit message got this wrong:
|
|
265
|
+
// this probe is matched by the vendor layer AND by the generic layer, so it does NOT
|
|
266
|
+
// isolate either one — deleting the vendor layer leaves it green. It pins that the
|
|
267
|
+
// reported probe stops printing, nothing more. The isolating probe is the next test.
|
|
268
|
+
it("redacts a foreign credential of a KNOWN vendor shape we never held", () => {
|
|
269
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
270
|
+
FAKE_NPM_VERSION: MERGED,
|
|
271
|
+
FAKE_FETCH_MODE: "foreign-github",
|
|
272
|
+
});
|
|
273
|
+
expect(r.out).not.toContain("gho_OTHERPARTYCRED_9999");
|
|
274
|
+
expect(r.out).not.toContain("OTHERPARTYCRED");
|
|
275
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("redacts a vendor-prefixed credential the generic layer CANNOT see — pins layer 2 alone", () => {
|
|
279
|
+
// All letters after the prefix, so the generic layer's digit lookahead fails and only
|
|
280
|
+
// the vendor-prefix layer can catch it. Deleting that layer reds this and nothing else.
|
|
281
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
282
|
+
FAKE_NPM_VERSION: MERGED,
|
|
283
|
+
FAKE_FETCH_MODE: "foreign-vendor-only",
|
|
284
|
+
});
|
|
285
|
+
expect(r.out).not.toContain("xoxb-SLACKBOTTOKENEXAMPLE");
|
|
286
|
+
expect(r.out).not.toContain("SLACKBOTTOKEN");
|
|
287
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("redacts an AWS secret access key — the `/` that split it out of the old class", () => {
|
|
291
|
+
// `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` printed intact: the old generic class
|
|
292
|
+
// excluded `/`, so it split into 13-, 7- and 18-character pieces and the 18-character
|
|
293
|
+
// piece was all letters. Long and mixed-class — inside what the header claimed to
|
|
294
|
+
// cover. Each surviving fragment is asserted separately, so a partial leak fails too.
|
|
295
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
296
|
+
FAKE_NPM_VERSION: MERGED,
|
|
297
|
+
FAKE_FETCH_MODE: "foreign-aws-secret",
|
|
298
|
+
});
|
|
299
|
+
expect(r.out).not.toContain("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
|
|
300
|
+
expect(r.out).not.toContain("wJalrXUtnFEMI");
|
|
301
|
+
expect(r.out).not.toContain("bPxRfiCYEXAMPLEKEY");
|
|
302
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("redacts a foreign credential of an UNKNOWN shape — the generic mixed-class layer", () => {
|
|
306
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
307
|
+
FAKE_NPM_VERSION: MERGED,
|
|
308
|
+
FAKE_FETCH_MODE: "foreign-unknown-shape",
|
|
309
|
+
});
|
|
310
|
+
expect(r.out).not.toContain("zt9f3k1QweRtyUiOpAsDf");
|
|
311
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("an HTTP failure still names the board and the endpoint even when the message is redacted", () => {
|
|
315
|
+
// Redaction must not be able to destroy diagnosability. The generic layer eats URL
|
|
316
|
+
// paths (a path and a base64 secret have the same shape), so the ref and the endpoint
|
|
317
|
+
// are printed from this process's OWN variables rather than recovered from the
|
|
318
|
+
// response. Removing `endpointOf(ref)` from the error string reds this.
|
|
319
|
+
const r = runScript(["--board", "fixture-ref-not-a-real-project", "--json"], {
|
|
320
|
+
FAKE_NPM_VERSION: MERGED,
|
|
321
|
+
FAKE_FETCH_MODE: "http-500-with-url",
|
|
322
|
+
});
|
|
323
|
+
// The path we supplied survives in full...
|
|
324
|
+
expect(r.out).toContain("POST /v1/projects/fixture-ref-not-a-real-project/database/query");
|
|
325
|
+
// ...while the path that came back over the network was still redacted, so the
|
|
326
|
+
// assertion above cannot be passing on the response text.
|
|
327
|
+
expect(r.out).not.toContain("some-other-ref-in-the-message");
|
|
328
|
+
expect(r.out).toMatch(/<redacted>/);
|
|
329
|
+
expect(r.status).toBe(2);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("never places a credential in a child process's argv", () => {
|
|
333
|
+
runScript(["--board", "fixture-ref-not-a-real-project"], {
|
|
334
|
+
FAKE_NPM_VERSION: MERGED,
|
|
335
|
+
FAKE_FETCH_MODE: "network disabled",
|
|
336
|
+
});
|
|
337
|
+
const argv = readFileSync(ARGV_LOG, "utf8");
|
|
338
|
+
expect(argv).not.toContain(CANARY);
|
|
339
|
+
expect(argv).not.toContain(CANARY_PREFIX);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// Manifest expectations: the exact field, not a pattern.
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
describe("the digest expectation comes from the named manifest field", () => {
|
|
347
|
+
it("carries schema-manifest.json's aggregateSha256 into the facts", () => {
|
|
348
|
+
const r = runScript(["--json"], { FAKE_NPM_VERSION: MERGED });
|
|
349
|
+
const doc = JSON.parse(r.stdout);
|
|
350
|
+
expect(MANIFEST_JSON.aggregateSha256).toBeTruthy();
|
|
351
|
+
expect(doc.facts.expectedDigest).toBe(MANIFEST_JSON.aggregateSha256);
|
|
352
|
+
expect(doc.facts.expectedSchemaVersion).toBe(MANIFEST_JSON.schemaVersion);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// The `git log -L` range. A seat reported that `/"version":/,+1` spans TWO lines, so an
|
|
358
|
+
// edit to the ADJACENT line would reset the bump date. Measured on git 2.55, `,+N` is N
|
|
359
|
+
// lines TOTAL, so `,+1` is one line and the finding is refuted. This test pins BOTH halves
|
|
360
|
+
// so the refutation is not just a comment: it reads the range the script actually passes,
|
|
361
|
+
// then runs REAL git with that exact range against a fixture where the line after
|
|
362
|
+
// `"version":` was edited later than the version itself.
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
describe("the bump date comes from the version line alone", () => {
|
|
365
|
+
const git = (cwd, args, env = {}) =>
|
|
366
|
+
spawnSync("git", args, { cwd, encoding: "utf8", env: { ...process.env, ...env } });
|
|
367
|
+
|
|
368
|
+
function fixtureRepo(dir) {
|
|
369
|
+
mkdirSync(dir, { recursive: true });
|
|
370
|
+
const commit = (msg, body, iso) => {
|
|
371
|
+
writeFileSync(join(dir, "package.json"), body);
|
|
372
|
+
git(dir, ["add", "-A"]);
|
|
373
|
+
git(dir, ["-c", "user.email=t@example.invalid", "-c", "user.name=t", "commit", "-qm", msg], {
|
|
374
|
+
GIT_AUTHOR_DATE: iso,
|
|
375
|
+
GIT_COMMITTER_DATE: iso,
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
git(dir, ["init", "-q", "."]);
|
|
379
|
+
const doc = (v, d) => `{\n "name": "x",\n "version": "${v}",\n "description": "${d}",\n "type": "module"\n}\n`;
|
|
380
|
+
commit("base", doc("1.0.0", "one"), "2026-01-01T00:00:00Z");
|
|
381
|
+
commit("bump", doc("1.1.0", "one"), "2026-02-01T00:00:00Z");
|
|
382
|
+
commit("adjacent-line-edit", doc("1.1.0", "TWO"), "2026-06-01T00:00:00Z");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
it("passes a range that selects the version bump and NOT the adjacent-line edit", () => {
|
|
386
|
+
// 1. what range does the shipped script actually pass?
|
|
387
|
+
runScript([], { FAKE_NPM_VERSION: MERGED });
|
|
388
|
+
const lines = readFileSync(ARGV_LOG, "utf8").split("\n");
|
|
389
|
+
const i = lines.lastIndexOf("-L");
|
|
390
|
+
expect(i).toBeGreaterThan(-1);
|
|
391
|
+
const range = lines[i + 1];
|
|
392
|
+
expect(range).toMatch(/:packages\/agent-board-schema\/package\.json$/);
|
|
393
|
+
|
|
394
|
+
// 2. does that range, under REAL git, select only the version line?
|
|
395
|
+
const dir = mkdtempSync(join(tmpdir(), "drift-Lfixture-"));
|
|
396
|
+
try {
|
|
397
|
+
fixtureRepo(dir);
|
|
398
|
+
// The fixture must really have the three commits; an empty `git log` result would
|
|
399
|
+
// otherwise satisfy `not.toBe("adjacent-line-edit")` while asserting nothing.
|
|
400
|
+
expect((git(dir, ["log", "--format=%s"]).stdout ?? "").trim().split("\n")).toEqual([
|
|
401
|
+
"adjacent-line-edit",
|
|
402
|
+
"bump",
|
|
403
|
+
"base",
|
|
404
|
+
]);
|
|
405
|
+
// The range itself contains colons (`/"version":/,+1`), so split on the LAST one.
|
|
406
|
+
const spec = `${range.slice(0, range.lastIndexOf(":"))}:package.json`;
|
|
407
|
+
const out = git(dir, ["log", "-1", "--format=%s", "-L", spec]).stdout ?? "";
|
|
408
|
+
const subject = out.split("\n")[0].trim();
|
|
409
|
+
expect(subject).toBe("bump");
|
|
410
|
+
expect(subject).not.toBe("adjacent-line-edit");
|
|
411
|
+
} finally {
|
|
412
|
+
rmSync(dir, { recursive: true, force: true });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
describe("the script under test is the one that ships", () => {
|
|
418
|
+
it("is the file CI runs", () => {
|
|
419
|
+
expect(existsSync(SCRIPT)).toBe(true);
|
|
420
|
+
expect(readFileSync(join(ROOT, ".github", "workflows", "ci.yml"), "utf8")).toContain(
|
|
421
|
+
"node scripts/check-deployment-drift.mjs",
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
});
|