fix(rectification): keep candidate state coherent
Independent Staging Quality Gate / validate (push) Failing after 15m32s
Independent Staging Quality Gate / publish (push) Has been skipped

This commit is contained in:
Jesse_Chen
2026-08-27 13:59:38 +08:00
parent 518119acbd
commit 5f855c610c
30 changed files with 2109 additions and 167 deletions
@@ -22,8 +22,10 @@ const skipWithoutDocker = dockerAvailable() ? false : "docker unavailable on thi
const snapshot = {
birth_date: "1997-08-08",
birth_place_label: "河北省邯郸市武安市",
latitude: 36.420487,
longitude: 114.209936,
timezone_id: "Asia/Shanghai",
timezone_offset: 8,
birth_time_source: "family_exact",
reported_birth_time: "05:00",
@@ -1085,7 +1087,7 @@ test("v9 agent api migration applies, seeds the runtime flag and guards consent"
}
});
test("v9 allows changing an accepted minute within the same live candidate result", { skip: skipWithoutDocker }, async () => {
test("v9 ignores a historical active minute and allows reselection within the live result", { skip: skipWithoutDocker }, async () => {
const fixture = startPostgresFixture();
const schemaUrl = fixture.connectionUrl("schema_owner", "schema-owner-test-password");
try {
@@ -1107,9 +1109,9 @@ test("v9 allows changing an accepted minute within the same live candidate resul
update public.profiles
set birth_date = '1997-08-08',
reported_birth_time = '05:00',
active_birth_time = null,
birth_time = '05:00',
birth_time_status = 'reported',
active_birth_time = '04:30',
birth_time = '04:30',
birth_time_status = 'accepted',
birth_time_source = 'family_exact',
birth_time_period = null,
uncertainty_before_minutes = 10,
@@ -1140,6 +1142,27 @@ test("v9 allows changing an accepted minute within the same live candidate resul
null,
"service_role",
);
const inferenceState = {
algorithm_version: "rectification-inference-v1",
candidate_set_id: "04:50-05:10:05:07,05:08,05:09",
revision: 0,
phase: "discrimination",
result_status: "completed_with_range",
range_start: "04:50",
range_end: "05:10",
candidates: [
{ id: "05:07", time: "05:07", cluster_range: ["05:07", "05:07"], prior_score: 34, posterior_score: 34, probability: 0.34, status: "active", rank: 2, strong_conflict_count: 0 },
{ id: "05:08", time: "05:08", cluster_range: ["05:08", "05:08"], prior_score: 33, posterior_score: 40, probability: 0.4, status: "active", rank: 1, strong_conflict_count: 0 },
{ id: "05:09", time: "05:09", cluster_range: ["05:09", "05:09"], prior_score: 33, posterior_score: 26, probability: 0.26, status: "active", rank: 3, strong_conflict_count: 0 },
],
events: [],
probes: [],
answered_probes: [],
rounds: [],
entropy: 1,
representative_time: "05:08",
credible_range: ["05:07", "05:09"],
};
const persisted = await service.rpc("persist_agentic_rectification_candidate_v2", {
p_user_id: userId,
p_case_id: caseId,
@@ -1163,6 +1186,7 @@ test("v9 allows changing an accepted minute within the same live candidate resul
representative_time: "05:08",
overall_confidence: "low",
margin_percent: 1,
inference_state: inferenceState,
},
p_execution_ledger: [
{ phase: "candidate.score", status: "completed", engine: "fixture-engine" },
@@ -1183,6 +1207,252 @@ test("v9 allows changing an accepted minute within the same live candidate resul
assert.match(switchedCandidateId, /^[0-9a-f-]{36}$/);
assert.match(thirdCandidateId, /^[0-9a-f-]{36}$/);
fixture.psql(`
update public.agentic_rectification_results
set decision_receipt = decision_receipt - 'inference_state'
where id = '${resultId}';
`);
const missingInference = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: firstCandidateId,
p_request_id: "01010101-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
});
assert.match(rpcError(missingInference.error), /agentic_rectification_candidate_state_inconsistent/);
fixture.psql(`
update public.agentic_rectification_results
set decision_receipt = jsonb_set(
decision_receipt,
'{inference_state}',
('${JSON.stringify(inferenceState)}'::jsonb - 'revision'),
true
)
where id = '${resultId}';
`);
const missingFallbackRevision = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: firstCandidateId,
p_request_id: "01010102-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
});
assert.match(
rpcError(missingFallbackRevision.error),
/agentic_rectification_candidate_state_inconsistent/,
"a fallback receipt without revision must fail closed",
);
const eliminatedInference = {
...inferenceState,
candidates: inferenceState.candidates.map((candidate) => (
candidate.time === "05:09"
? { ...candidate, status: "eliminated", probability: 0 }
: candidate
)),
credible_range: ["05:07", "05:08"],
};
fixture.psql(`
update public.agentic_rectification_results
set decision_receipt = jsonb_set(decision_receipt, '{inference_state}', '${JSON.stringify(eliminatedInference)}'::jsonb, true)
where id = '${resultId}';
`);
const eliminatedCandidate = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: thirdCandidateId,
p_request_id: "02020202-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
});
assert.match(rpcError(eliminatedCandidate.error), /agentic_rectification_candidate_state_inconsistent/);
fixture.psql(`
update public.agentic_rectification_results
set decision_receipt = jsonb_set(decision_receipt, '{inference_state}', '${JSON.stringify(inferenceState)}'::jsonb, true)
where id = '${resultId}';
`);
const latestInferenceState = { ...inferenceState, revision: 1 };
fixture.psql(`
insert into public.agentic_rectification_inference_transitions (
case_id, result_id, previous_revision, revision, candidate_set_id,
probe_id, semantic_key, candidate_split_hash, answer_class, raw_answer,
posterior_before, posterior_after, score_deltas, inference_state,
decision_state_fingerprint, reason, idempotency_key, receipt_after
) values (
'${caseId}', '${resultId}', 0, 1, '${inferenceState.candidate_set_id}',
'probe:fixture', 'fixture.latest-transition', '${"d".repeat(64)}', 'no', 'fixture',
'{}'::jsonb, '{}'::jsonb, '{}'::jsonb, '${JSON.stringify(latestInferenceState)}'::jsonb,
'${"e".repeat(64)}', 'choice', 'fixture-transition-revision-1', '{}'::jsonb
);
`);
assert.equal(
fixture.psql(`select decision_receipt #>> '{inference_state,revision}' from public.agentic_rectification_results where id = '${resultId}'`),
"0",
"the immutable engine receipt must remain at revision 0",
);
const composedRevision = () => fixture.psql(`
select public.compose_agentic_rectification_decision_receipt(
'${caseId}',
'${resultId}',
decision_receipt
) #>> '{inference_state,revision}'
from public.agentic_rectification_results
where id = '${resultId}'
`);
const composedHasInference = () => fixture.psql(`
select public.compose_agentic_rectification_decision_receipt(
'${caseId}',
'${resultId}',
decision_receipt
) ? 'inference_state'
from public.agentic_rectification_results
where id = '${resultId}'
`);
assert.equal(composedRevision(), "1", "a coherent latest transition must overlay the read receipt");
fixture.psql(`
update public.agentic_rectification_inference_transitions
set inference_state = jsonb_set(inference_state, '{revision}', '2'::jsonb, true)
where case_id = '${caseId}' and revision = 1;
`);
assert.equal(composedHasInference(), "f", "a nested revision mismatch must hide stale read candidates");
fixture.psql(`
update public.agentic_rectification_inference_transitions
set inference_state = jsonb_set(
'${JSON.stringify(latestInferenceState)}'::jsonb,
'{candidate_set_id}',
to_jsonb('wrong-set'::text),
true
)
where case_id = '${caseId}' and revision = 1;
`);
assert.equal(composedHasInference(), "f", "a nested candidate-set mismatch must hide stale read candidates");
fixture.psql(`
update public.agentic_rectification_inference_transitions
set inference_state = '${JSON.stringify(latestInferenceState)}'::jsonb
where case_id = '${caseId}' and revision = 1;
`);
assert.equal(composedRevision(), "1", "restoring a coherent transition must restore the overlay");
const malformedLatestInferenceStates = [
{
label: "status",
state: {
...latestInferenceState,
candidates: latestInferenceState.candidates.map((candidate, index) => (
index === 0 ? Object.fromEntries(Object.entries(candidate).filter(([key]) => key !== "status")) : candidate
)),
},
},
{
label: "probability",
state: {
...latestInferenceState,
candidates: latestInferenceState.candidates.map((candidate, index) => (
index === 0 ? Object.fromEntries(Object.entries(candidate).filter(([key]) => key !== "probability")) : candidate
)),
},
},
{
label: "posterior_score",
state: {
...latestInferenceState,
candidates: latestInferenceState.candidates.map((candidate, index) => (
index === 0 ? Object.fromEntries(Object.entries(candidate).filter(([key]) => key !== "posterior_score")) : candidate
)),
},
},
];
for (const [index, malformed] of malformedLatestInferenceStates.entries()) {
fixture.psql(`
update public.agentic_rectification_inference_transitions
set inference_state = '${JSON.stringify(malformed.state)}'::jsonb
where case_id = '${caseId}' and revision = 1;
`);
const malformedAccept = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: firstCandidateId,
p_request_id: `0303030${index + 3}-aaaa-4aaa-8aaa-aaaaaaaaaaaa`,
});
assert.match(
rpcError(malformedAccept.error),
/agentic_rectification_candidate_state_inconsistent/,
`missing ${malformed.label} must fail closed`,
);
}
fixture.psql(`
update public.agentic_rectification_inference_transitions
set inference_state = '${JSON.stringify(latestInferenceState)}'::jsonb
where case_id = '${caseId}' and revision = 1;
`);
fixture.psql(`
update public.profiles
set declared_window_start = '04:45', declared_window_end = '05:15'
where id = '${userId}';
`);
assert.equal(
fixture.psql(`select status from public.agentic_rectification_cases where id = '${caseId}'`),
"needs_rebaseline",
);
assert.equal(
fixture.psql(`select invalidated_at is not null from public.agentic_rectification_results where id = '${resultId}'`),
"t",
);
fixture.psql(`
set session_replication_role = replica;
update public.profiles
set declared_window_start = null, declared_window_end = null
where id = '${userId}';
update public.agentic_rectification_cases
set status = 'candidate_ready'
where id = '${caseId}';
update public.agentic_rectification_results
set invalidated_at = null
where id = '${resultId}';
set session_replication_role = origin;
update public.profiles
set birth_place_label = '邯郸(用户备注)', active_birth_time = '04:31'
where id = '${userId}';
`);
assert.equal(
fixture.psql(`select status from public.agentic_rectification_cases where id = '${caseId}'`),
"candidate_ready",
);
assert.equal(
fixture.psql(`select invalidated_at is null from public.agentic_rectification_results where id = '${resultId}'`),
"t",
);
fixture.psql(`
set session_replication_role = replica;
update public.profiles set timezone_id = 'Asia/Taipei' where id = '${userId}';
set session_replication_role = origin;
`);
const staleTimezoneFirstAccept = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: firstCandidateId,
p_request_id: "00000000-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
});
assert.match(rpcError(staleTimezoneFirstAccept.error), /agentic_rectification_candidate_profile_changed/);
assert.equal(
fixture.psql(`select selected_candidate_id is null from public.agentic_rectification_results where id = '${resultId}'`),
"t",
);
fixture.psql(`
set session_replication_role = replica;
update public.profiles set timezone_id = 'Asia/Shanghai' where id = '${userId}';
set session_replication_role = origin;
`);
const firstAccept = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
@@ -1192,6 +1462,41 @@ test("v9 allows changing an accepted minute within the same live candidate resul
});
assert.equal(firstAccept.error, null, rpcError(firstAccept.error));
fixture.psql(`
set session_replication_role = replica;
update public.profiles
set birth_date = '1997-08-09',
birth_place_label = '台北市',
latitude = 25.033,
longitude = 121.5654,
timezone_id = 'Asia/Taipei'
where id = '${userId}';
set session_replication_role = origin;
`);
const staleReselection = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,
p_case_id: caseId,
p_result_id: resultId,
p_candidate_id: switchedCandidateId,
p_request_id: "12121212-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
});
assert.match(rpcError(staleReselection.error), /agentic_rectification_candidate_profile_changed/);
assert.equal(
fixture.psql(`select selected_candidate_id from public.agentic_rectification_results where id = '${resultId}'`),
firstCandidateId,
);
fixture.psql(`
set session_replication_role = replica;
update public.profiles
set birth_date = '1997-08-08',
birth_place_label = '河北省邯郸市武安市',
latitude = 36.420487,
longitude = 114.209936,
timezone_id = 'Asia/Shanghai'
where id = '${userId}';
set session_replication_role = origin;
`);
const switchRequestId = "22222222-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
const switched = await service.rpc("accept_agentic_rectification_candidate_for_case_v2", {
p_user_id: userId,