fix: harden durable runtime DEV gates

This commit is contained in:
Code Queue Review
2026-05-23 12:41:41 +00:00
parent 85e4c1c4ad
commit 30967566b7
7 changed files with 355 additions and 19 deletions
+97 -5
View File
@@ -14,6 +14,12 @@ const defaultReportPath = "reports/dev-gate/dev-runtime-postflight-report.json";
const issue = "pikasTech/HWLAB#311";
const defaultHealthUrl = `${DEV_ENDPOINT}/health/live`;
const defaultV1Url = `${DEV_ENDPOINT}/v1`;
const expectedM3Sequence = Object.freeze([
{ id: "write-do1-true", action: "do.write", resultValue: true },
{ id: "read-di1-true", action: "di.read", resultValue: true },
{ id: "write-do1-false", action: "do.write", resultValue: false },
{ id: "read-di1-false", action: "di.read", resultValue: false }
]);
export async function runDevRuntimePostflightCli(argv = process.argv.slice(2), options = {}) {
const args = parseArgs(argv);
@@ -241,11 +247,14 @@ function summarizeApiPayload(check, response) {
m3IoControl: json.m3IoControl
? {
route: json.m3IoControl.route ?? null,
enabled: json.m3IoControl.enabled === true,
contractVersion: json.m3IoControl.contractVersion ?? null
status: json.m3IoControl.status ?? null,
enabled: json.m3IoControl.enabled === true || json.m3IoControl.status === "available",
contractVersion: json.m3IoControl.contractVersion ?? null,
directFrontendGatewayOrBoxAccess: json.m3IoControl.boundaries?.directFrontendGatewayOrBoxAccess === true,
genericHardwareRpcExposedToFrontend: json.m3IoControl.boundaries?.genericHardwareRpcExposedToFrontend === true
}
: null,
error: response.error ?? null
error: response.error ? redactFailureText(response.error) : null
};
}
@@ -297,6 +306,7 @@ function summarizeDb(db = {}) {
function summarizeM3Report(report = {}) {
const operations = Array.isArray(report.liveOperations) ? report.liveOperations : [];
const persistenceContract = summarizeM3PersistenceContract(operations);
return {
mode: report.mode ?? "unknown",
status: report.summary?.status ?? report.status ?? "unknown",
@@ -304,6 +314,8 @@ function summarizeM3Report(report = {}) {
trustedGreen: report.summary?.trustedGreen === true,
result: report.summary?.result ?? null,
operationCount: operations.length,
persisted: persistenceContract.ready,
persistenceContract,
operations: operations.map((operation) => ({
id: operation.id,
action: operation.action,
@@ -316,13 +328,47 @@ function summarizeM3Report(report = {}) {
evidenceState: {
status: operation.evidenceState?.status ?? null,
durable: operation.evidenceState?.durable === true,
sourceKind: operation.evidenceState?.sourceKind ?? null
sourceKind: operation.evidenceState?.sourceKind ?? null,
writeStatus: operation.evidenceState?.writeStatus ?? null
},
blocker: operation.blocker ?? null
blocker: sanitizeM3Blocker(operation.blocker)
}))
};
}
function summarizeM3PersistenceContract(operations = []) {
const observed = new Map(operations.map((operation) => [operation.id, operation]));
const sequence = expectedM3Sequence.map((expected) => {
const operation = observed.get(expected.id) ?? {};
const evidenceState = operation.evidenceState ?? {};
const idsPresent = Boolean(operation.operationId && operation.traceId && operation.auditId && operation.evidenceId);
const valueMatches = operation.resultValue === expected.resultValue;
const evidenceGreen = evidenceState.status === "green" &&
evidenceState.durable === true &&
evidenceState.sourceKind === "DEV-LIVE";
const persisted = evidenceState.writeStatus === "persisted";
return {
id: expected.id,
action: expected.action,
expectedValue: expected.resultValue,
observedValue: Object.hasOwn(operation, "resultValue") ? operation.resultValue : null,
status: operation.status ?? "missing",
idsPresent,
valueMatches,
evidenceGreen,
persisted
};
});
return {
contractVersion: "m3-do1-true-false-persisted-v1",
requiredRoute: "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1",
requiredValues: [true, true, false, false],
ready: operations.length === expectedM3Sequence.length &&
sequence.every((item) => item.status === "succeeded" && item.idsPresent && item.valueMatches && item.evidenceGreen && item.persisted),
sequence
};
}
function addApiReadinessBlockers(report) {
for (const [scope, payload] of [["api-health-live", report.apiHealth], ["api-v1", report.apiV1]]) {
if (!payload.reachable) {
@@ -338,10 +384,36 @@ function addApiReadinessBlockers(report) {
blockerCodes: payload.blockerCodes
});
}
if (scope === "api-v1" && !apiV1M3ContractReady(payload)) {
addBlocker(report, "runtime_blocker", scope, "GET /v1 did not expose the controlled M3 IO route required by durable postflight.", {
m3IoControl: payload.m3IoControl,
requiredRoute: "/v1/m3/io",
requiredContractVersion: "m3-io-control-v1"
});
}
}
}
function apiV1M3ContractReady(payload) {
const control = payload.m3IoControl;
return payload.check === "GET /v1" &&
control?.route === "/v1/m3/io" &&
control?.contractVersion === "m3-io-control-v1" &&
control?.enabled === true &&
control?.directFrontendGatewayOrBoxAccess !== true &&
control?.genericHardwareRpcExposedToFrontend !== true;
}
function addM3Blockers(report) {
if (report.m3?.persistenceContract?.ready !== true) {
addBlocker(report, "runtime_blocker", "m3-durable-postflight", "M3 DO1 true/false durable postflight did not prove persisted operation/audit/evidence for every step.", {
persistenceContract: report.m3?.persistenceContract ?? null,
trustedGreen: report.m3?.trustedGreen === true,
operationCount: report.m3?.operationCount ?? 0
});
return;
}
if (report.m3?.trustedGreen !== true || report.m3?.status !== "pass") {
addBlocker(report, "runtime_blocker", "m3-durable-postflight", "M3 true/false durable postflight did not produce trusted green persisted evidence.", {
classification: report.m3?.classification ?? "unknown",
@@ -433,6 +505,26 @@ function summarizeReport(report) {
};
}
function sanitizeM3Blocker(blocker) {
if (!blocker || typeof blocker !== "object") {
return typeof blocker === "string" ? redactFailureText(blocker) : blocker ?? null;
}
return sanitizeRedactedValue(blocker);
}
function sanitizeRedactedValue(value) {
if (typeof value === "string") {
return redactFailureText(value);
}
if (Array.isArray(value)) {
return value.map(sanitizeRedactedValue);
}
if (value && typeof value === "object") {
return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, sanitizeRedactedValue(nested)]));
}
return value;
}
async function writeReport(report, reportPath, root) {
const absolute = path.resolve(root, reportPath);
await mkdir(path.dirname(absolute), { recursive: true });