Review-only snapshot for BUG-1115 through BUG-1117; not merge-ready. New opening and append-turn PostgreSQL permission failures remain blocked. Persisted joint replay has zero completed questions; segment ordering remains off by default. The existing offline replay JSON is retained stale and unchanged after a denied overwrite, including its CRLF line endings. Browser/provider validation and final serial gates remain pending. No deployment, role permission changes, or staging/main push. Co-Authored-By: Claude Code <noreply@anthropic.com>
152 lines
13 KiB
TypeScript
152 lines
13 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { randomUUID } from "node:crypto";
|
|
import { readFileSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import test from "node:test";
|
|
import { closeLocalPostgresDataPool, createLocalPostgresDataClient } from "../src/lib/db/local-postgres-client-core.ts";
|
|
import { resolveActiveSkillPackage, resolveExactSkillPackage } from "../src/lib/skill-package-registry.ts";
|
|
import { excludeEmptyConsultations } from "../src/lib/session-list-filter.ts";
|
|
import { openRectificationCase } from "../src/lib/rectification-agentic/v9/case-service.ts";
|
|
import { runV9CandidateScore, type V9EngineEvent } from "../src/lib/rectification-agentic/v9/engine-client.ts";
|
|
import { buildProductCaseInferenceState } from "../src/lib/rectification-agentic/v9/product-inference.ts";
|
|
import { asInferenceState } from "../src/lib/rectification-agentic/core/compose-receipt.ts";
|
|
import { refinementFromDecisionReceipt } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
|
|
import { applyRectificationChoice } from "../src/lib/rectification-agentic/v9/answer-choice.ts";
|
|
import { CHOICE_ACTION } from "../src/lib/rectification-agentic/v9/choice-action.ts";
|
|
import { buildChoiceFrame } from "../src/lib/rectification-agentic/v9/choice-card.ts";
|
|
import type { MethodFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts";
|
|
import { persistServerOwnedFocus } from "../src/lib/rectification-agentic/v9/server-focus.ts";
|
|
import { appendV9Turn, insertV9SkillRunReceipt, loadV9CaseDossier, loadV9CaseSkillIdentity, persistV9Candidate } from "../src/lib/rectification-agentic/v9/tool-service.ts";
|
|
import { startPostgresFixture } from "./helpers/postgres-fixture.ts";
|
|
|
|
const golden = JSON.parse(readFileSync(new URL("./fixtures/varga-api-response.golden.json", import.meta.url), "utf8"));
|
|
const docker = spawnSync("docker", ["version"], { stdio: "ignore" }).status === 0;
|
|
const runner = fileURLToPath(new URL("../scripts/db-migrate.mjs", import.meta.url));
|
|
|
|
// A historically bound Case seeded explicitly, not a new-opening bypass or an engine-policy fixture.
|
|
// Scoring/probes are unmodified public-AA API captures; user/session ownership is fictional.
|
|
test("post-bump historical exact Skill survives real PostgreSQL list, session open, cached result, choice and run receipt", { skip: !docker && "docker unavailable" }, async () => {
|
|
const fixture = startPostgresFixture();
|
|
const url = fixture.connectionUrl("service_runtime", "service-runtime-test-password");
|
|
const authenticatedUrl = fixture.connectionUrl("app_runtime", "app-runtime-test-password");
|
|
const previousFetch = globalThis.fetch;
|
|
try {
|
|
const migrated = spawnSync(process.execPath, [runner], { encoding: "utf8", env: {
|
|
...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password"),
|
|
} });
|
|
assert.equal(migrated.status, 0, migrated.stderr);
|
|
const active = resolveActiveSkillPackage("jyotish-birth-time-rectification");
|
|
const old = resolveExactSkillPackage(active.name, "10.0.32", "c2cab1364542e96290be416bb3d445c8c2baa90a52371aff2067b4d338184cee");
|
|
assert.equal(active.version, "10.0.33");
|
|
assert.equal(old.status, "deprecated");
|
|
const req = golden.request, intervals = golden.response.decision_receipt.candidate_intervals;
|
|
const range = { start_time: req.start_time, end_time: req.end_time, candidate_intervals: intervals };
|
|
const reported = new Date((Date.parse(`${intervals[0].start_at}:00Z`) + Date.parse(`${intervals[0].end_at}:00Z`)) / 2).toISOString().slice(11, 16);
|
|
const snapshot = { birth_date: req.birth_date, reported_birth_time: reported, active_birth_time: null,
|
|
birth_time_source: "approximate", birth_time_period: null, uncertainty_before_minutes: 15, uncertainty_after_minutes: 15,
|
|
latitude: req.lat, longitude: req.lon, timezone_id: "America/Los_Angeles", timezone_offset: req.tz };
|
|
const userId = randomUUID(), sessionId = randomUUID(), caseId = randomUUID();
|
|
const closedSessionId = randomUUID(), closedCaseId = randomUUID(), emptyConsultationId = randomUUID();
|
|
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `insert into identity.users(id,name,email,email_verified)
|
|
values('${userId}','Fictional Historical Skill Owner','${userId}@example.invalid',true)`);
|
|
fixture.psql(`update public.profiles set birth_date='${req.birth_date}',reported_birth_time='${reported}',birth_time_source='approximate',birth_time_status='reported',
|
|
uncertainty_before_minutes=15,uncertainty_after_minutes=15,latitude=${req.lat},longitude=${req.lon},timezone_id='America/Los_Angeles',timezone_offset=${req.tz} where id='${userId}';
|
|
insert into public.chat_sessions(id,user_id,title,theme,session_type,messages) values
|
|
('${sessionId}','${userId}','Fictional historical active','general','birth_time_rectification','[]'),
|
|
('${closedSessionId}','${userId}','Fictional historical readonly','general','birth_time_rectification','[]'),
|
|
('${emptyConsultationId}','${userId}','Fictional empty consultation','general','consultation','[]');
|
|
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
|
|
('${caseId}','${userId}','${sessionId}','candidate_ready','${old.name}','${old.version}','${old.sha256}',${old.sourceCommit ? `'${old.sourceCommit}'` : "null"},'${"a".repeat(64)}','${JSON.stringify(snapshot)}','${JSON.stringify(range)}',null),
|
|
('${closedCaseId}','${userId}','${closedSessionId}','closed','${old.name}','${old.version}','${old.sha256}',${old.sourceCommit ? `'${old.sourceCommit}'` : "null"},'${"a".repeat(64)}','${JSON.stringify(snapshot)}','${JSON.stringify(range)}',now())`);
|
|
const service = createLocalPostgresDataClient(url, null, "service_role");
|
|
const authenticated = createLocalPostgresDataClient(authenticatedUrl, { id: userId, email: null }, "authenticated");
|
|
const listed = await excludeEmptyConsultations(authenticated.from("chat_sessions")
|
|
.select("id,session_type,rectification_case_id,messages").eq("user_id", userId).eq("pinned", false)
|
|
.is("archived_at", null).or("chart_profile_id.is.null,chart_profile_id.eq.self"))
|
|
.order("updated_at", { ascending: false }).order("id", { ascending: false }).limit(21);
|
|
assert.equal(listed.error, null, JSON.stringify(listed.error));
|
|
const rows = listed.data as { id: string; messages: unknown[] }[];
|
|
assert.deepEqual(new Set(rows.map(row => row.id)), new Set([sessionId, closedSessionId]));
|
|
assert.ok(rows.every(row => row.messages.length === 0), "production filter keeps history whose messages live in the Case ledger");
|
|
for (const [id, expectedCase, disposition] of [[sessionId, caseId, "resumed"], [closedSessionId, closedCaseId, "readonly"]] as const) {
|
|
// Production session path reads the old identity first, then calls the old eleven-argument overload.
|
|
// Any permission refusal is a test failure; no grants, direct replacement open or twelve-argument retry.
|
|
const opened = await openRectificationCase(
|
|
service as unknown as Parameters<typeof openRectificationCase>[0],
|
|
userId, { intent: "session", sessionId: id, requestId: randomUUID() },
|
|
);
|
|
assert.equal(opened.caseId, expectedCase);
|
|
assert.equal(opened.sessionId, id);
|
|
assert.equal(opened.skillVersion, old.version);
|
|
assert.equal(opened.disposition, disposition);
|
|
assert.equal(opened.shouldStartOpening, false);
|
|
}
|
|
const bound = await loadV9CaseSkillIdentity(service, userId, caseId);
|
|
assert.deepEqual(bound, { name: old.name, version: old.version, sha256: old.sha256, sourceCommit: old.sourceCommit });
|
|
assert.equal(resolveExactSkillPackage(bound.name, bound.version, bound.sha256).sha256, old.sha256);
|
|
globalThis.fetch = (async (endpoint: unknown) => new Response(JSON.stringify(String(endpoint).includes("versions")
|
|
? { algorithm_version: golden.response.algorithm_version, decision_policy_version: golden.response.decision_policy_version }
|
|
: golden.response), { status: 200 })) as typeof fetch;
|
|
const score = await runV9CandidateScore({ baselineBirthSnapshot: snapshot, candidateRange: range, events: req.events as V9EngineEvent[] });
|
|
const probes = refinementFromDecisionReceipt(score.decisionReceipt).discriminating_event_probes;
|
|
const inference = buildProductCaseInferenceState({ range, candidates: score.candidates, evidence: [], probes }, null);
|
|
assert.equal(inference.segment_targets, undefined, "nullable historical identity does not opt into the product scan");
|
|
assert.equal(inference.segment_summary, undefined);
|
|
assert.equal(inference.segment_minutes, undefined);
|
|
assert.ok(inference.candidates.every(candidate => candidate.signature === undefined
|
|
&& candidate.raw_prior_score === undefined && candidate.raw_posterior_score === undefined && candidate.raw_eliminated === undefined));
|
|
const input = { engineResultId: score.engineResultId, algorithmVersion: score.algorithmVersion,
|
|
evidenceFingerprint: "b".repeat(64), rangeFingerprint: "c".repeat(64), skillVersion: old.version,
|
|
eventContractVersion: score.eventContractVersion, policyVersion: score.policyVersion, candidateRange: range,
|
|
candidates: score.candidates, decisionReceipt: { ...score.decisionReceipt, inference_state: inference }, executionLedger: score.executionLedger };
|
|
const first = await persistV9Candidate(service, userId, caseId, input);
|
|
const cached = await persistV9Candidate(service, userId, caseId, input);
|
|
assert.equal(cached.cached, true);
|
|
assert.equal(cached.resultId, first.resultId);
|
|
assert.equal(fixture.psql(`select count(*) from public.agentic_rectification_results where case_id='${caseId}'`), "1");
|
|
const loaded = await loadV9CaseDossier(service, userId, caseId);
|
|
assert.equal(loaded.case.skillVersion, old.version);
|
|
assert.equal(loaded.case.rectificationDomain, null);
|
|
assert.equal(loaded.latestResult?.decisionReceipt?.product_delivery, undefined);
|
|
const before = asInferenceState(loaded.latestResult!.decisionReceipt?.inference_state);
|
|
assert.ok(before);
|
|
const probe = probes.find(probe => (probe.information_gain ?? 0) > 0 && probe.year > 0
|
|
&& probe.expected_outcomes?.some(outcome => outcome.answer_class === "yes" && outcome.supports.length > 0));
|
|
assert.ok(probe);
|
|
const followup = { method_id: "dasha_events", ask_theme: "dated_event", domain: probe.domain,
|
|
user_prompt_hint: probe.user_meaning, intent: "distinguish_candidates", source: "event_probe",
|
|
semantic_key: probe.semantic_key, candidate_split_hash: probe.candidate_split_hash, candidate_ids: probe.candidate_ids,
|
|
expected_outcomes: probe.expected_outcomes, information_gain: probe.information_gain, probe_year: probe.year,
|
|
...(probe.choice_kind ? { choice_kind: probe.choice_kind } : {}), ...(probe.style_options ? { style_options: probe.style_options } : {}) };
|
|
const frame = buildChoiceFrame(followup, { probes: [probe], birthDate: req.birth_date });
|
|
assert.ok(frame);
|
|
const focus = await persistServerOwnedFocus({ accounting: service, userId, caseId, activeFocus: null,
|
|
decisionReceipt: loaded.latestResult!.decisionReceipt,
|
|
followup: { ...followup, choice_frame: frame, must_not_label: [] } as unknown as MethodFollowup });
|
|
assert.ok(focus.focus);
|
|
const actionId = randomUUID();
|
|
const chosen = await applyRectificationChoice(service, { userId, caseId, sessionId, actionId, action: CHOICE_ACTION,
|
|
focusId: focus.focus.id, optionId: "A", expectedRevision: before.revision, deferFollowup: true });
|
|
assert.equal(chosen.applied, true);
|
|
const reloaded = await loadV9CaseDossier(service, userId, caseId);
|
|
assert.equal(asInferenceState(reloaded.latestResult!.decisionReceipt?.inference_state)?.revision, before.revision + 1);
|
|
assert.equal(reloaded.case.rectificationDomain, null);
|
|
assert.equal(asInferenceState(reloaded.latestResult!.decisionReceipt?.inference_state)?.segment_summary, undefined);
|
|
assert.ok(asInferenceState(reloaded.latestResult!.decisionReceipt?.inference_state)?.candidates.every(candidate =>
|
|
candidate.signature === undefined && candidate.raw_prior_score === undefined
|
|
&& candidate.raw_posterior_score === undefined && candidate.raw_eliminated === undefined));
|
|
assert.equal(fixture.psql(`select count(*) from public.agentic_rectification_inference_transitions where case_id='${caseId}'`), "1");
|
|
assert.equal(fixture.psql(`select count(*) from public.agentic_rectification_choice_actions where case_id='${caseId}' and status in ('applied','narrated')`), "1");
|
|
const turn = await appendV9Turn(service, userId, caseId, { modelName: "fictional-history-receipt", userMessage: "Fictional historical action" });
|
|
await insertV9SkillRunReceipt(service, userId, caseId, turn.turnId, randomUUID(), "turn", bound);
|
|
assert.equal(fixture.psql(`select skill_version || ':' || skill_sha256 from public.agentic_rectification_skill_run_receipts where case_id='${caseId}'`), `${old.version}:${old.sha256}`);
|
|
assert.deepEqual(await loadV9CaseSkillIdentity(service, userId, caseId), bound, "choice/cache/run action never upgrades to active");
|
|
} finally {
|
|
globalThis.fetch = previousFetch;
|
|
await closeLocalPostgresDataPool(url);
|
|
await closeLocalPostgresDataPool(authenticatedUrl);
|
|
fixture.stop();
|
|
}
|
|
});
|