Original value: the 12-arg wrapper expected definer=true. New value: definer is true for the six historical signatures and false for the 12-arg wrapper. Reason: a definer wrapper runs as schema_owner, which the ACL reconciliation leaves without EXECUTE on the 11-arg open, so homepage/new opens hit 42501. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
553 lines
28 KiB
TypeScript
553 lines
28 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { spawnSync } from "node:child_process";
|
||
import { fileURLToPath } from "node:url";
|
||
import { readFileSync } from "node:fs";
|
||
import test from "node:test";
|
||
|
||
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
||
|
||
const runnerPath = fileURLToPath(
|
||
new URL("../scripts/db-migrate.mjs", import.meta.url),
|
||
);
|
||
const migrationSql = readFileSync(
|
||
new URL("../supabase/migrations/20260814010000_immutable_skill_registry.sql", import.meta.url),
|
||
"utf8",
|
||
);
|
||
|
||
function dockerAvailable(): boolean {
|
||
return spawnSync("docker", ["version", "--format", "{{.Server.Version}}"], {
|
||
encoding: "utf8",
|
||
stdio: "ignore",
|
||
}).status === 0;
|
||
}
|
||
|
||
const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on this host";
|
||
|
||
const ids = {
|
||
user: "66666666-6666-4666-8666-666666666666",
|
||
otherUser: "77777777-7777-4777-8777-777777777777",
|
||
cascadeUser: "88888888-8888-4888-8888-888888888888",
|
||
legacySession: "22222222-2222-4222-8222-222222222222",
|
||
legacyCase: "11111111-1111-4111-8111-111111111111",
|
||
legacyTurn: "33333333-3333-4333-8333-333333333333",
|
||
legacyEvidence: "55555555-5555-4555-8555-555555555555",
|
||
legacyResult: "99999999-9999-4999-8999-999999999999",
|
||
terminalSession: "22222222-2222-4222-8222-222222222223",
|
||
terminalCase: "11111111-1111-4111-8111-111111111112",
|
||
verifiedTurn: "33333333-3333-4333-8333-333333333334",
|
||
verifiedRequest: "44444444-4444-4444-8444-444444444444",
|
||
cascadeSession: "22222222-2222-4222-8222-222222222224",
|
||
cascadeCase: "11111111-1111-4111-8111-111111111113",
|
||
cascadeTurn: "33333333-3333-4333-8333-333333333335",
|
||
cascadeRequest: "44444444-4444-4444-8444-444444444445",
|
||
caseDeleteSession: "22222222-2222-4222-8222-222222222225",
|
||
caseDeleteCase: "11111111-1111-4111-8111-111111111114",
|
||
caseDeleteTurn: "33333333-3333-4333-8333-333333333336",
|
||
caseDeleteRequest: "44444444-4444-4444-8444-444444444446",
|
||
};
|
||
const oldHash = "a".repeat(64);
|
||
const newHash = "b".repeat(64);
|
||
const adoptionHash = "c".repeat(64);
|
||
const sourceCommit = "0fd111d16b45796086a6c1d0945dbd3de6755d8a";
|
||
|
||
function sqlLiteral(value: string): string {
|
||
return `'${value.replaceAll("'", "''")}'`;
|
||
}
|
||
|
||
test("immutable Skill migration enforces RPC-only identity, legacy adoption, and cascade-safe receipts", { skip: skipWithoutDocker }, () => {
|
||
const fixture = startPostgresFixture();
|
||
const migrate = () =>
|
||
spawnSync(process.execPath, [runnerPath], {
|
||
encoding: "utf8",
|
||
env: {
|
||
...process.env,
|
||
SCHEMA_DATABASE_URL: fixture.connectionUrl(
|
||
"schema_owner",
|
||
"schema-owner-test-password",
|
||
),
|
||
},
|
||
});
|
||
const serviceSql = (statement: string) =>
|
||
fixture
|
||
.psqlAs(
|
||
"service_runtime",
|
||
"service-runtime-test-password",
|
||
`set role service_role; ${statement}`,
|
||
)
|
||
.replace(/^SET\n/, "");
|
||
const expectServiceError = (statement: string, pattern: RegExp) => {
|
||
assert.throws(() => serviceSql(statement), pattern);
|
||
};
|
||
const expectOwnerError = (statement: string, pattern: RegExp) => {
|
||
assert.throws(
|
||
() => fixture.psqlAs(
|
||
"schema_owner",
|
||
"schema-owner-test-password",
|
||
statement,
|
||
),
|
||
pattern,
|
||
);
|
||
};
|
||
const insertUser = (id: string, email: string) => fixture.psqlAs(
|
||
"identity_runtime",
|
||
"identity-runtime-test-password",
|
||
`insert into identity.users (id, name, email, email_verified, email_verified_at)
|
||
values (${sqlLiteral(id)}, 'Skill Registry User', ${sqlLiteral(email)}, true, now());`,
|
||
);
|
||
const insertSession = (id: string, userId: string) => fixture.psql(`
|
||
insert into public.chat_sessions (id, user_id, title, theme, session_type, messages)
|
||
values (${sqlLiteral(id)}, ${sqlLiteral(userId)}, '生时校正', 'general',
|
||
'birth_time_rectification', '[]'::jsonb);
|
||
`);
|
||
const insertCase = (input: {
|
||
id: string;
|
||
userId: string;
|
||
sessionId: string;
|
||
status?: "draft" | "closed";
|
||
hash?: string | null;
|
||
}) => fixture.psql(`
|
||
insert into public.agentic_rectification_cases (
|
||
id, user_id, session_id, status, skill_name, skill_version,
|
||
skill_sha256, skill_source_commit, baseline_profile_fingerprint,
|
||
baseline_birth_snapshot, candidate_range, completed_at
|
||
) values (
|
||
${sqlLiteral(input.id)}, ${sqlLiteral(input.userId)}, ${sqlLiteral(input.sessionId)},
|
||
${sqlLiteral(input.status ?? "draft")}, 'jyotish-birth-time-rectification', '9.0.0',
|
||
${input.hash ? sqlLiteral(input.hash) : "null"},
|
||
${input.hash ? sqlLiteral(sourceCommit) : "null"}, ${sqlLiteral(oldHash)},
|
||
'{"birth_date":"1997-08-08"}'::jsonb,
|
||
'{"start_time":"04:50","end_time":"05:10"}'::jsonb,
|
||
${input.status === "closed" ? "now()" : "null"}
|
||
);
|
||
`);
|
||
|
||
try {
|
||
fixture.psql(`create role historical_runtime_probe noinherit;`);
|
||
fixture.psqlAs(
|
||
"schema_owner",
|
||
"schema-owner-test-password",
|
||
`create or replace function public.open_agentic_rectification_case_v2(
|
||
uuid, uuid, text, uuid, text, text, text, text, text, jsonb, jsonb
|
||
) returns jsonb language sql as $$ select '{}'::jsonb $$;
|
||
grant execute on function public.open_agentic_rectification_case_v2(
|
||
uuid, uuid, text, uuid, text, text, text, text, text, jsonb, jsonb
|
||
) to historical_runtime_probe, service_runtime;`,
|
||
);
|
||
|
||
const first = migrate();
|
||
assert.equal(first.status, 0, `${first.stdout}${first.stderr}`);
|
||
assert.match(first.stdout, /applied 20260814010000_immutable_skill_registry\.sql/);
|
||
|
||
const oldSignatures = [
|
||
"public.open_agentic_rectification_case_v2(uuid,uuid,text,uuid,text,text,text,text,text,jsonb,jsonb)",
|
||
"public.get_agentic_rectification_skill_identity(uuid,uuid)",
|
||
"public.get_agentic_rectification_skill_identity_status(uuid,uuid)",
|
||
"public.upgrade_agentic_rectification_skill_v2(uuid,uuid,text,text,text,text)",
|
||
"public.adopt_agentic_rectification_skill_v1(uuid,uuid,text,text,text,text)",
|
||
"public.insert_agentic_rectification_skill_run_receipt(uuid,uuid,uuid,uuid,text,text,text,text,text)",
|
||
];
|
||
const segmentOpenSignature = "public.open_agentic_rectification_case_v2(uuid,uuid,text,uuid,text,text,text,text,text,jsonb,jsonb,text)";
|
||
const assertExactAcls = () => {
|
||
for (const signature of [...oldSignatures, segmentOpenSignature]) {
|
||
const row = JSON.parse(fixture.psql(`
|
||
select jsonb_build_object(
|
||
'owner', pg_catalog.pg_get_userbyid(p.proowner),
|
||
'definer', p.prosecdef,
|
||
'config', p.proconfig,
|
||
'owner_execute', has_function_privilege(p.proowner,p.oid,'EXECUTE'),
|
||
'legacy', has_function_privilege('historical_runtime_probe',p.oid,'EXECUTE'),
|
||
'service', has_function_privilege('service_role',p.oid,'EXECUTE'),
|
||
'anon', has_function_privilege('anon',p.oid,'EXECUTE'),
|
||
'authenticated', has_function_privilege('authenticated',p.oid,'EXECUTE'),
|
||
'public', exists(select 1 from pg_catalog.aclexplode(p.proacl) a where a.grantee=0 and a.privilege_type='EXECUTE'),
|
||
'runtime_direct', exists(select 1 from pg_catalog.aclexplode(p.proacl) a join pg_catalog.pg_roles r on r.oid=a.grantee
|
||
where r.rolname='service_runtime' and a.privilege_type='EXECUTE')
|
||
) from pg_catalog.pg_proc p where p.oid='${signature}'::regprocedure;
|
||
`));
|
||
// 原值:12 参数包装 definer: true;新值:12 参数包装 definer: false,旧 6 签名仍为 true;
|
||
// 原因:BUG-1131——definer 包装以属主身份调用只授权 service_role 的 11 参数开案,首页/新建开案 42501。
|
||
assert.deepEqual(row, { owner: "schema_owner", definer: signature !== segmentOpenSignature, config: ['search_path=""'], owner_execute: signature === segmentOpenSignature,
|
||
legacy: false, service: true, anon: false, authenticated: false, public: false, runtime_direct: false }, signature);
|
||
}
|
||
};
|
||
// 原值:按proname串联6项f:t:f:f:f;新值:旧6精确签名逐角色不变+新12参数签名独立检查。
|
||
// 原因:overload增加同名函数,不能以任意count替代或遗漏owner/PUBLIC/anon/auth/历史角色权限。
|
||
assertExactAcls();
|
||
|
||
fixture.psql(`
|
||
grant execute on function public.open_agentic_rectification_case_v2(
|
||
uuid, uuid, text, uuid, text, text, text, text, text, jsonb, jsonb
|
||
) to historical_runtime_probe, service_runtime;
|
||
grant execute on function public.get_agentic_rectification_skill_identity(uuid, uuid)
|
||
to historical_runtime_probe, service_runtime;
|
||
grant execute on function public.get_agentic_rectification_skill_identity_status(uuid, uuid)
|
||
to historical_runtime_probe, service_runtime;
|
||
grant execute on function public.upgrade_agentic_rectification_skill_v2(
|
||
uuid, uuid, text, text, text, text
|
||
) to historical_runtime_probe, service_runtime;
|
||
grant execute on function public.adopt_agentic_rectification_skill_v1(
|
||
uuid, uuid, text, text, text, text
|
||
) to historical_runtime_probe, service_runtime;
|
||
grant execute on function public.insert_agentic_rectification_skill_run_receipt(
|
||
uuid, uuid, uuid, uuid, text, text, text, text, text
|
||
) to historical_runtime_probe, service_runtime;
|
||
`);
|
||
fixture.psqlAs(
|
||
"schema_owner",
|
||
"schema-owner-test-password",
|
||
migrationSql,
|
||
);
|
||
// 原值:reconciliation后同名6项串;新值:复用精确签名与逐角色断言;原因:不把新overload混入旧身份。
|
||
assertExactAcls();
|
||
assert.equal(
|
||
fixture.psql("select pg_has_role('service_runtime', 'service_role', 'MEMBER');"),
|
||
"t",
|
||
);
|
||
// 原值:同名6项t串;新值:旧6+新overload逐签名runtime检查;原因:保留全部身份与SET ROLE生产边界。
|
||
for (const signature of [...oldSignatures, segmentOpenSignature]) {
|
||
assert.equal(serviceSql(`select has_function_privilege(current_user,'${signature}'::regprocedure,'EXECUTE');`), "t", signature);
|
||
}
|
||
|
||
const second = migrate();
|
||
assert.equal(second.status, 0, `${second.stdout}${second.stderr}`);
|
||
assert.match(second.stdout, /already applied 20260814010000_immutable_skill_registry\.sql/);
|
||
|
||
assert.equal(
|
||
fixture.psql(`
|
||
select concat_ws(':',
|
||
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'SELECT'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'INSERT'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'UPDATE'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'DELETE'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_cases', 'TRUNCATE'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_skill_upgrade_receipts', 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE'),
|
||
has_table_privilege('service_role', 'public.agentic_rectification_skill_run_receipts', 'SELECT,INSERT,UPDATE,DELETE,TRUNCATE')
|
||
)
|
||
`),
|
||
"f:f:f:f:f:f:f",
|
||
);
|
||
|
||
const catalog = fixture.psql(`
|
||
select p.proname || ':' || pg_catalog.pg_get_userbyid(p.proowner) || ':' ||
|
||
p.prosecdef || ':' || coalesce(array_to_string(p.proconfig, ','), '') || ':' ||
|
||
has_function_privilege('service_role', p.oid, 'EXECUTE') || ':' ||
|
||
has_function_privilege('anon', p.oid, 'EXECUTE') || ':' ||
|
||
has_function_privilege('authenticated', p.oid, 'EXECUTE')
|
||
from pg_catalog.pg_proc p
|
||
join pg_catalog.pg_namespace n on n.oid = p.pronamespace
|
||
-- 原值:按6个proname含所有overload;新值:仅旧6个精确签名。
|
||
-- 原因:旧catalog数量/配置保持6不变,新12参数由assertExactAcls单独验证。
|
||
where n.nspname = 'public' and p.oid in (
|
||
${oldSignatures.map(signature => `'${signature}'::regprocedure`).join(",\n ")}
|
||
)
|
||
order by p.proname;
|
||
`);
|
||
for (const line of catalog.split("\n")) {
|
||
assert.match(line, /^[^:]+:schema_owner:true:search_path="":true:f:f$/);
|
||
}
|
||
assert.equal(catalog.split("\n").length, 6);
|
||
assert.equal(
|
||
fixture.psql(`
|
||
select string_agg(c.relname || ':' || pg_catalog.pg_get_userbyid(c.relowner), ',' order by c.relname)
|
||
from pg_catalog.pg_class c
|
||
join pg_catalog.pg_namespace n on n.oid = c.relnamespace
|
||
where n.nspname = 'public' and c.relname in (
|
||
'agentic_rectification_cases',
|
||
'agentic_rectification_skill_upgrade_receipts',
|
||
'agentic_rectification_skill_run_receipts'
|
||
);
|
||
`),
|
||
"agentic_rectification_cases:schema_owner,agentic_rectification_skill_run_receipts:schema_owner,agentic_rectification_skill_upgrade_receipts:schema_owner",
|
||
);
|
||
|
||
insertUser(ids.user, "skill-registry@example.com");
|
||
insertUser(ids.otherUser, "skill-registry-other@example.com");
|
||
insertUser(ids.cascadeUser, "skill-registry-cascade@example.com");
|
||
|
||
const openedCaseId = serviceSql(`
|
||
select public.open_agentic_rectification_case_v2(
|
||
${sqlLiteral(ids.user)}::uuid, gen_random_uuid(), 'homepage', null,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(oldHash)},
|
||
${sqlLiteral(sourceCommit)}, ${sqlLiteral(oldHash)},
|
||
'{"birth_date":"1997-08-08","latitude":25.04,"longitude":121.56,"timezone_offset":8,"birth_time_source":"family_exact"}'::jsonb,
|
||
'{"start_time":"04:50","end_time":"05:10"}'::jsonb
|
||
)->>'case_id';
|
||
`);
|
||
assert.match(openedCaseId, /^[0-9a-f-]{36}$/);
|
||
assert.equal(
|
||
fixture.psql(`select skill_version || ':' || skill_sha256 from public.agentic_rectification_cases where id = ${sqlLiteral(openedCaseId)}`),
|
||
`9.0.0:${oldHash}`,
|
||
);
|
||
expectServiceError(
|
||
`insert into public.agentic_rectification_cases (
|
||
user_id, session_id, status, skill_name, skill_version,
|
||
baseline_profile_fingerprint, baseline_birth_snapshot, candidate_range
|
||
) values (${sqlLiteral(ids.user)}, gen_random_uuid(), 'draft',
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(oldHash)}, '{}'::jsonb, '{}'::jsonb);`,
|
||
/permission denied for table agentic_rectification_cases/,
|
||
);
|
||
expectServiceError(
|
||
`update public.agentic_rectification_cases set skill_sha256 = ${sqlLiteral(newHash)} where id = ${sqlLiteral(openedCaseId)};`,
|
||
/permission denied for table agentic_rectification_cases/,
|
||
);
|
||
|
||
fixture.psql(`
|
||
insert into public.agentic_rectification_turns (id, case_id, user_message, status, model_name)
|
||
values (${sqlLiteral(ids.verifiedTurn)}, ${sqlLiteral(openedCaseId)}, 'verified run', 'pending', 'fixture-model');
|
||
`);
|
||
assert.equal(
|
||
serviceSql(`select public.insert_agentic_rectification_skill_run_receipt(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(openedCaseId)}::uuid,
|
||
${sqlLiteral(ids.verifiedTurn)}::uuid, ${sqlLiteral(ids.verifiedRequest)}::uuid,
|
||
'turn', 'jyotish-birth-time-rectification', '9.0.0',
|
||
${sqlLiteral(oldHash)}, ${sqlLiteral(sourceCommit)}
|
||
)->>'skill_sha256';`),
|
||
oldHash,
|
||
);
|
||
assert.equal(
|
||
serviceSql(`select public.upgrade_agentic_rectification_skill_v2(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(openedCaseId)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.1.0', ${sqlLiteral(newHash)}, null
|
||
)->>'skill_sha256';`),
|
||
newHash,
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select previous_identity_status || ':' || upgrade_kind || ':' || previous_skill_sha256 || ':' || skill_sha256
|
||
from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(openedCaseId)}`),
|
||
`verified:version_upgrade:${oldHash}:${newHash}`,
|
||
);
|
||
assert.equal(
|
||
serviceSql(`select public.insert_agentic_rectification_skill_run_receipt(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(openedCaseId)}::uuid,
|
||
${sqlLiteral(ids.verifiedTurn)}::uuid, gen_random_uuid()::uuid,
|
||
'regeneration', 'jyotish-birth-time-rectification', '9.1.0',
|
||
${sqlLiteral(newHash)}, null
|
||
)->>'run_kind';`),
|
||
'regeneration',
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select count(*) from public.agentic_rectification_skill_run_receipts where turn_id = ${sqlLiteral(ids.verifiedTurn)}`),
|
||
'2',
|
||
);
|
||
expectServiceError(
|
||
`select public.adopt_agentic_rectification_skill_v1(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(openedCaseId)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`,
|
||
/agentic_rectification_skill_identity_already_verified/,
|
||
);
|
||
|
||
insertSession(ids.legacySession, ids.user);
|
||
insertCase({ id: ids.legacyCase, userId: ids.user, sessionId: ids.legacySession, hash: null });
|
||
fixture.psql(`
|
||
insert into public.agentic_rectification_turns (id, case_id, user_message, status, model_name)
|
||
values (${sqlLiteral(ids.legacyTurn)}, ${sqlLiteral(ids.legacyCase)}, 'legacy evidence', 'pending', 'fixture-model');
|
||
insert into public.agentic_rectification_evidence (
|
||
id, case_id, source_turn_id, user_quote, subject, event_kind, domain, date_precision, summary
|
||
) values (
|
||
${sqlLiteral(ids.legacyEvidence)}, ${sqlLiteral(ids.legacyCase)}, ${sqlLiteral(ids.legacyTurn)},
|
||
'legacy evidence', 'self', 'career_entry', 'career', 'year', 'legacy evidence summary'
|
||
);
|
||
insert into public.agentic_rectification_results (
|
||
id, user_id, session_id, engine_result_id, canonical_input_hash, algorithm_version,
|
||
candidate_range, candidates, overall_confidence, selection_allowed, confirmation_allowed,
|
||
baseline_birth_date, baseline_latitude, baseline_longitude, baseline_timezone_offset,
|
||
case_id, skill_version
|
||
) values (
|
||
${sqlLiteral(ids.legacyResult)}, ${sqlLiteral(ids.user)}, ${sqlLiteral(ids.legacySession)},
|
||
'legacy-engine-result', ${sqlLiteral(oldHash)}, 'legacy-algorithm',
|
||
'{"start_time":"04:50","end_time":"05:10"}'::jsonb, '[]'::jsonb,
|
||
'low', false, false, '1997-08-08', 25.04, 121.56, 8,
|
||
${sqlLiteral(ids.legacyCase)}, '9.0.0'
|
||
);
|
||
`);
|
||
assert.equal(
|
||
serviceSql(`select
|
||
(public.get_agentic_rectification_skill_identity_status(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid
|
||
)->>'skill_identity_status') || ':' ||
|
||
(public.get_agentic_rectification_skill_identity_status(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid
|
||
)->>'requires_skill_adoption');`),
|
||
"legacy_unverifiable:true",
|
||
);
|
||
expectServiceError(
|
||
`select public.get_agentic_rectification_skill_identity(${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid);`,
|
||
/agentic_rectification_legacy_skill_identity_unverifiable/,
|
||
);
|
||
expectServiceError(
|
||
`select public.insert_agentic_rectification_skill_run_receipt(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid,
|
||
${sqlLiteral(ids.legacyTurn)}::uuid, gen_random_uuid(), 'turn',
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`,
|
||
/agentic_rectification_skill_identity_mismatch/,
|
||
);
|
||
expectServiceError(
|
||
`select public.adopt_agentic_rectification_skill_v1(
|
||
${sqlLiteral(ids.otherUser)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`,
|
||
/agentic_rectification_case_not_found/,
|
||
);
|
||
|
||
insertSession(ids.terminalSession, ids.user);
|
||
insertCase({ id: ids.terminalCase, userId: ids.user, sessionId: ids.terminalSession, status: "closed", hash: null });
|
||
assert.equal(
|
||
serviceSql(`select
|
||
(public.get_agentic_rectification_skill_identity_status(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.terminalCase)}::uuid
|
||
)->>'skill_identity_status') || ':' ||
|
||
(public.get_agentic_rectification_skill_identity_status(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.terminalCase)}::uuid
|
||
)->>'requires_skill_adoption');`),
|
||
"legacy_unverifiable:false",
|
||
);
|
||
expectServiceError(
|
||
`select public.adopt_agentic_rectification_skill_v1(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.terminalCase)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`,
|
||
/agentic_rectification_case_terminal/,
|
||
);
|
||
|
||
const adopted = serviceSql(`select public.adopt_agentic_rectification_skill_v1(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`);
|
||
assert.equal(JSON.parse(adopted).idempotent, false);
|
||
const replayed = serviceSql(`select public.adopt_agentic_rectification_skill_v1(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.legacyCase)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`);
|
||
assert.equal(JSON.parse(replayed).idempotent, true);
|
||
assert.equal(
|
||
fixture.psql(`select coalesce(previous_skill_sha256, 'NULL') || ':' || previous_identity_status || ':' || upgrade_kind || ':' || skill_sha256
|
||
from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(ids.legacyCase)}`),
|
||
`NULL:legacy_unverifiable:legacy_adoption:${adoptionHash}`,
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select count(*) from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(ids.legacyCase)}`),
|
||
"1",
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select concat_ws(':',
|
||
(select count(*) from public.agentic_rectification_turns where case_id = ${sqlLiteral(ids.legacyCase)}),
|
||
(select count(*) from public.agentic_rectification_evidence where case_id = ${sqlLiteral(ids.legacyCase)}),
|
||
(select count(*) from public.agentic_rectification_results where case_id = ${sqlLiteral(ids.legacyCase)})
|
||
)`),
|
||
"1:1:1",
|
||
);
|
||
|
||
expectServiceError(
|
||
`insert into public.agentic_rectification_skill_upgrade_receipts (
|
||
case_id, user_id, previous_skill_name, previous_skill_version,
|
||
previous_identity_status, upgrade_kind, skill_name, skill_version, skill_sha256
|
||
) values (${sqlLiteral(ids.legacyCase)}, ${sqlLiteral(ids.user)},
|
||
'jyotish-birth-time-rectification', '9.0.0', 'legacy_unverifiable', 'legacy_adoption',
|
||
'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(adoptionHash)});`,
|
||
/permission denied for table agentic_rectification_skill_upgrade_receipts/,
|
||
);
|
||
expectOwnerError(
|
||
`update public.agentic_rectification_skill_upgrade_receipts set skill_sha256 = ${sqlLiteral(oldHash)} where case_id = ${sqlLiteral(ids.legacyCase)};`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
expectOwnerError(
|
||
`delete from public.agentic_rectification_skill_run_receipts where case_id = ${sqlLiteral(openedCaseId)};`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
expectOwnerError(
|
||
`delete from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(openedCaseId)};`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
expectOwnerError(
|
||
`truncate table public.agentic_rectification_skill_upgrade_receipts;`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
expectOwnerError(
|
||
`truncate table public.agentic_rectification_skill_run_receipts;`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
expectOwnerError(
|
||
`truncate table public.agentic_rectification_cases cascade;`,
|
||
/agentic_rectification_skill_receipts_are_append_only/,
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select concat_ws(':',
|
||
(select count(*) from public.agentic_rectification_cases where id = ${sqlLiteral(ids.legacyCase)}),
|
||
(select count(*) from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(ids.legacyCase)}),
|
||
(select count(*) from public.agentic_rectification_skill_run_receipts where case_id = ${sqlLiteral(openedCaseId)})
|
||
)`),
|
||
'1:1:2',
|
||
);
|
||
|
||
insertSession(ids.caseDeleteSession, ids.user);
|
||
insertCase({
|
||
id: ids.caseDeleteCase,
|
||
userId: ids.user,
|
||
sessionId: ids.caseDeleteSession,
|
||
hash: oldHash,
|
||
});
|
||
fixture.psql(`insert into public.agentic_rectification_turns (id, case_id, user_message, status, model_name)
|
||
values (${sqlLiteral(ids.caseDeleteTurn)}, ${sqlLiteral(ids.caseDeleteCase)}, 'case cascade user', 'pending', 'fixture-model');`);
|
||
serviceSql(`select public.insert_agentic_rectification_skill_run_receipt(
|
||
${sqlLiteral(ids.user)}::uuid, ${sqlLiteral(ids.caseDeleteCase)}::uuid,
|
||
${sqlLiteral(ids.caseDeleteTurn)}::uuid, ${sqlLiteral(ids.caseDeleteRequest)}::uuid,
|
||
'turn', 'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(oldHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`);
|
||
fixture.psql(`delete from public.agentic_rectification_cases where id = ${sqlLiteral(ids.caseDeleteCase)};`);
|
||
assert.equal(
|
||
fixture.psql(`select concat_ws(':',
|
||
(select count(*) from public.agentic_rectification_cases where id = ${sqlLiteral(ids.caseDeleteCase)}),
|
||
(select count(*) from public.agentic_rectification_turns where id = ${sqlLiteral(ids.caseDeleteTurn)}),
|
||
(select count(*) from public.agentic_rectification_skill_run_receipts where case_id = ${sqlLiteral(ids.caseDeleteCase)})
|
||
)`),
|
||
'0:0:0',
|
||
);
|
||
|
||
fixture.psql(`delete from public.agentic_rectification_turns where id = ${sqlLiteral(ids.verifiedTurn)};`);
|
||
assert.equal(
|
||
fixture.psql(`select count(*) from public.agentic_rectification_skill_run_receipts where turn_id = ${sqlLiteral(ids.verifiedTurn)}`),
|
||
"0",
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select count(*) from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(openedCaseId)}`),
|
||
"1",
|
||
);
|
||
fixture.psql(`delete from public.agentic_rectification_cases where id = ${sqlLiteral(openedCaseId)};`);
|
||
assert.equal(
|
||
fixture.psql(`select count(*) from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(openedCaseId)}`),
|
||
"0",
|
||
);
|
||
|
||
insertSession(ids.cascadeSession, ids.cascadeUser);
|
||
insertCase({ id: ids.cascadeCase, userId: ids.cascadeUser, sessionId: ids.cascadeSession, hash: oldHash });
|
||
fixture.psql(`insert into public.agentic_rectification_turns (id, case_id, user_message, status, model_name)
|
||
values (${sqlLiteral(ids.cascadeTurn)}, ${sqlLiteral(ids.cascadeCase)}, 'cascade user', 'pending', 'fixture-model');`);
|
||
serviceSql(`select public.insert_agentic_rectification_skill_run_receipt(
|
||
${sqlLiteral(ids.cascadeUser)}::uuid, ${sqlLiteral(ids.cascadeCase)}::uuid,
|
||
${sqlLiteral(ids.cascadeTurn)}::uuid, ${sqlLiteral(ids.cascadeRequest)}::uuid,
|
||
'turn', 'jyotish-birth-time-rectification', '9.0.0', ${sqlLiteral(oldHash)}, ${sqlLiteral(sourceCommit)}
|
||
);`);
|
||
serviceSql(`select public.upgrade_agentic_rectification_skill_v2(
|
||
${sqlLiteral(ids.cascadeUser)}::uuid, ${sqlLiteral(ids.cascadeCase)}::uuid,
|
||
'jyotish-birth-time-rectification', '9.1.0', ${sqlLiteral(newHash)}, null
|
||
);`);
|
||
fixture.psqlAs(
|
||
"identity_runtime",
|
||
"identity-runtime-test-password",
|
||
`delete from identity.users where id = ${sqlLiteral(ids.cascadeUser)};`,
|
||
);
|
||
assert.equal(
|
||
fixture.psql(`select concat_ws(':',
|
||
(select count(*) from public.agentic_rectification_cases where id = ${sqlLiteral(ids.cascadeCase)}),
|
||
(select count(*) from public.agentic_rectification_skill_run_receipts where case_id = ${sqlLiteral(ids.cascadeCase)}),
|
||
(select count(*) from public.agentic_rectification_skill_upgrade_receipts where case_id = ${sqlLiteral(ids.cascadeCase)})
|
||
)`),
|
||
"0:0:0",
|
||
);
|
||
} finally {
|
||
fixture.stop();
|
||
}
|
||
});
|