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>
157 lines
12 KiB
TypeScript
157 lines
12 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 { runV9CandidateScore, type V9EngineEvent } from "../src/lib/rectification-agentic/v9/engine-client.ts";
|
|
import { scanCaseSegments } from "../src/lib/rectification-agentic/v9/segment-scan.ts";
|
|
import { buildCaseInferenceState } from "../src/lib/rectification-agentic/v9/inference-adapter.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 { persistV9Candidate, loadV9CaseDossier } from "../src/lib/rectification-agentic/v9/tool-service.ts";
|
|
import { asInferenceState } from "../src/lib/rectification-agentic/core/compose-receipt.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));
|
|
|
|
// Real Linux loopback API responses for an existing public AA case; SQL owner/session IDs are fictional.
|
|
test("real score API optional metadata survives production persist/readback and PostgreSQL segment mass validation", { skip: !docker && "docker unavailable" }, async () => {
|
|
const fixture = startPostgresFixture();
|
|
const url = fixture.connectionUrl("service_runtime", "service-runtime-test-password");
|
|
const previousFetch = globalThis.fetch;
|
|
try {
|
|
const migration = spawnSync(process.execPath, [runner], { encoding: "utf8", env: {
|
|
...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl("schema_owner", "schema-owner-test-password"),
|
|
} });
|
|
assert.equal(migration.status, 0, migration.stderr);
|
|
const req = golden.request;
|
|
// The score request has no reported clock; derive its civil midpoint, not a second fixture birth time.
|
|
const interval = golden.response.decision_receipt.candidate_intervals[0];
|
|
const reportedTime = new Date((Date.parse(`${interval.start_at}:00Z`) + Date.parse(`${interval.end_at}:00Z`)) / 2).toISOString().slice(11, 16);
|
|
const snapshot = { birth_date: req.birth_date, reported_birth_time: reportedTime, 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 range = { start_time: req.start_time, end_time: req.end_time,
|
|
candidate_intervals: golden.response.decision_receipt.candidate_intervals };
|
|
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 }
|
|
: String(endpoint).includes("varga-segments") ? golden.scan : golden.response,
|
|
), { status: 200 })) as typeof fetch;
|
|
const score = await runV9CandidateScore({ baselineBirthSnapshot: snapshot, candidateRange: range,
|
|
events: req.events as V9EngineEvent[] });
|
|
const minutes = await scanCaseSegments({ snapshot, range, targets: ["D1", "D9", "D10"] });
|
|
assert.equal(score.candidates.length, golden.response.candidate_decisions.length);
|
|
for (const candidate of score.candidates) {
|
|
const decision = golden.response.candidate_decisions.find((row: { time: string }) => row.time === candidate.time);
|
|
assert.deepEqual(candidate.clusterTimes, decision.cluster_times);
|
|
const raw = golden.response.candidate_scores.find((row: { time: string }) => row.time === candidate.time);
|
|
const feature = golden.response.candidate_feature_snapshot.features.find((row: { time: string }) => row.time === candidate.time);
|
|
assert.equal(candidate.rawScore, raw.score);
|
|
assert.deepEqual(candidate.signature, { D1: feature.ascendant_sign_index, D9: feature.varga_ascendants.D9, D10: feature.varga_ascendants.D10 });
|
|
}
|
|
assert.ok(score.candidates.some(candidate => !Number.isInteger(candidate.rawScore)));
|
|
const engineProbes = refinementFromDecisionReceipt(score.decisionReceipt).discriminating_event_probes;
|
|
assert.ok(engineProbes.length > 0);
|
|
const inference = buildCaseInferenceState({ range, candidates: score.candidates, evidence: [], probes: engineProbes,
|
|
segmentMinutes: minutes, segmentTargets: ["D1", "D9", "D10"], segmentScanComplete: true });
|
|
assert.ok(inference.segment_summary?.charts.some(chart => chart.segments.length > 1));
|
|
const userId = randomUUID(), sessionId = randomUUID(), caseId = randomUUID();
|
|
fixture.psqlAs("identity_runtime", "identity-runtime-test-password", `
|
|
insert into identity.users(id,name,email,email_verified) values ('${userId}','Public AA Contract Fixture','${userId}@example.invalid',true);
|
|
`);
|
|
fixture.psql(`
|
|
update public.profiles set birth_date='${req.birth_date}',reported_birth_time='${reportedTime}',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}','Public AA optional metadata contract','general','birth_time_rectification','[]');
|
|
insert into public.agentic_rectification_cases(id,user_id,session_id,status,skill_name,skill_version,baseline_profile_fingerprint,baseline_birth_snapshot,candidate_range)
|
|
values('${caseId}','${userId}','${sessionId}','candidate_ready','jyotish-birth-time-rectification','9.0.0','${"a".repeat(64)}','${JSON.stringify(snapshot)}','${JSON.stringify(range)}');
|
|
`);
|
|
const service = createLocalPostgresDataClient(url, null, "service_role");
|
|
const persisted = await persistV9Candidate(service, userId, caseId, {
|
|
engineResultId: score.engineResultId, algorithmVersion: score.algorithmVersion, evidenceFingerprint: "b".repeat(64),
|
|
rangeFingerprint: "c".repeat(64), skillVersion: "9.0.0", eventContractVersion: score.eventContractVersion,
|
|
policyVersion: score.policyVersion, candidateRange: range, candidates: score.candidates,
|
|
decisionReceipt: { ...score.decisionReceipt, inference_state: inference }, executionLedger: score.executionLedger,
|
|
});
|
|
const loaded = await loadV9CaseDossier(service, userId, caseId);
|
|
assert.equal(loaded.latestResult?.resultId, persisted.resultId);
|
|
for (const candidate of loaded.latestResult!.candidates) {
|
|
const original = score.candidates.find(row => row.time === candidate.time)!;
|
|
assert.equal(candidate.rawScore, original.rawScore);
|
|
assert.deepEqual(candidate.signature, original.signature);
|
|
assert.deepEqual(candidate.clusterTimes, original.clusterTimes);
|
|
}
|
|
const initialReadback = asInferenceState(loaded.latestResult!.decisionReceipt?.inference_state);
|
|
assert.ok(initialReadback?.segment_summary);
|
|
assert.deepEqual(initialReadback.segment_summary, inference.segment_summary);
|
|
const immutableReceipt = fixture.psql(`select decision_receipt from public.agentic_rectification_results where id='${persisted.resultId}'`);
|
|
// Exercise one real dated engine probe through the production focus/schema/tap handler and PostgreSQL ledger.
|
|
// This is an answer persistence contract, not proof of the method planner's six-question benchmark.
|
|
const probe = engineProbes.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 baseFollowup = { 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(baseFollowup, { probes: [probe], birthDate: req.birth_date });
|
|
assert.ok(frame);
|
|
const focus = await persistServerOwnedFocus({ accounting: service, userId, caseId, activeFocus: null,
|
|
decisionReceipt: loaded.latestResult!.decisionReceipt,
|
|
followup: { ...baseFollowup, choice_frame: frame, must_not_label: [] } as unknown as MethodFollowup });
|
|
assert.equal(focus.status, "created");
|
|
assert.ok(focus.focus);
|
|
const choice = await applyRectificationChoice(service, { userId, caseId, sessionId,
|
|
actionId: randomUUID(), action: CHOICE_ACTION, focusId: focus.focus.id, optionId: "A",
|
|
expectedRevision: initialReadback.revision, deferFollowup: true });
|
|
assert.equal(choice.applied, true);
|
|
const afterChoice = await loadV9CaseDossier(service, userId, caseId);
|
|
const readback = asInferenceState(afterChoice.latestResult!.decisionReceipt?.inference_state);
|
|
assert.ok(readback?.segment_summary);
|
|
assert.equal(readback.revision, initialReadback.revision + 1);
|
|
assert.ok(readback.candidates.some(candidate => candidate.raw_posterior_score !==
|
|
initialReadback.candidates.find(original => original.time === candidate.time)?.raw_posterior_score));
|
|
assert.notDeepEqual(readback.segment_summary, initialReadback.segment_summary);
|
|
assert.deepEqual(readback.segment_minutes, initialReadback.segment_minutes, "full scan remains immutable");
|
|
assert.equal(fixture.psql(`select decision_receipt from public.agentic_rectification_results where id='${persisted.resultId}'`), immutableReceipt,
|
|
"latest transition composes on read, never rewrites initial receipt");
|
|
assert.equal(fixture.psql(`select count(*) from public.agentic_rectification_choice_actions where case_id='${caseId}' and status in ('applied','narrated')`), "1");
|
|
assert.equal(fixture.psql(`select count(*) from public.agentic_rectification_inference_transitions where case_id='${caseId}'`), "1");
|
|
const anchor = afterChoice.latestResult!.candidates.find(candidate => candidate.time === readback.representative_time)!;
|
|
assert.ok(anchor);
|
|
const adopted = await service.rpc("accept_agentic_rectification_segment_for_case_v1", {
|
|
p_user_id: userId, p_case_id: caseId, p_result_id: persisted.resultId,
|
|
p_candidate_id: anchor.candidateId, p_request_id: randomUUID(),
|
|
});
|
|
assert.equal(score.selectionAllowed, golden.response.decision_receipt.accept_allowed);
|
|
if (!score.selectionAllowed) {
|
|
assert.ok(adopted.error, "real engine selection gate remains closed");
|
|
return;
|
|
}
|
|
assert.equal(adopted.error, null, JSON.stringify(adopted.error));
|
|
const saved = adopted.data as Record<string, unknown>;
|
|
assert.equal(saved.saved_time, readback.segment_summary.adoption_minute?.time);
|
|
assert.equal(saved.saved_date, readback.segment_summary.adoption_minute?.date);
|
|
const adoptedReadback = await loadV9CaseDossier(service, userId, caseId);
|
|
assert.equal(adoptedReadback.latestResult?.segmentSelection?.date, saved.saved_date);
|
|
assert.equal(adoptedReadback.latestResult?.segmentSelection?.time, saved.saved_time);
|
|
} finally {
|
|
globalThis.fetch = previousFetch;
|
|
await closeLocalPostgresDataPool(url);
|
|
fixture.stop();
|
|
}
|
|
});
|