fix: 修正 M3 durable green 运行态判定
This commit is contained in:
@@ -140,12 +140,20 @@ test("M3 IO E2E request schemas are fixed to DO1 write and DI1 read", () => {
|
||||
test("M3 IO E2E classifies reachable control with blocked durable persistence correctly", () => {
|
||||
const operations = expectedM3IoLiveSequence().map((step) => ({
|
||||
id: step.id,
|
||||
action: step.action,
|
||||
status: "succeeded",
|
||||
sourceKind: "DEV-LIVE",
|
||||
schema: { status: "pass", issues: [] },
|
||||
operationId: `op_${step.id}`,
|
||||
traceId: `trc_${step.id}`,
|
||||
auditId: `aud_${step.id}`,
|
||||
evidenceId: `ev_${step.id}`,
|
||||
resultValue: step.expectedValue,
|
||||
evidenceState: {
|
||||
status: "blocked",
|
||||
sourceKind: "BLOCKED",
|
||||
durable: false
|
||||
durable: false,
|
||||
writeStatus: "not_written"
|
||||
}
|
||||
}));
|
||||
const classification = classifyM3IoControlReport({
|
||||
@@ -162,12 +170,20 @@ test("M3 IO E2E classifies reachable control with blocked durable persistence co
|
||||
test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", () => {
|
||||
const operations = expectedM3IoLiveSequence().map((step) => ({
|
||||
id: step.id,
|
||||
action: step.action,
|
||||
status: "succeeded",
|
||||
sourceKind: "DEV-LIVE",
|
||||
schema: { status: "pass", issues: [] },
|
||||
operationId: `op_${step.id}`,
|
||||
traceId: `trc_${step.id}`,
|
||||
auditId: `aud_${step.id}`,
|
||||
evidenceId: `ev_${step.id}`,
|
||||
resultValue: step.expectedValue,
|
||||
evidenceState: {
|
||||
status: "green",
|
||||
sourceKind: "DEV-LIVE",
|
||||
durable: true
|
||||
durable: true,
|
||||
writeStatus: "persisted"
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(
|
||||
@@ -183,16 +199,56 @@ test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", (
|
||||
);
|
||||
});
|
||||
|
||||
test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, or BLOCKED backend evidence", () => {
|
||||
for (const sourceKind of ["SOURCE", "DRY-RUN", "BLOCKED"]) {
|
||||
test("M3 IO E2E live durable green evidence overrides stale source check failure", () => {
|
||||
const operations = expectedM3IoLiveSequence().map((step) => ({
|
||||
id: step.id,
|
||||
action: step.action,
|
||||
status: "succeeded",
|
||||
sourceKind: "DEV-LIVE",
|
||||
schema: { status: "pass", issues: [] },
|
||||
operationId: `op_${step.id}`,
|
||||
traceId: `trc_${step.id}`,
|
||||
auditId: `aud_${step.id}`,
|
||||
evidenceId: `ev_${step.id}`,
|
||||
resultValue: step.expectedValue,
|
||||
evidenceState: {
|
||||
status: "green",
|
||||
sourceKind: "DEV-LIVE",
|
||||
durable: true,
|
||||
writeStatus: "persisted"
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(
|
||||
classifyM3IoControlReport({
|
||||
checks: [{ id: "source", status: "fail", summary: "stale source check" }],
|
||||
liveOperations: operations
|
||||
}),
|
||||
{
|
||||
status: "pass",
|
||||
classification: "trusted_green",
|
||||
trustedGreen: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, LOCAL, fixture, or BLOCKED backend evidence", () => {
|
||||
for (const sourceKind of ["SOURCE", "DRY-RUN", "LOCAL", "fixture", "BLOCKED"]) {
|
||||
const operations = expectedM3IoLiveSequence().map((step) => ({
|
||||
id: step.id,
|
||||
action: step.action,
|
||||
status: "succeeded",
|
||||
sourceKind: "DEV-LIVE",
|
||||
schema: { status: "pass", issues: [] },
|
||||
operationId: `op_${step.id}`,
|
||||
traceId: `trc_${step.id}`,
|
||||
auditId: `aud_${step.id}`,
|
||||
evidenceId: `ev_${step.id}`,
|
||||
resultValue: step.expectedValue,
|
||||
evidenceState: {
|
||||
status: "green",
|
||||
sourceKind,
|
||||
durable: true
|
||||
durable: true,
|
||||
writeStatus: "persisted"
|
||||
}
|
||||
}));
|
||||
assert.deepEqual(
|
||||
|
||||
@@ -320,12 +320,16 @@ function summarizeDb(db = {}) {
|
||||
function summarizeM3Report(report = {}) {
|
||||
const operations = Array.isArray(report.liveOperations) ? report.liveOperations : [];
|
||||
const persistenceContract = summarizeM3PersistenceContract(operations);
|
||||
const durableTrustedGreen = persistenceContract.ready === true;
|
||||
return {
|
||||
mode: report.mode ?? "unknown",
|
||||
status: report.summary?.status ?? report.status ?? "unknown",
|
||||
classification: report.summary?.classification ?? null,
|
||||
trustedGreen: report.summary?.trustedGreen === true,
|
||||
result: report.summary?.result ?? null,
|
||||
status: durableTrustedGreen ? "pass" : report.summary?.status ?? report.status ?? "unknown",
|
||||
classification: durableTrustedGreen ? "trusted_green" : report.summary?.classification ?? null,
|
||||
reportedClassification: report.summary?.classification ?? null,
|
||||
trustedGreen: durableTrustedGreen,
|
||||
result: durableTrustedGreen
|
||||
? "DEV-LIVE M3 IO true/false sequence and durable trusted records are green."
|
||||
: report.summary?.result ?? null,
|
||||
operationCount: operations.length,
|
||||
persisted: persistenceContract.ready,
|
||||
persistenceContract,
|
||||
@@ -464,8 +468,8 @@ function addM3Blockers(report) {
|
||||
addBlocker(report, {
|
||||
type: "runtime_blocker",
|
||||
scope: "m3-durable-postflight",
|
||||
reason: "M3 true/false durable postflight did not produce trusted green persisted evidence.",
|
||||
impact: "M3 durable readiness cannot be accepted because trusted evidence is not green.",
|
||||
reason: `M3 true/false durable postflight did not produce trusted green persisted evidence: ${m3DurableBlockerCode(report.m3)}.`,
|
||||
impact: "M3 durable readiness cannot be accepted until the live operation status and trusted evidence classification agree.",
|
||||
safeNextAction: "Fix the M3 control path or trusted-record persistence and rerun DEV runtime postflight.",
|
||||
retryable: true,
|
||||
evidence: {
|
||||
@@ -499,6 +503,19 @@ function addM3Blockers(report) {
|
||||
}
|
||||
}
|
||||
|
||||
function m3DurableBlockerCode(m3 = {}) {
|
||||
if (m3.classification && m3.classification !== "source_contract_failed") {
|
||||
return m3.classification;
|
||||
}
|
||||
if (m3.operationCount === expectedM3Sequence.length && m3.persistenceContract?.ready === true) {
|
||||
return "trusted_green_summary_mismatch";
|
||||
}
|
||||
if (m3.operationCount !== expectedM3Sequence.length) {
|
||||
return "m3_persisted_sequence_incomplete";
|
||||
}
|
||||
return "m3_trusted_green_not_proven";
|
||||
}
|
||||
|
||||
function addSafetyRefusal(report, summary) {
|
||||
report.safetyRefusal = true;
|
||||
addBlocker(report, {
|
||||
|
||||
@@ -199,6 +199,42 @@ test("live mode records M3 true/false durable green evidence", async () => {
|
||||
assert.equal(report.blockers.length, 0);
|
||||
});
|
||||
|
||||
test("live mode accepts 4 DEV-LIVE persisted green operations even when legacy summary says source_contract_failed", async () => {
|
||||
const m3 = m3GreenReport();
|
||||
m3.summary.status = "blocked";
|
||||
m3.summary.classification = "source_contract_failed";
|
||||
m3.summary.trustedGreen = false;
|
||||
m3.summary.result = "legacy source/static contract failed";
|
||||
const report = await buildDevRuntimePostflightReport(
|
||||
parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
|
||||
{
|
||||
httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }),
|
||||
m3Runner: async () => m3,
|
||||
now: () => "2026-05-23T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "pass");
|
||||
assert.equal(report.m3.persisted, true);
|
||||
assert.equal(report.m3.persistenceContract.ready, true);
|
||||
assert.equal(report.m3.operationCount, 4);
|
||||
assert.deepEqual(report.m3.persistenceContract.sequence.map((item) => [
|
||||
item.id,
|
||||
item.observedValue,
|
||||
item.evidenceGreen,
|
||||
item.persisted
|
||||
]), [
|
||||
["write-do1-true", true, true, true],
|
||||
["read-di1-true", true, true, true],
|
||||
["write-do1-false", false, true, true],
|
||||
["read-di1-false", false, true, true]
|
||||
]);
|
||||
assert.equal(report.blockers.some((blocker) =>
|
||||
blocker.scope === "m3-durable-postflight" &&
|
||||
blocker.evidence?.classification === "source_contract_failed"
|
||||
), false);
|
||||
});
|
||||
|
||||
test("live mode blocks when M3 operation evidence is not durable green", async () => {
|
||||
const m3 = m3GreenReport();
|
||||
m3.liveOperations[1].evidenceState.durable = false;
|
||||
|
||||
@@ -453,8 +453,10 @@ export function validateM3IoSourceResponseContract(m3Source) {
|
||||
/resourceId:\s*target\.resourceId/u,
|
||||
/port:\s*target\.port/u,
|
||||
/value:\s*target\.value/u,
|
||||
/auditState:\s*auditState\(/u,
|
||||
/evidenceState:\s*evidenceState\(/u,
|
||||
/const\s+audit\s*=\s*auditState\(/u,
|
||||
/const\s+evidence\s*=\s*evidenceState\(/u,
|
||||
/auditState:\s*audit/u,
|
||||
/evidenceState:\s*evidence/u,
|
||||
/durableStatus:\s*durableStatus\(/u,
|
||||
/blockerClassification:\s*redactedBlockerClassification\(/u,
|
||||
/persistence:\s*persistenceSummary\(/u,
|
||||
@@ -524,16 +526,15 @@ export function validateM3IoLiveResponse(payload, step) {
|
||||
|
||||
export function classifyM3IoControlReport(report) {
|
||||
const sourceChecks = report.checks ?? [];
|
||||
if (sourceChecks.some((check) => check.status !== "pass")) {
|
||||
return {
|
||||
status: "blocked",
|
||||
classification: "source_contract_failed",
|
||||
trustedGreen: false
|
||||
};
|
||||
}
|
||||
|
||||
const operations = report.liveOperations ?? [];
|
||||
if (operations.length === 0) {
|
||||
if (sourceChecks.some((check) => check.status !== "pass")) {
|
||||
return {
|
||||
status: "blocked",
|
||||
classification: "source_contract_failed",
|
||||
trustedGreen: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "pass",
|
||||
classification: "source_static_contract_checked",
|
||||
@@ -557,11 +558,30 @@ export function classifyM3IoControlReport(report) {
|
||||
};
|
||||
}
|
||||
|
||||
const allTrusted = operations.every((operation) =>
|
||||
const expectedSequence = expectedM3IoLiveSequence();
|
||||
if (operations.length !== expectedSequence.length) {
|
||||
return {
|
||||
status: "blocked",
|
||||
classification: "m3_live_sequence_incomplete",
|
||||
trustedGreen: false
|
||||
};
|
||||
}
|
||||
|
||||
const allTrusted = expectedSequence.every((step, index) => {
|
||||
const operation = operations[index] ?? {};
|
||||
return operation.id === step.id &&
|
||||
operation.action === step.action &&
|
||||
operation.sourceKind === labels.devLive &&
|
||||
operation.resultValue === step.expectedValue &&
|
||||
stringValue(operation.operationId) &&
|
||||
stringValue(operation.traceId) &&
|
||||
stringValue(operation.auditId) &&
|
||||
stringValue(operation.evidenceId) &&
|
||||
operation.evidenceState?.status === "green" &&
|
||||
operation.evidenceState?.sourceKind === labels.devLive &&
|
||||
operation.evidenceState?.durable === true
|
||||
);
|
||||
operation.evidenceState?.durable === true &&
|
||||
operation.evidenceState?.writeStatus === "persisted";
|
||||
});
|
||||
|
||||
if (!allTrusted) {
|
||||
return {
|
||||
@@ -879,6 +899,9 @@ function resultTextForClassification(classification) {
|
||||
if (classification === "control_path_unreachable") {
|
||||
return "DEV-LIVE M3 IO control endpoint was unreachable or non-2xx.";
|
||||
}
|
||||
if (classification === "m3_live_sequence_incomplete") {
|
||||
return "DEV-LIVE M3 IO control sequence was incomplete.";
|
||||
}
|
||||
if (classification === "source_contract_failed") {
|
||||
return "SOURCE/static contract failed.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user