Files
Jyotisha/frontend/tests/rectification-v9-engine-contract.test.ts
T
jesse-ux 8b982baf64
Independent Staging Quality Gate / validate (push) Canceled after 4m2s
Independent Staging Quality Gate / publish (push) Canceled after 0s
fix(rectification): 分类器失败、引擎忙、整轮超时不再归因错
点选题把分类器两次异常说成用户没说清(BUG-722);引擎 429 被压成坏了且重算静默失败(BUG-723);两次 attempt 总预算大于路由 maxDuration(BUG-724)。本单只改归因:classifier_unavailable 请用户重发、busy 分档并可见「这次没有重新比较」、整轮 225s 预算不够则 host fallback。不改计费、分类模型、并发闸门。
2026-09-16 07:06:57 +08:00

715 lines
26 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
RectificationEngineError,
RECTIFICATION_ENGINE_BUSY_BACKOFF_BUDGET_MS,
RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT,
engineRequestBody,
mergeVedastroValidateIntoReceipt,
nextEngineBusyBackoffMs,
runV9CandidateScore,
runV9Diagnostics,
runV9VedastroValidate,
sanitizeAskedProbeKeysForEngine,
setEngineBusyWaitForTests,
toEngineEvents,
type V9EngineScoreResult,
} from "../src/lib/rectification-agentic/v9/engine-client.ts";
import { askedSemanticKeysForEngine } from "../src/lib/rectification-agentic/v9/inference-adapter.ts";
const RANGE = { start_time: "04:50", end_time: "05:10" };
const CANDIDATE_ID = "88888888-8888-4888-8888-888888888881";
const SECOND_CANDIDATE_ID = "88888888-8888-4888-8888-888888888882";
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
const DECISION_RECEIPT = {
receipt_version: "candidate-decision-receipt-v2",
contract_version: "v2",
event_contract_version: "rectification-event-contract-v2",
policy_version: "rectification-candidate-policy-v2",
decision_policy_version: "rectification-candidate-policy-v2",
display_allowed: true,
selection_allowed: true,
acceptance_allowed: true,
propose_allowed: true,
confirmation_allowed: false,
accept_allowed: true,
confirm_allowed: false,
representative_candidate_id: CANDIDATE_ID,
representative_time: "04:50",
overall_confidence: "high",
margin_percent: 42.5,
};
const EXECUTION_LEDGER = [
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "d1-rashi", status: "executed", source: "python-engine" },
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "gochara", status: "executed", source: "python-engine" },
{ ledger_version: "rectification-execution-ledger-v2", stage: "technique_layer", method: "d24-chaturvimshamsha", status: "not_executed", source: "python-engine" },
];
const ENGINE_SCORE_RESPONSE_V2 = {
success: true,
endpoint: "rectification_v5_score",
result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62",
algorithm_version: "rectification-event-contract-v2",
event_contract_version: "rectification-event-contract-v2",
decision_policy_version: "rectification-candidate-policy-v2",
execution_ledger_version: "rectification-execution-ledger-v2",
candidate_decisions: [
{ candidate_id: CANDIDATE_ID, time: "04:50", rank: 1, relative_support: 57, tied_minute_count: 1 },
{ candidate_id: SECOND_CANDIDATE_ID, time: "04:51", rank: 3, relative_support: 18, tied_minute_count: 2 },
{ candidate_id: THIRD_CANDIDATE_ID, time: "04:52", rank: 2, relative_support: 25, tied_minute_count: 2 },
],
decision_receipt: DECISION_RECEIPT,
execution_ledger: EXECUTION_LEDGER,
diagnostics: {
primary_secondary_margin_percent: 99,
leave_one_event_out_retention_rate: 1,
},
candidate_scores: [
{ time: "04:59", score: 999999 },
],
};
const ENGINE_DIAGNOSTICS_RESPONSE_V2 = {
success: true,
endpoint: "rectification_v5_diagnostics",
result_id: "e4fbf2e0-85dc-5b42-a5a3-34e5dd4b7e62",
algorithm_version: "rectification-event-contract-v2",
event_contract_version: "rectification-event-contract-v2",
decision_policy_version: "rectification-candidate-policy-v2",
execution_ledger_version: "rectification-execution-ledger-v2",
candidate_decisions: ENGINE_SCORE_RESPONSE_V2.candidate_decisions,
diagnostics: {
primary_cluster_retention_rate: 0.86,
leave_one_event_out_retention_rate: 0.81,
leave_one_domain_out_retention_rate: 0.9,
date_sensitivity_retention_rate: 0.72,
neighbor_support_minutes: 2,
primary_secondary_margin_percent: 42.5,
unstable_event_ids: [],
most_discriminating_layers: ["vimsottari_dasha"],
candidate_splits: [{ candidate_id: SECOND_CANDIDATE_ID, width: 6 }],
},
missing_layers: ["KP_cusps"],
decision_receipt: DECISION_RECEIPT,
execution_ledger: EXECUTION_LEDGER,
};
function stubEngine(response: unknown, status = 200) {
const previous = globalThis.fetch;
globalThis.fetch = (async () => ({
ok: status >= 200 && status < 300,
status,
json: async () => response,
})) as unknown as typeof fetch;
return () => {
globalThis.fetch = previous;
};
}
const SNAPSHOT = {
birth_date: "1997-08-08",
latitude: 36.420487,
longitude: 114.209936,
timezone_offset: 8,
birth_time_source: "family_exact",
};
const EVIDENCE = [
{
id: "00000000-0000-4000-8000-000000000001",
sourceTurnId: "33333333-3333-4333-8333-333333333333",
subject: "self",
eventKind: "education_start",
domain: "education",
occurredFrom: "2016-09-01",
occurredTo: "2016-09-30",
datePrecision: "month",
summary: "大学入学",
status: "confirmed",
supersedesEvidenceId: null,
createdAt: "2026-08-12T10:00:06.000Z",
dateSource: "user_quote",
dateReliability: "month_exact",
dateCorroboration: "录取通知书",
dateConflictStatus: "none",
},
];
test("toEngineEvents preserves Event Contract v2 detailed kind and provenance", () => {
const events = toEngineEvents(EVIDENCE);
assert.equal(events.length, 1);
assert.equal(events[0]!.domain, "education");
assert.equal(events[0]!.event_kind, "education_start");
assert.equal(events[0]!.date_start, "2016-09-01");
assert.equal(events[0]!.precision, "month");
assert.equal(events[0]!.date_source, "user_quote");
assert.equal(events[0]!.date_reliability, "month_exact");
assert.equal(events[0]!.date_corroboration, "录取通知书");
assert.equal(events[0]!.date_conflict_status, "none");
assert.equal(events[0]!.source_turn_id, EVIDENCE[0]!.sourceTurnId);
assert.equal(events[0]!.subject, "self");
});
test("toEngineEvents keeps all scoreable detailed v2 kinds and drops background kinds", () => {
const detailed = [
"education_completion",
"career_entry",
"promotion",
"career_pressure",
"relationship_commitment",
"relationship_separation",
"finance_gain",
"finance_loss",
"self_health_event",
"family_event",
].map((eventKind, index) => ({
...EVIDENCE[0]!,
id: `00000000-0000-4000-8000-${String(index + 2).padStart(12, "0")}`,
eventKind,
domain: eventKind.startsWith("education") ? "education"
: eventKind.startsWith("career") || eventKind === "promotion" ? "career"
: eventKind.startsWith("relationship") ? "relationship"
: eventKind.startsWith("finance") ? "finance"
: eventKind === "family_event" ? "family"
: "health",
}));
const background = [
{ ...EVIDENCE[0]!, id: "00000000-0000-4000-8000-000000000100", eventKind: "other", domain: "other" },
];
assert.deepEqual(toEngineEvents([...detailed, ...background]).map((event) => event.event_kind), detailed.map((event) => event.eventKind));
const family = toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000200",
eventKind: "family_event",
domain: "family",
subject: "self",
}]);
assert.equal(family[0]!.subject, "family");
assert.deepEqual(toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000201",
eventKind: "appearance_note",
domain: "appearance",
datePrecision: "unknown",
occurredFrom: null,
occurredTo: null,
}]), []);
const datedAppearance = toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000202",
eventKind: "birthmark_or_scar",
domain: "marks",
datePrecision: "year",
occurredFrom: "2010-01-01",
occurredTo: "2010-12-31",
}]);
assert.equal(datedAppearance[0]!.event_kind, "birthmark_or_scar");
assert.equal(datedAppearance[0]!.domain, "marks");
const occupation = toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000203",
eventKind: "occupation_note",
domain: "occupation",
datePrecision: "year",
occurredFrom: "2019-01-01",
occurredTo: "2019-12-31",
}]);
assert.equal(occupation[0]!.event_kind, "occupation_note");
assert.equal(occupation[0]!.domain, "occupation");
const horary = toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000204",
eventKind: "horary_query",
domain: "horary",
datePrecision: "day",
occurredFrom: "2024-01-15",
occurredTo: "2024-01-15",
}]);
assert.equal(horary[0]!.event_kind, "horary_query");
assert.equal(horary[0]!.domain, "horary");
assert.deepEqual(toEngineEvents([{
...EVIDENCE[0]!,
id: "00000000-0000-4000-8000-000000000205",
eventKind: "horary_query",
domain: "horary",
datePrecision: "unknown",
occurredFrom: null,
occurredTo: null,
}]), []);
});
test("runV9CandidateScore strictly consumes server candidate decisions and v2 receipt", async () => {
const restore = stubEngine(ENGINE_SCORE_RESPONSE_V2);
try {
const result: V9EngineScoreResult = await runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
});
assert.deepEqual(result.candidates, [
{ candidateId: CANDIDATE_ID, time: "04:50", rank: 1, relativeSupport: 57, tiedMinuteCount: 1 },
{ candidateId: SECOND_CANDIDATE_ID, time: "04:51", rank: 3, relativeSupport: 18, tiedMinuteCount: 2 },
{ candidateId: THIRD_CANDIDATE_ID, time: "04:52", rank: 2, relativeSupport: 25, tiedMinuteCount: 2 },
], "Web must preserve server rank/support/tie instead of deriving or sorting them");
assert.equal(result.representativeCandidateId, CANDIDATE_ID);
assert.equal(result.representativeTime, "04:50");
assert.equal(result.selectionAllowed, true);
assert.equal(result.confirmationAllowed, false);
assert.equal(result.overallConfidence, "high");
assert.equal(result.policyVersion, "rectification-candidate-policy-v2");
assert.deepEqual(result.decisionReceipt, DECISION_RECEIPT);
assert.deepEqual(result.executionLedger, EXECUTION_LEDGER);
assert.deepEqual(result.executedMethods, ["d1-rashi", "gochara"], "methods come only from executed server ledger entries");
assert.equal(JSON.stringify(result).includes("999999"), false, "raw internal score must not enter the TS projection");
} finally {
restore();
}
});
test("runV9CandidateScore fails closed on a mismatched question contract version", async () => {
const restore = stubEngine({
...ENGINE_SCORE_RESPONSE_V2,
decision_receipt: { ...DECISION_RECEIPT, question_contract_version: "probe-question-v0" },
});
try {
await assert.rejects(
runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
}),
(error: unknown) => error instanceof RectificationEngineError && error.code === "engine_invalid_v2_receipt",
);
} finally {
restore();
}
});
test("runV9CandidateScore fails closed without a v2 decision receipt", async () => {
const missingReceipt = Object.fromEntries(
Object.entries(ENGINE_SCORE_RESPONSE_V2).filter(([key]) => key !== "decision_receipt"),
);
const restore = stubEngine(missingReceipt);
try {
await assert.rejects(
runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
}),
(error: unknown) => error instanceof RectificationEngineError && error.code === "engine_invalid_v2_receipt",
);
} finally {
restore();
}
});
test("runV9CandidateScore fails closed on invalid server candidate UUIDs", async () => {
const restore = stubEngine({
...ENGINE_SCORE_RESPONSE_V2,
candidate_decisions: [{ ...ENGINE_SCORE_RESPONSE_V2.candidate_decisions[0], candidate_id: "04:50" }],
});
try {
await assert.rejects(
runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
}),
(error: unknown) => error instanceof RectificationEngineError && error.code === "engine_invalid_candidate_decisions",
);
} finally {
restore();
}
});
test("runV9CandidateScore fails closed when no evidence is scoreable", async () => {
const restore = stubEngine(ENGINE_SCORE_RESPONSE_V2);
try {
await assert.rejects(
runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents([{ ...EVIDENCE[0]!, eventKind: "other", domain: "other" }]),
}),
(error: unknown) => error instanceof RectificationEngineError && error.code === "no_scorable_evidence",
);
} finally {
restore();
}
});
test("runV9Diagnostics maps diagnostics and receipt-owned confirmation", async () => {
const restore = stubEngine(ENGINE_DIAGNOSTICS_RESPONSE_V2);
try {
const result = await runV9Diagnostics({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
});
assert.equal(result.canConfirmExactMinute, false);
assert.equal(result.missingLayers.join(","), "KP_cusps");
assert.equal(result.diagnostics.primary_secondary_margin_percent, 42.5);
assert.deepEqual(result.executedMethods, ["d1-rashi", "gochara"]);
assert.equal(result.policyVersion, "rectification-candidate-policy-v2");
} finally {
restore();
}
});
test("engine http failures surface as safe engine errors, never raw stack traces", async () => {
const restore = stubEngine({ error: "invalid event kind" }, 400);
try {
await assert.rejects(
runV9CandidateScore({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
}),
(error: unknown) => error instanceof Error && error.message.includes("invalid event kind"),
);
} finally {
restore();
}
});
test("vedastro-validate propagates the engine minute confirmation signal into the combined gate input", async () => {
const restore = stubEngine({
success: true,
endpoint: "rectification_v5_vedastro_validate",
status: "pass",
passed: true,
can_confirm_exact_minute: true,
minute_sensitive_validation: { status: "pass", discriminated: true },
event_validation: {
status: "pass",
search_events_primary_supports_local_winner: true,
},
});
try {
const result = await runV9VedastroValidate({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
candidateTimes: ["04:50", "04:51"],
});
assert.equal(result.status, "passed");
assert.equal(result.canConfirmExactMinute, true);
assert.equal(result.searchEventsSupportsLocalWinner, true);
const merged = mergeVedastroValidateIntoReceipt(
{ ...DECISION_RECEIPT, gates: { exact_confirmation: { external_validation_status: "passed" } } },
result,
);
const exact = (merged.gates as { exact_confirmation: Record<string, unknown> }).exact_confirmation;
assert.equal(exact.external_validation_status, "passed");
assert.equal(
(exact.vedastro_event_validation as { search_events_primary_supports_local_winner: boolean })
.search_events_primary_supports_local_winner,
true,
);
assert.equal(merged.confirmation_allowed, false);
} finally {
restore();
}
});
test("vedastro-validate cannot pass the minute gate when the overall validation failed", async () => {
const restore = stubEngine({
status: "failed",
can_confirm_exact_minute: true,
minute_sensitive_validation: { status: "passed" },
event_validation: { search_events_primary_supports_local_winner: true },
});
try {
const result = await runV9VedastroValidate({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
candidateTimes: ["04:50", "04:51"],
});
assert.equal(result.status, "failed");
assert.equal(result.minuteSensitiveStatus, "failed");
assert.equal(result.canConfirmExactMinute, false);
const merged = mergeVedastroValidateIntoReceipt(
{ ...DECISION_RECEIPT, gates: { exact_confirmation: { external_validation_status: "not_evaluated" } } },
result,
);
const exact = (merged.gates as { exact_confirmation: Record<string, unknown> }).exact_confirmation;
assert.equal(exact.external_validation_status, "failed");
} finally {
restore();
}
});
test("vedastro-validate keeps safe timeout, HTTP, and invalid-response failure codes", async () => {
const input = {
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
candidateTimes: ["04:50", "04:51"] as const,
};
const previous = globalThis.fetch;
try {
globalThis.fetch = (async () => {
throw new DOMException("secret upstream detail", "TimeoutError");
}) as unknown as typeof fetch;
const timeout = await runV9VedastroValidate(input);
assert.equal(timeout.status, "failed");
assert.deepEqual(timeout.failure, { code: "timeout" });
assert.doesNotMatch(JSON.stringify(timeout.raw), /secret upstream detail/);
globalThis.fetch = (async () => ({
ok: false,
status: 503,
json: async () => ({ error: "private upstream URL and token" }),
})) as unknown as typeof fetch;
const requestFailure = await runV9VedastroValidate(input);
assert.equal(requestFailure.status, "failed");
assert.deepEqual(requestFailure.failure, { code: "engine_request_failed" });
assert.doesNotMatch(JSON.stringify(requestFailure.raw), /private upstream URL|token/);
globalThis.fetch = (async () => ({
ok: true,
status: 200,
json: async () => null,
})) as unknown as typeof fetch;
const invalid = await runV9VedastroValidate(input);
assert.equal(invalid.status, "failed");
assert.deepEqual(invalid.failure, { code: "engine_invalid_response" });
} finally {
globalThis.fetch = previous;
}
});
const VARGA_SPLIT_HASH = "04:45-05:15:04:47,04:51,04:53,04:59,05:00,05:06,05:08,05:13,05:15:varga.d9.04:47|04:51/05:00|05:06|04:59|04:53/05:08|05:13|05:15";
test("engineRequestBody drops varga split hashes and keeps short semantic keys", () => {
assert.equal(VARGA_SPLIT_HASH.length, 128);
const receipt = {
inference_state: {
answered_probes: [{
probe_id: "contrast:varga.d9.style",
semantic_key: "varga.d9.style",
candidate_split_hash: VARGA_SPLIT_HASH,
answer_class: "yes",
classified_from: "choice",
}],
},
};
const semantic = askedSemanticKeysForEngine(receipt, []);
assert.deepEqual(semantic, ["varga.d9.style"]);
assert.equal(semantic.includes(VARGA_SPLIT_HASH), false);
const body = engineRequestBody({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
askedProbeKeys: [VARGA_SPLIT_HASH, "varga.d9.style", "k".repeat(201)],
});
const keys = body.asked_probe_keys as string[];
assert.ok(Array.isArray(keys));
assert.equal(keys.includes(VARGA_SPLIT_HASH), false);
assert.equal(keys.some((key) => key.includes(":varga.")), false);
assert.ok(keys.every((key) => key.length <= 120));
assert.deepEqual(sanitizeAskedProbeKeysForEngine([VARGA_SPLIT_HASH, "varga.d9.style"]), ["varga.d9.style"]);
});
test("engineRequestBody forwards a slow-compare column_times subset", () => {
const body = engineRequestBody({
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
columnTimes: ["14:46", "14:47", "14:46", "not-a-clock"],
});
assert.deepEqual(body.column_times, ["14:46", "14:47"]);
});
const SCORE_INPUT = {
baselineBirthSnapshot: SNAPSHOT,
candidateRange: RANGE,
events: toEngineEvents(EVIDENCE),
};
function stubEngineSequence(
responses: Array<{
status?: number;
body?: unknown;
retryAfter?: string;
throwError?: Error;
}>,
) {
const previous = globalThis.fetch;
let index = 0;
globalThis.fetch = (async () => {
const next = responses[index] ?? responses.at(-1);
index += 1;
if (!next) throw new Error("no stubbed engine response");
if (next.throwError) throw next.throwError;
const status = next.status ?? 200;
return {
ok: status >= 200 && status < 300,
status,
headers: new Headers(next.retryAfter ? { "Retry-After": next.retryAfter } : {}),
json: async () => next.body,
};
}) as unknown as typeof fetch;
return {
calls: () => index,
restore() {
globalThis.fetch = previous;
},
};
}
function captureWarnings<T>(run: () => Promise<T>): Promise<{ result: PromiseSettledResult<T>; warnings: unknown[] }> {
const warnings: unknown[] = [];
const original = console.warn;
console.warn = (...args: unknown[]) => {
warnings.push(args);
};
return run()
.then((value) => ({ result: { status: "fulfilled" as const, value }, warnings }))
.catch((reason: unknown) => ({ result: { status: "rejected" as const, reason }, warnings }))
.finally(() => {
console.warn = original;
});
}
test("busy backoff is a named cap of two waits and six seconds", () => {
assert.equal(RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT, 2);
assert.equal(RECTIFICATION_ENGINE_BUSY_BACKOFF_BUDGET_MS, 6_000);
assert.equal(nextEngineBusyBackoffMs({
retryAfterSeconds: 2,
retriesUsed: 0,
backoffUsedMs: 0,
}), 2_000);
assert.equal(nextEngineBusyBackoffMs({
retryAfterSeconds: 2,
retriesUsed: 1,
backoffUsedMs: 2_000,
}), 2_000);
assert.equal(nextEngineBusyBackoffMs({
retryAfterSeconds: 2,
retriesUsed: 2,
backoffUsedMs: 4_000,
}), null);
assert.equal(nextEngineBusyBackoffMs({
retryAfterSeconds: 10,
retriesUsed: 0,
backoffUsedMs: 0,
}), 6_000);
assert.equal(nextEngineBusyBackoffMs({
retryAfterSeconds: 2,
retriesUsed: 1,
backoffUsedMs: 6_000,
}), null);
const client = readFileSync(
new URL("../src/lib/rectification-agentic/v9/engine-client.ts", import.meta.url),
"utf8",
);
assert.match(client, /RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT = 2/);
assert.match(client, /RECTIFICATION_ENGINE_BUSY_BACKOFF_BUDGET_MS = 6_000/);
assert.match(client, /retriesUsed >= RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT/);
});
test("score classifies 429, 500, timeout, and bad JSON separately and only 429 retries", async () => {
const waits: number[] = [];
setEngineBusyWaitForTests(async (ms) => {
waits.push(ms);
});
try {
const busyThenOk = stubEngineSequence([
{ status: 429, body: { error: "ERR_COMPUTE_BUSY" }, retryAfter: "2" },
{ status: 200, body: ENGINE_SCORE_RESPONSE_V2 },
]);
const busyOk = await captureWarnings(() => runV9CandidateScore(SCORE_INPUT));
busyThenOk.restore();
assert.equal(busyOk.result.status, "fulfilled");
if (busyOk.result.status === "fulfilled") {
assert.equal(busyOk.result.value.representativeTime, "04:50");
}
assert.equal(waits.length, 1);
assert.equal(waits[0], 2_000);
assert.equal(busyThenOk.calls(), 2);
const busyLog = JSON.stringify(busyOk.warnings[0]);
assert.match(busyLog, /rectification_engine_failed/);
assert.match(busyLog, /"busy"/);
assert.match(busyLog, /"httpStatus":429/);
assert.doesNotMatch(busyLog, /1997-08-08|latitude|token|JWT/);
waits.length = 0;
const http = stubEngineSequence([{ status: 500, body: { error: "boom" } }]);
const httpResult = await captureWarnings(() => runV9CandidateScore(SCORE_INPUT));
http.restore();
assert.equal(httpResult.result.status, "rejected");
if (httpResult.result.status === "rejected") {
const error = httpResult.result.reason;
assert.ok(error instanceof RectificationEngineError);
assert.equal(error.kind, "http_error");
assert.equal(error.httpStatus, 500);
}
assert.equal(waits.length, 0);
assert.equal(http.calls(), 1);
assert.match(JSON.stringify(httpResult.warnings[0]), /"http_error"/);
const timeout = stubEngineSequence([{
throwError: Object.assign(new Error("aborted"), { name: "TimeoutError" }),
}]);
const timeoutResult = await captureWarnings(() => runV9CandidateScore(SCORE_INPUT));
timeout.restore();
assert.equal(timeoutResult.result.status, "rejected");
if (timeoutResult.result.status === "rejected") {
const error = timeoutResult.result.reason;
assert.ok(error instanceof RectificationEngineError);
assert.equal(error.kind, "timeout");
}
assert.equal(waits.length, 0);
assert.match(JSON.stringify(timeoutResult.warnings[0]), /"timeout"/);
const bad = stubEngineSequence([{ status: 200, body: null }]);
const badResult = await captureWarnings(() => runV9CandidateScore(SCORE_INPUT));
bad.restore();
assert.equal(badResult.result.status, "rejected");
if (badResult.result.status === "rejected") {
const error = badResult.result.reason;
assert.ok(error instanceof RectificationEngineError);
assert.equal(error.kind, "bad_payload");
assert.equal(error.code, "engine_invalid_response");
}
assert.equal(waits.length, 0);
assert.match(JSON.stringify(badResult.warnings[0]), /"bad_payload"/);
} finally {
setEngineBusyWaitForTests(null);
}
});
test("busy retries that still fail keep the busy kind", async () => {
const waits: number[] = [];
setEngineBusyWaitForTests(async (ms) => {
waits.push(ms);
});
const stub = stubEngineSequence([
{ status: 429, body: { error: "ERR_COMPUTE_BUSY" }, retryAfter: "2" },
{ status: 429, body: { error: "ERR_COMPUTE_BUSY" }, retryAfter: "2" },
{ status: 429, body: { error: "ERR_COMPUTE_BUSY" }, retryAfter: "2" },
]);
try {
const result = await captureWarnings(() => runV9CandidateScore(SCORE_INPUT));
assert.equal(result.result.status, "rejected");
if (result.result.status === "rejected") {
const error = result.result.reason;
assert.ok(error instanceof RectificationEngineError);
assert.equal(error.kind, "busy");
}
assert.equal(waits.length, 2);
assert.equal(stub.calls(), 3);
} finally {
stub.restore();
setEngineBusyWaitForTests(null);
}
});