@consiliency/agent-board-schema 1.64.0 → 1.68.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.
@@ -0,0 +1,146 @@
1
+ -- Consiliency/message-board#668: resolution uses the pending reader's flat authority gate.
2
+ --
3
+ -- Consiliency/message-board#665 (integration): this migration sorts after
4
+ -- 20261015000100_agent_board_session_provenance, so on every fresh apply THIS body is the one
5
+ -- that survives. It therefore carries #665's session-derived attribution as well: both sink calls
6
+ -- (the redundant-pending retirement and the resolution write) pass v_actor, never the caller's
7
+ -- p_created_by_agent_id, which is retained for signature compatibility only. Because the gate
8
+ -- below refuses an unresolved principal (including an unresolved service-role session), v_actor
9
+ -- is always a resolved owner/admin when a sink is reached.
10
+
11
+ create or replace function agent_board.resolve_board_request(
12
+ p_board_id uuid,
13
+ p_agent_id uuid,
14
+ p_membership_state text,
15
+ p_created_by_agent_id uuid,
16
+ p_metadata jsonb default '{}'::jsonb
17
+ )
18
+ returns table(membership_id uuid, board_id uuid, agent_id uuid, membership_state text, left_at timestamp with time zone, updated_at timestamp with time zone)
19
+ language plpgsql
20
+ security definer
21
+ set search_path to 'agent_board', 'public'
22
+ as $function$
23
+ declare
24
+ v_effective_state text;
25
+ v_board agent_board.boards%rowtype;
26
+ v_membership_id uuid;
27
+ v_active_id uuid;
28
+ v_membership agent_board.board_memberships%rowtype;
29
+ v_actor uuid := agent_board.current_agent_id();
30
+ begin
31
+ if v_actor is null and not agent_board._is_service_role() then
32
+ raise exception 'current agent principal not resolved' using errcode = '42501';
33
+ end if;
34
+
35
+ if p_membership_state not in ('joined', 'subscribed', 'removed') then
36
+ raise exception 'resolve_board_request only accepts joined, subscribed, or removed' using errcode = '22023';
37
+ end if;
38
+ select * into v_board from agent_board.boards where id = p_board_id and status = 'active';
39
+ if v_board.id is null then
40
+ raise exception 'board not found' using errcode = 'P0002';
41
+ end if;
42
+ if v_actor is null or not exists (
43
+ select 1 from agent_board.board_memberships bm
44
+ where bm.board_id = p_board_id
45
+ and bm.agent_id = v_actor
46
+ and bm.membership_state in ('joined', 'subscribed')
47
+ and bm.role in ('owner', 'admin')
48
+ ) then
49
+ raise exception 'board request resolution requires owner or admin role' using errcode = '42501';
50
+ end if;
51
+
52
+ -- Lock the WHOLE (board, agent) set first so a concurrent self-withdrawal on ANY physical row
53
+ -- serialises behind this resolution (#109's race is per-(board,agent), not per-row).
54
+ perform 1
55
+ from agent_board.board_memberships bm
56
+ where bm.board_id = p_board_id
57
+ and bm.agent_id = p_agent_id
58
+ order by bm.id
59
+ for update;
60
+
61
+ -- Select the PENDING row explicitly (not the total-order winner). B2 (#116): with TWO pending
62
+ -- rows (a requested AND an invited) for one subject this resolves the NEWEST; the older dangles.
63
+ -- Inherent to the multi-row model — resolution is one act on one row.
64
+ select bm.id
65
+ into v_membership_id
66
+ from agent_board.board_memberships bm
67
+ where bm.board_id = p_board_id
68
+ and bm.agent_id = p_agent_id
69
+ and bm.membership_state in ('requested', 'invited')
70
+ order by bm.created_at desc, bm.id desc
71
+ limit 1;
72
+
73
+ select bm.id
74
+ into v_active_id
75
+ from agent_board.board_memberships bm
76
+ where bm.board_id = p_board_id
77
+ and bm.agent_id = p_agent_id
78
+ and bm.membership_state in ('joined', 'subscribed')
79
+ order by bm.created_at desc, bm.id desc
80
+ limit 1;
81
+
82
+ if v_membership_id is null then
83
+ raise exception 'pending board membership not found' using errcode = 'P0002';
84
+ end if;
85
+
86
+ -- OPTION 1 FALLBACK (#109 A3): approving a subject who is ALREADY active is a no-op success,
87
+ -- not a promotion of the pending row into collision with the active one. Return the active row
88
+ -- untouched. Rejection still falls through to act on the pending row.
89
+ --
90
+ -- B4 (#116): the no-op path intentionally IGNORES the requested target state and p_metadata —
91
+ -- approving with target 'subscribed' a 'joined' active row returns 'joined' (a self-toggle
92
+ -- belongs to update_board_membership, which is where the old path coupled the metadata write to
93
+ -- a demotion). Nothing legitimate is lost.
94
+ if p_membership_state in ('joined', 'subscribed') and v_active_id is not null then
95
+ -- B1 (#116): retire the redundant pending row so it stops showing in pending listings. The
96
+ -- subject is already active via v_active_id (untouched); route the retire through the sink so
97
+ -- the write EMITS (the emission-completeness invariant requires every membership write to hit
98
+ -- the ledger) — a board-authored 'left' event, which is NEUTRAL for consent (not the subject's
99
+ -- own act) and does not affect the subject's consent-present status carried by the active row.
100
+ -- 'left' is recoverable. Previously only a reject cleared this, leaving an owner-visible
101
+ -- dangling request after a no-op approve.
102
+ perform agent_board._upsert_board_membership_state(
103
+ p_board_id, p_agent_id, 'left', v_actor, p_metadata, v_membership_id, 'noop_superseded'
104
+ );
105
+
106
+ return query
107
+ select bm.id, bm.board_id, bm.agent_id, bm.membership_state, bm.left_at, bm.updated_at
108
+ from agent_board.board_memberships bm
109
+ where bm.id = v_active_id;
110
+ return;
111
+ end if;
112
+
113
+ -- REJECTSEM (#89): a rejection is NOT an eviction — resolve only acts on a PENDING row, so a
114
+ -- refusal lands on 'left' (recoverable), never 'removed' (which bars self-re-entry). Applied to
115
+ -- both requested and invited origins.
116
+ v_effective_state := case when p_membership_state = 'removed' then 'left'
117
+ else p_membership_state end;
118
+
119
+ -- #82: promotion into an active state requires the subject's own consent act (the sole
120
+ -- consent-predicate read site). Every accepted session, including service-role operators,
121
+ -- has a resolved owner/admin principal after the authority gate above; there is no exemption.
122
+ if v_actor is not null
123
+ and v_effective_state in ('joined', 'subscribed')
124
+ and not agent_board._membership_consent_present(p_board_id, p_agent_id) then
125
+ raise exception 'admitting % requires an act by that agent', p_agent_id
126
+ using errcode = '42501';
127
+ end if;
128
+
129
+ v_membership := agent_board._upsert_board_membership_state(
130
+ p_board_id,
131
+ p_agent_id,
132
+ v_effective_state,
133
+ v_actor,
134
+ p_metadata,
135
+ v_membership_id,
136
+ case when p_membership_state = 'removed' then 'reject_withdrawn' else null end
137
+ );
138
+
139
+ return query
140
+ select v_membership.id, v_membership.board_id, v_membership.agent_id, v_membership.membership_state, v_membership.left_at, v_membership.updated_at;
141
+ end;
142
+ $function$;
143
+
144
+ revoke all on function agent_board.resolve_board_request(uuid,uuid,text,uuid,jsonb) from public, anon;
145
+ grant execute on function agent_board.resolve_board_request(uuid,uuid,text,uuid,jsonb) to authenticated, service_role;
146
+ comment on function agent_board.resolve_board_request(uuid,uuid,text,uuid,jsonb) is 'Resolves pending memberships only for a resolved active owner/admin principal, independent of role_gate_enforced and creator identity. Matches list_board_pending_memberships, including refusal of an unresolved service-role session. Consent, rejection mapping and redundant-request retirement remain in force. Membership attribution is derived from current_agent_id(); p_created_by_agent_id is retained for signature compatibility only.';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@consiliency/agent-board-schema",
3
- "version": "1.64.0",
3
+ "version": "1.68.0",
4
4
  "description": "Canonical, immutable, exact-pinnable agent_board schema bundle: ordered SQL migrations, digest + compatibility manifest, and deterministic materialize/plan/check tooling for adopters.",
5
5
  "repository": "https://github.com/Consiliency/message-board.git",
6
6
  "type": "module",
@@ -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.64.0",
5
+ "packageVersion": "1.68.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": 162,
50
- "aggregateSha256": "b6b24a1c50a1ac51cd935f6ad2091876a0b0ff98f001d776c89445a889416745",
49
+ "migrationCount": 166,
50
+ "aggregateSha256": "c59fcf6ae0ce9cf567f68a27abee5c8a3a11d0d98ebc4d5cb07a496e7832964c",
51
51
  "migrations": [
52
52
  {
53
53
  "order": 0,
@@ -1506,6 +1506,42 @@
1506
1506
  "sourceRef": "message-board",
1507
1507
  "bytes": 13141,
1508
1508
  "sha256": "2fed8fc5a2c7c0ba9acbb12872b329db3f0902d96597c52cc50e21e4682c2fff"
1509
+ },
1510
+ {
1511
+ "order": 162,
1512
+ "id": "20261015000100_agent_board_session_provenance",
1513
+ "filename": "20261015000100_agent_board_session_provenance.sql",
1514
+ "origin": "message-board-forward",
1515
+ "sourceRef": "message-board",
1516
+ "bytes": 16704,
1517
+ "sha256": "b5e3c7fc859dff7c3a0ccd4c0a7fb22d6ada1df68cb028be138035b586e1462a"
1518
+ },
1519
+ {
1520
+ "order": 163,
1521
+ "id": "20261015000200_agent_board_request_preserve_removed",
1522
+ "filename": "20261015000200_agent_board_request_preserve_removed.sql",
1523
+ "origin": "message-board-forward",
1524
+ "sourceRef": "message-board",
1525
+ "bytes": 3736,
1526
+ "sha256": "9bad7438909e9c02cc034b59af67fa962e7251bedb53d691e762e651ba1c343c"
1527
+ },
1528
+ {
1529
+ "order": 164,
1530
+ "id": "20261015000300_agent_board_rls_correlation",
1531
+ "filename": "20261015000300_agent_board_rls_correlation.sql",
1532
+ "origin": "message-board-forward",
1533
+ "sourceRef": "message-board",
1534
+ "bytes": 6348,
1535
+ "sha256": "aafe4a4b6eb1b6a5e3f0b5fbd8b4e4d395399de079c6a7b2eb2535ef4514bdc4"
1536
+ },
1537
+ {
1538
+ "order": 165,
1539
+ "id": "20261015000400_agent_board_resolution_authority",
1540
+ "filename": "20261015000400_agent_board_resolution_authority.sql",
1541
+ "origin": "message-board-forward",
1542
+ "sourceRef": "message-board",
1543
+ "bytes": 7396,
1544
+ "sha256": "bfeb7bda1eb41bc431416502db69c33156ff72acfd79cefdfd556d933f79d809"
1509
1545
  }
1510
1546
  ],
1511
1547
  "excludedBridge": {
@@ -0,0 +1,156 @@
1
+ // Run the shipped checker with REAL divergent Git history. Only npm/board I/O is stubbed.
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { spawnSync } from "node:child_process";
4
+ import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
8
+
9
+ const ROOT = fileURLToPath(new URL("../../../", import.meta.url));
10
+ const PKG = "packages/agent-board-schema";
11
+ const roots = [];
12
+ afterEach((context) => { context.task.meta.drift_assertions = expect.getState().assertionCalls; for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
13
+ const stamp = "2026-01-02T00:00:00+00:00";
14
+ function git(cwd, args, env = {}) {
15
+ const r = spawnSync("git", args, { cwd, encoding: "utf8", env: { ...process.env, ...env } });
16
+ if (r.status !== 0) throw new Error(`fixture git failed: ${r.stderr}`);
17
+ return r.stdout.trim();
18
+ }
19
+ function installChecker(dir) {
20
+ mkdirSync(join(dir, "scripts/lib"), { recursive: true });
21
+ for (const file of ["check-deployment-drift.mjs", "lib/drift-evaluate.mjs"]) {
22
+ copyFileSync(join(ROOT, "scripts", file), join(dir, "scripts", file));
23
+ }
24
+ }
25
+ function fixture() {
26
+ const root = mkdtempSync(join(tmpdir(), "drift-main-")); roots.push(root);
27
+ const dir = join(root, "checkout"); mkdirSync(join(dir, PKG), { recursive: true });
28
+ git(dir, ["init", "-q", "-b", "main"]);
29
+ const commit = (version, date, description = "original") => {
30
+ writeFileSync(join(dir, PKG, "package.json"), JSON.stringify({ name: "@consiliency/agent-board-schema", version, description }, null, 2));
31
+ writeFileSync(join(dir, PKG, "schema-manifest.json"), JSON.stringify({ schemaVersion: 8, aggregateSha256: `digest-${version}` }));
32
+ git(dir, ["add", PKG]);
33
+ git(dir, ["-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", version + description],
34
+ { GIT_AUTHOR_DATE: date, GIT_COMMITTER_DATE: date });
35
+ return git(dir, ["rev-parse", "HEAD"]);
36
+ };
37
+ commit("1.63.0", "2026-01-01T00:00:00Z");
38
+ commit("1.64.0", stamp);
39
+ const main = commit("1.64.0", "2026-01-04T00:00:00Z", "adjacent edit");
40
+ git(dir, ["switch", "-qc", "proposal"]);
41
+ const proposal = commit("1.65.0", "2026-01-05T00:00:00Z", "unmerged proposal");
42
+ // A cached tracking branch deliberately lies. CI must use the event's base SHA.
43
+ git(dir, ["update-ref", "refs/remotes/origin/main", proposal]);
44
+ // A real local remote supplies operator truth without any network/service access.
45
+ const remote = join(root, "remote.git"); git(root, ["clone", "--bare", "-q", dir, remote]);
46
+ git(dir, ["remote", "add", "origin", remote]);
47
+ installChecker(dir);
48
+ const shims = join(root, "bin"); mkdirSync(shims);
49
+ writeFileSync(join(shims, "npm"), '#!/bin/sh\nprintf "%s\\n" "$PUBLISHED"\n'); chmodSync(join(shims, "npm"), 0o755);
50
+ const preload = join(root, "no-network.mjs");
51
+ writeFileSync(preload, 'globalThis.fetch = async () => ({ ok: true, json: async () => [JSON.parse(process.env.BOARD_ROW)] });');
52
+ const eventPath = join(root, "event.json");
53
+ const event = { pull_request: { base: { ref: "main", sha: main, repo: { full_name: "Consiliency/message-board" } } } };
54
+ const run = ({ env = {}, payload = event, cwd = dir, args = [] } = {}) => {
55
+ writeFileSync(eventPath, JSON.stringify(payload));
56
+ const r = spawnSync(process.execPath, [join(cwd, "scripts/check-deployment-drift.mjs"), "--json", ...args], {
57
+ cwd, encoding: "utf8", timeout: 15_000, env: { ...process.env,
58
+ PATH: `${shims}:${process.env.PATH}`, NODE_OPTIONS: `--import ${pathToFileURL(preload).href}`,
59
+ GITHUB_ACTIONS: "true", GITHUB_EVENT_NAME: "pull_request", GITHUB_EVENT_PATH: eventPath,
60
+ GITHUB_REF: "refs/pull/1/merge", GITHUB_SHA: proposal, GITHUB_REPOSITORY: "Consiliency/message-board",
61
+ PUBLISHED: "1.64.0", SUPABASE_ACCESS_TOKEN: "disposable-canary-never-authenticates", ...env },
62
+ });
63
+ expect(r.error).toBeUndefined();
64
+ const out = r.stdout + r.stderr;
65
+ const doc = JSON.parse(r.stdout);
66
+ return { ...r, out, doc };
67
+ };
68
+ return { root, dir, remote, main, proposal, event, run };
69
+ }
70
+ function expectMain(r, main) {
71
+ expect(r.status).toBe(0);
72
+ expect(r.doc.status).toBe("ok");
73
+ expect(r.doc.facts.merged).toBe("1.64.0");
74
+ expect(r.doc.facts.mergedCommit).toBe(main);
75
+ expect(new Date(r.doc.facts.bumpIso).toISOString()).toBe(new Date(stamp).toISOString());
76
+ expect(r.doc.facts.expectedDigest).toBe("digest-1.64.0");
77
+ }
78
+ function expectUnknown(r) {
79
+ expect(r.status).toBe(2);
80
+ expect(r.doc.status).toBe("unknown");
81
+ expect(r.doc.facts.mergedSourceError).toMatch(/authoritative main\/history unavailable/);
82
+ }
83
+
84
+ describe("authoritative main, independent of the checked-out proposal", () => {
85
+ it.each(["proposal", "merge"])("PR %s: main 1.64, old unmerged 1.65, poisoned origin/main", (checkout) => {
86
+ const f = fixture();
87
+ if (checkout === "merge") {
88
+ // An actual two-parent PR-style merge, still containing the unmerged 1.65 proposal.
89
+ git(f.dir, ["switch", "-qc", "merge", f.main]);
90
+ git(f.dir, ["-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", "merge", "--no-ff", "-qm", "PR merge", "proposal"]);
91
+ }
92
+ expect(JSON.parse(readFileSync(join(f.dir, PKG, "package.json"))).version).toBe("1.65.0");
93
+ expectMain(f.run({ env: { GITHUB_SHA: git(f.dir, ["rev-parse", "HEAD"]) } }), f.main);
94
+ });
95
+ it.each(["push", "schedule", "workflow_dispatch"])("%s uses its main event SHA, with another ref checked out", (event) => {
96
+ const f = fixture();
97
+ expectMain(f.run({ env: { GITHUB_EVENT_NAME: event, GITHUB_REF: "refs/heads/main", GITHUB_SHA: f.main } }), f.main);
98
+ });
99
+ it("operator resolves live origin main despite poisoned tracking ref and proposal checkout", () => {
100
+ const f = fixture(); expectMain(f.run({ env: { GITHUB_ACTIONS: "false" } }), f.main);
101
+ });
102
+ it("a real merged publication gap still fails its age threshold", () => {
103
+ const f = fixture(); const r = f.run({ env: { PUBLISHED: "1.63.0" } });
104
+ expect(r.status).toBe(1); expect(r.doc.status).toBe("drift");
105
+ expect(new Date(r.doc.facts.bumpIso).toISOString()).toBe(new Date(stamp).toISOString());
106
+ expect(r.out).toMatch(/1 schema version\(s\) merged but NOT published/);
107
+ });
108
+ it("version threshold remains independent of the age threshold", () => {
109
+ const f = fixture(); const r = f.run({ env: { PUBLISHED: "1.60.0" }, args: ["--max-days", "10000"] });
110
+ expect(r.status).toBe(1); expect(r.out).toMatch(/4 schema version\(s\)/);
111
+ expect(f.run({ env: { PUBLISHED: "1.63.0" }, args: ["--max-days", "10000"] }).status).toBe(0);
112
+ });
113
+ it("board comparisons use main's manifest along with main's version", () => {
114
+ const f = fixture(); const row = { package_version: "1.64.0", schema_version: 8, bundle_digest: "digest-1.64.0", installed_at: "2026-01-06T00:00:00Z" };
115
+ const options = { args: ["--board", "fixture-only"], env: { BOARD_ROW: JSON.stringify(row) } };
116
+ expectMain(f.run(options), f.main);
117
+ const mismatch = f.run({ ...options, env: { BOARD_ROW: JSON.stringify({ ...row, bundle_digest: "digest-1.65.0" }) } });
118
+ expect(mismatch.status).toBe(1); expect(mismatch.out).toMatch(/digest/);
119
+ });
120
+ it.each([{}, { pull_request: { base: { ref: "proposal", sha: "a".repeat(40) } } },
121
+ { pull_request: { base: { ref: "main", sha: "a".repeat(40), repo: { full_name: "other/repo" } } } }])("missing/untrusted PR base is UNKNOWN, never HEAD fallback: %j", (payload) => {
122
+ const f = fixture(); expectUnknown(f.run({ payload }));
123
+ });
124
+ it("an unavailable authoritative commit is UNKNOWN even when HEAD could pass", () => {
125
+ const f = fixture(); f.event.pull_request.base.sha = "f".repeat(40); expectUnknown(f.run());
126
+ });
127
+ it.each(["push", "schedule", "workflow_dispatch", "unrecognized"])("%s outside main cannot fall back to checkout or cached main", (event) => {
128
+ const f = fixture(); expectUnknown(f.run({ env: { GITHUB_EVENT_NAME: event, GITHUB_REF: "refs/heads/proposal" } }));
129
+ });
130
+ it("missing event SHA is UNKNOWN", () => {
131
+ const f = fixture(); expectUnknown(f.run({ env: { GITHUB_EVENT_NAME: "push", GITHUB_REF: "refs/heads/main", GITHUB_SHA: "" } }));
132
+ });
133
+ it("unreadable PR event is UNKNOWN", () => {
134
+ const f = fixture(); expectUnknown(f.run({ env: { GITHUB_EVENT_PATH: join(f.root, "absent.json") } }));
135
+ });
136
+ it("shallow main is UNKNOWN even when its version equals published", () => {
137
+ const f = fixture(); const shallow = join(f.root, "shallow");
138
+ git(f.root, ["clone", "-q", "--depth=1", "--branch=main", pathToFileURL(f.remote).href, shallow]);
139
+ expect(git(shallow, ["rev-parse", "--is-shallow-repository"])).toBe("true");
140
+ installChecker(shallow); expectUnknown(f.run({ cwd: shallow }));
141
+ });
142
+ it("operator refuses missing live main; credential-bearing git errors are never echoed", () => {
143
+ const f = fixture(); const canary = "do-not-echo-this-credential";
144
+ git(f.dir, ["remote", "set-url", "origin", `file://${join(f.root, canary, "absent.git")}`]);
145
+ const r = f.run({ env: { GITHUB_ACTIONS: "false" } }); expectUnknown(r); expect(r.out).not.toContain(canary);
146
+ });
147
+ it("operator refuses a live main object unavailable in its checkout", () => {
148
+ const f = fixture();
149
+ // Create a remote-only descendant without fetching it into the checked-out repo.
150
+ const other = join(f.root, "writer"); git(f.root, ["clone", "-q", "--branch=main", f.remote, other]);
151
+ writeFileSync(join(other, "new.txt"), "remote-only"); git(other, ["add", "new.txt"]);
152
+ git(other, ["-c", "user.name=fixture", "-c", "user.email=fixture@example.invalid", "commit", "-qm", "remote-only"]);
153
+ git(other, ["push", "-q", "origin", "main"]);
154
+ expectUnknown(f.run({ env: { GITHUB_ACTIONS: "false" } }));
155
+ });
156
+ });
@@ -67,6 +67,8 @@ let PRELOAD;
67
67
  beforeAll(() => {
68
68
  SHIMS = mkdtempSync(join(tmpdir(), "drift-shims-"));
69
69
  ARGV_LOG = join(SHIMS, "argv.log");
70
+ writeFileSync(join(SHIMS, "package.json"), readFileSync(PKG_JSON));
71
+ writeFileSync(join(SHIMS, "manifest.json"), readFileSync(MANIFEST));
70
72
 
71
73
  // One arg per line, `--` between invocations, so an assertion can read exact argv rather
72
74
  // than a space-joined approximation.
@@ -78,7 +80,7 @@ beforeAll(() => {
78
80
  );
79
81
  writeFileSync(
80
82
  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`,
83
+ `#!/bin/sh\n${logArgv}\ncase " $* " in\n *" --is-shallow-repository "*) printf "%s\n" "\${FAKE_SHALLOW:-false}" ;;\n *" cat-file "*) exit 0 ;;\n *" show "*"/package.json "*) cat "$DRIFT_SHIMS/package.json" ;;\n *" show "*"/schema-manifest.json "*) cat "$DRIFT_SHIMS/manifest.json" ;;\n *" log "*) [ -n "\${FAKE_BUMP_ISO:-}" ] || exit 1; printf "%s\n" "$FAKE_BUMP_ISO" ;;\n *) exit 1 ;;\nesac\n`,
82
84
  );
83
85
  chmodSync(join(SHIMS, "npm"), 0o755);
84
86
  chmodSync(join(SHIMS, "git"), 0o755);
@@ -139,6 +141,11 @@ function runScript(args = [], env = {}) {
139
141
  PATH: `${SHIMS}:${process.env.PATH}`,
140
142
  NODE_OPTIONS: `--import ${pathToFileURL(PRELOAD).href}`,
141
143
  DRIFT_ARGV_LOG: ARGV_LOG,
144
+ DRIFT_SHIMS: SHIMS,
145
+ GITHUB_ACTIONS: "true",
146
+ GITHUB_EVENT_NAME: "push",
147
+ GITHUB_REF: "refs/heads/main",
148
+ GITHUB_SHA: "a".repeat(40),
142
149
  FAKE_BUMP_ISO: FRESH_BUMP,
143
150
  FAKE_SHALLOW: "false",
144
151
  FAKE_FETCH_MODE: "",
@@ -186,6 +193,12 @@ describe("the shipped script's exit mapping", () => {
186
193
  expect(r.status).toBe(2);
187
194
  });
188
195
 
196
+ it("missing bump history is UNKNOWN even when merged and published agree", () => {
197
+ const r = runScript([], { FAKE_NPM_VERSION: MERGED, FAKE_BUMP_ISO: "" });
198
+ expect(r.status).toBe(2);
199
+ expect(r.out).toMatch(/authoritative main\/history unavailable/);
200
+ });
201
+
189
202
  it("refuses to date the bump on a shallow clone, so an unpublished gap is UNKNOWN", () => {
190
203
  // FAKE_SHALLOW was never true in any test, so the shallow-clone refusal the previous
191
204
  // round added was driven by nothing. Making isShallowClone() return false
@@ -0,0 +1,17 @@
1
+ import { expect, it } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+
4
+ const comment = (file) => {
5
+ const sql = readFileSync(new URL('../migrations/' + file, import.meta.url), 'utf8');
6
+ const statement = sql.match(/^comment on function agent_board\._current_agent_can_access_thread\(uuid\) is\s*((?:'(?:''|[^'])*'\s*)+);/im);
7
+ if (!statement) throw new Error('thread-access comment missing in ' + file);
8
+ return [...statement[1].matchAll(/'((?:''|[^'])*)'/g)].map((part) => part[1].replaceAll("''", "'")).join('');
9
+ };
10
+
11
+ it('RLS-comment-ruling: authenticated grants preserve the complete ratified thread-access comment', () => {
12
+ const ruling = comment('20260914000100_agent_board_claim_subject_gate.sql');
13
+ expect(ruling).toContain('message-board#291');
14
+ expect(ruling).toContain('REVERSING A RULING');
15
+ expect(comment('20261015000300_agent_board_rls_correlation.sql')).toBe(
16
+ ruling + ' Callable by authenticated for its RLS policies and by service_role.');
17
+ });
@@ -41,9 +41,9 @@ describe('manifest integrity', () => {
41
41
  expect(manifest.schemaVersion).toBe(8);
42
42
  expect(manifest.minClientVersion).toMatch(/^\d+\.\d+\.\d+$/);
43
43
  });
44
- it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST + one ONBOARD + one HOSTED + one HOSTED-FOLLOWUPS + one OWNPOST + one SIDSCHEMA + one SIDOWNER + one GRANTTARGETROLE (#674) + one LASTOWNER (Consiliency/message-board#675) forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 162)', () => {
44
+ it('registers the four MBAUTHZ + four MBSENDAUTHZ + four MBSESSION + four MBDELEG + three MBFED + three MBHARDEN + two MBPORTALGATE + two MBPORTAL-PR32 + two ROOMOWN + two ROOMLINK + one ROOMDELEG + one LOCALAUTH + one ROOMUIAPI + one FEDSESSION + one FEDSESSION-AUTOJOIN + one GATENULL + one MEMBSTATE + one AGENTSDIR + one LEAVEIDEM + one ROLERESET + one MBROWID + one MBHYGIENE + one MBCONSENT-A + one MBCONSENT-B(a) + one MBCONSENT-B(i) + one MBCONSENT-B(ii) + one MBCONSENT-B(iii) + one MBHARDEN-#97 + one MBHARDEN-#118 + one INVITEPOLICY + one OBSGATE + one OBSGATE-2 + one OBSGATE-3 + one OBSGATE-4 + one FLEETMACHINERLS + one RESOLVENOOP + one ROOMDELEG-LANEB + one CLAIMKEY + two MINTHARDEN + one FLEETMACHINEHIJACK + one CLAIMGATE-SHADOW + one CLAIMGATE-ENFORCE + one FLEETMACHINEREACTIVATION + one DELEGCAPCEILING + one FLEETMACHINERECLAIM + one DELEGSOLEWRITER + one FLEETDOMAINRECLAIM + five ATOMICFLEETONBOARDING (onboarding + ceiling-lock + revocation-evidence + fedsession-gate + fedsession-lock-order) + one OUTBOXWAKE + one ENROLLLOCKORDER + one FEDPROVIDERABSENCE + one WAKERECOVERY + one CONSENTRECONCILE + one ENROLLRESUMPTION + one SIGNEDAUTHORITY + one SIGNERLIFECYCLE + one LEDGERAUTHORITY + one CEILINGLOCKCOMMENT + one LEDGERBOARDCREATE + one RECEIPTSUBJECTGATE + one TASKTHREADLOCKORDER + one CLAIMSUBJECTGATE + one FEDSESSIONIDENTITYGATE + one FEDIDENTITYSLUG + one INVITENODOWNGRADE + one SIGNEDWRITEFLAG + one SIGNEDWRITEMODEL + one RTCREDCLAIM + one ORIGINPOSTURE + one BOARDCLAIMPOLICY + one HEARTBEATPRESERVE + one LIVENESSDENOM + one OWNERLESSRENEWTTL + one OWNERLESSQUOTACENSUS + one FLEETDOMAINNODOWNGRADE + one OWNERLESSQUOTACONVERGE + one ADVISORYREVERTRESET + one CLAIMPOLICYRACE + one CLAIMPOLICYLOCKGRANT + one SESSIONCHALLENGESUPERSEDE + one ROOMCONTRACT + one CALLBACKCONSUMER + one ENROLLTOKENSINGLEUSE + one ROOMCONTRACT-ACTIVEFIRST + one ONBOARD + one HOSTED + one HOSTED-FOLLOWUPS + one OWNPOST + one SIDSCHEMA + one SIDOWNER + one GRANTTARGETROLE (#674) + one LASTOWNER (Consiliency/message-board#675) + one SESSIONPROVENANCE (Consiliency/message-board#665) + one REQUESTREMOVED (Consiliency/message-board#680) + one RLSCORRELATION (Consiliency/message-board#589) + one RESOLUTIONAUTHORITY (Consiliency/message-board#668) forward migrations (migrationCount = prior 113 + 5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 166)', () => {
45
45
  expect(manifest.migrationCount).toBe(manifest.migrations.length);
46
- expect(manifest.migrationCount).toBe(162);
46
+ expect(manifest.migrationCount).toBe(166);
47
47
  // MBSESSION switched enforcement on (scoped-send re-grant): the client-visible
48
48
  // schema version bumped to 8. MBDELEG tightens delegation and MBFED adds only
49
49
  // enrollment-only additive surface (identity_providers registry, enrollment audit,
@@ -364,6 +364,10 @@ describe('manifest integrity', () => {
364
364
  // #674: grant_board_role checks the target's current role (C-A + C-B).
365
365
  '20261014000100_agent_board_grant_target_role.sql',
366
366
  '20261014000200_agent_board_last_owner.sql',
367
+ '20261015000100_agent_board_session_provenance.sql',
368
+ '20261015000200_agent_board_request_preserve_removed.sql',
369
+ '20261015000300_agent_board_rls_correlation.sql',
370
+ '20261015000400_agent_board_resolution_authority.sql',
367
371
  ]) {
368
372
  expect(names.has(f)).toBe(true);
369
373
  }
@@ -202,7 +202,7 @@ select is(pg_temp.state('c2000000-0000-4000-8000-000000000001','c1000000-0000-40
202
202
  'joined', 'EC-2 join_board: re-entry after leaving still works');
203
203
 
204
204
  -- ===========================================================================
205
- -- EC-3 OPERATOR -- the carve-out is preserved on every route
205
+ -- EC-3 OPERATOR -- direct placement remains; resolution requires owner/admin identity
206
206
  -- ===========================================================================
207
207
  select pg_temp.service();
208
208
  select lives_ok($$
@@ -215,13 +215,13 @@ select is(
215
215
  (select provenance from agent_board.board_membership_events
216
216
  where agent_id = 'c1000000-0000-4000-8000-000000000006' order by seq desc limit 1),
217
217
  'operator-designated', 'EC-3: ...recorded as an operator placement, never as consent');
218
- select lives_ok($$
218
+ select throws_ok($$
219
219
  select agent_board.resolve_board_request(
220
220
  'c2000000-0000-4000-8000-000000000001'::uuid,'c1000000-0000-4000-8000-000000000003'::uuid,
221
221
  'joined'::text, null::uuid, '{}'::jsonb) $$,
222
- 'EC-3: service_role may resolve a pending row the gate refuses to an owner [call succeeds]');
222
+ '42501', null, 'EC-3: unresolved service_role cannot resolve pending membership');
223
223
  select is(pg_temp.state('c2000000-0000-4000-8000-000000000001','c1000000-0000-4000-8000-000000000003'),
224
- 'joined', 'EC-3: service_role may resolve a pending row the gate refuses to an owner');
224
+ 'requested', 'EC-3: refused service_role resolution preserves the pending row');
225
225
 
226
226
  -- ===========================================================================
227
227
  -- EC-6 / EC-7 -- consent is EXERCISABLE (#108), and the widening admits nobody else
@@ -23,7 +23,7 @@ set search_path = extensions, public, agent_board;
23
23
 
24
24
  begin;
25
25
 
26
- select plan(27);
26
+ select plan(28);
27
27
 
28
28
  -- ---------------------------------------------------------------------------
29
29
  -- Phase 0: exactly one pg_proc row per name (a changed arg list would OVERLOAD, not replace)
@@ -340,13 +340,16 @@ select set_config('request.jwt.claim.role', 'authenticated', true);
340
340
  select throws_ok(
341
341
  $$ select agent_board.resolve_board_request((select board from _f), (select nopend from _f3), 'joined', (select owner_agent from _f)) $$,
342
342
  'P0002', null, 'NULLFIX: cannot resolve an agent with no pending request straight to joined');
343
- -- 27 — resolving a GENUINE request still works (service path).
343
+ -- Consiliency/message-board#668: resolution requires a resolved owner/admin identity.
344
344
  reset role;
345
345
  select set_config('request.jwt.claim.sub', '', true);
346
346
  select set_config('request.jwt.claim.role', '', true);
347
- select lives_ok(
347
+ select throws_ok(
348
348
  $$ select agent_board.resolve_board_request((select board from _f), (select victim from _f), 'joined', (select owner_agent from _f)) $$,
349
- 'NO-REGRESSION: a genuine invited/requested membership still resolves');
349
+ '42501', null, 'unresolved service_role cannot resolve a pending membership');
350
+ select is((select membership_state from agent_board.board_memberships
351
+ where board_id=(select board from _f) and agent_id=(select victim from _f)),
352
+ 'invited', 'service-role refusal preserves the pending membership');
350
353
 
351
354
  select * from finish();
352
355
  rollback;