fix(web): persist rectification C/D answers on an append-only inference ledger
Independent Staging Quality Gate / validate (push) Failing after 4m0s
Independent Staging Quality Gate / publish (push) Has been skipped

Engine result rows stay immutable. Choice answers append transitions, and reads overlay the latest revision instead of patching the cached receipt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-24 08:22:01 +08:00
co-authored by Cursor
parent 29750d3835
commit 909de8b884
14 changed files with 1701 additions and 65 deletions
+26 -10
View File
@@ -456,10 +456,26 @@ test("a failed opening does not let the next turn skip the server Skill load gat
});
test("same evidence + range fingerprints reuse the cached candidate snapshot", async () => {
const inputReceipt = {
receipt_version: "candidate-decision-receipt-v2",
policy_version: "rectification-candidate-policy-v2",
selection_allowed: true,
acceptance_allowed: true,
confirmation_allowed: false,
representative_candidate_id: null,
overall_confidence: "medium",
inference_state: { revision: 1, candidates: [{ id: "05:02", posterior_score: 10 }] },
};
const composedReceipt = {
...inputReceipt,
inference_state: { revision: 2, candidates: [{ id: "05:02", posterior_score: 18 }] },
decision_state_fingerprint: "a".repeat(64),
};
const accounting = fakeAccounting({
persist_agentic_rectification_candidate_v2: () => ({
...candidateSnapshotFixture(),
cached: true,
decision_receipt: composedReceipt,
}),
});
const cached = await persistV9Candidate(accounting.client, USER_ID, CASE_ID, {
@@ -472,15 +488,7 @@ test("same evidence + range fingerprints reuse the cached candidate snapshot", a
eventContractVersion: "rectification-event-contract-v2",
policyVersion: "rectification-candidate-policy-v2",
candidates: [{ candidateId: CANDIDATE_ID, rank: 1, time: "05:02", relativeSupport: 58, tiedMinuteCount: 2 }],
decisionReceipt: {
receipt_version: "candidate-decision-receipt-v2",
policy_version: "rectification-candidate-policy-v2",
selection_allowed: true,
acceptance_allowed: true,
confirmation_allowed: false,
representative_candidate_id: null,
overall_confidence: "medium",
},
decisionReceipt: inputReceipt,
executionLedger: [{ method: "d1-rashi", status: "executed" }],
});
assert.equal(cached.cached, true);
@@ -491,12 +499,20 @@ test("same evidence + range fingerprints reuse the cached candidate snapshot", a
assert.equal(call.args.p_skill_version, "9.0.0");
assert.equal(call.args.p_event_contract_version, "rectification-event-contract-v2");
assert.equal(call.args.p_decision_policy_version, "rectification-candidate-policy-v2");
assert.deepEqual(call.args.p_decision_receipt, cached.decisionReceipt);
assert.deepEqual(call.args.p_decision_receipt, inputReceipt);
assert.notDeepEqual(cached.decisionReceipt, inputReceipt);
assert.deepEqual(cached.decisionReceipt, composedReceipt);
assert.equal(
(cached.decisionReceipt.inference_state as { revision?: number }).revision,
2,
);
assert.equal(cached.decisionReceipt.decision_state_fingerprint, "a".repeat(64));
assert.deepEqual(call.args.p_execution_ledger, cached.executionLedger);
assert.equal("p_selection_allowed" in call.args, false);
assert.equal("p_confirmation_allowed" in call.args, false);
assert.equal("p_representative_time" in call.args, false);
assert.equal("p_margin_percent" in call.args, false);
assert.equal("p_decision_state_fingerprint" in call.args, false);
});
test("accept-candidate requires a server-persisted result; no tool means no minute", async () => {