Merge pull request #363 from pikasTech/feat/d601-m3-code-agent-io

feat: expose M3 Code Agent route evidence
This commit is contained in:
Lyon
2026-05-23 23:30:44 +08:00
committed by GitHub
10 changed files with 295 additions and 6 deletions
+33 -2
View File
@@ -486,10 +486,12 @@ export function classifySkillCliM3Route(payload, { httpStatus = null, httpOk = f
) ?? null;
const providerTrace = payload?.providerTrace && typeof payload.providerTrace === "object" ? payload.providerTrace : null;
const route = skillTool?.route ?? providerTrace?.route ?? payload?.skills?.items?.find((item) => item?.route)?.route ?? null;
const method = skillTool?.method ?? providerTrace?.method ?? null;
const operationId = skillTool?.operationId ?? providerTrace?.operationId ?? null;
const traceId = skillTool?.traceId ?? providerTrace?.traceId ?? payload?.traceId ?? null;
const auditId = skillTool?.audit?.auditId ?? null;
const evidenceId = skillTool?.evidence?.evidenceId ?? null;
const auditId = skillTool?.auditId ?? skillTool?.audit?.auditId ?? providerTrace?.auditId ?? null;
const evidenceId = skillTool?.evidenceId ?? skillTool?.evidence?.evidenceId ?? providerTrace?.evidenceId ?? null;
const readback = skillTool?.readback ?? providerTrace?.readback ?? skillTool?.result?.targetReadback ?? null;
const blockers = normalizeM3RouteBlockers(payload, skillTool);
const safety = {
directGatewayCalls: Boolean(skillTool?.directGatewayCalls ?? payload?.runnerTrace?.directGatewayCalls ?? false),
@@ -510,6 +512,7 @@ export function classifySkillCliM3Route(payload, { httpStatus = null, httpOk = f
skillTool &&
["completed", "blocked"].includes(skillTool.status) &&
routeEvidenceReady &&
method === "POST" &&
safety.directGatewayCalls === false &&
safety.directBoxCalls === false &&
safety.directPatchPanelCalls === false &&
@@ -523,6 +526,7 @@ export function classifySkillCliM3Route(payload, { httpStatus = null, httpOk = f
if (payload?.provider !== "hwlab-skill-cli") missing.push("provider=hwlab-skill-cli");
if (!skillTool) missing.push(`${HWLAB_M3_IO_SKILL_NAME} toolCall`);
if (!routeEvidenceReady) missing.push(`route=${HWLAB_M3_IO_API_ROUTE}`);
if (method !== "POST") missing.push("method=POST");
if (safety.directGatewayCalls || safety.directBoxCalls || safety.directPatchPanelCalls || safety.directTargetMentioned) {
missing.push("no direct gateway/box/patch-panel target");
}
@@ -544,6 +548,7 @@ export function classifySkillCliM3Route(payload, { httpStatus = null, httpOk = f
backend: payload?.backend ?? null,
route,
expectedRoute: HWLAB_M3_IO_API_ROUTE,
method,
toolCallStatus: skillTool?.status ?? null,
accepted,
controlReady,
@@ -552,6 +557,7 @@ export function classifySkillCliM3Route(payload, { httpStatus = null, httpOk = f
traceId,
auditId,
evidenceId,
readback,
idsPresent,
structuredBlocker,
blockers,
@@ -1277,6 +1283,22 @@ function buildCommanderSummary(report) {
auditId: route.auditId ?? null,
evidenceId: route.evidenceId ?? null
};
const routeEvidenceContract = {
route: route.route ?? null,
expectedRoute: route.expectedRoute ?? HWLAB_M3_IO_API_ROUTE,
method: route.method ?? null,
operationId: ids.operationId,
traceId: ids.traceId,
auditId: ids.auditId,
evidenceId: ids.evidenceId,
accepted: route.accepted ?? null,
status: route.toolCallStatus ?? null,
controlReady: route.controlReady ?? null,
readback: route.readback ?? null,
visible: route.route === HWLAB_M3_IO_API_ROUTE &&
route.method === "POST" &&
Boolean(ids.operationId && ids.traceId && ids.auditId && ids.evidenceId)
};
return {
reportClass: "live-mutating",
allowedLiveMutationUsed: report.liveMutationUsed === true,
@@ -1307,10 +1329,15 @@ function buildCommanderSummary(report) {
status: report.dimensions.skillCliM3Route?.status ?? "not_run",
route: route.route ?? null,
expectedRoute: route.expectedRoute ?? HWLAB_M3_IO_API_ROUTE,
method: route.method ?? null,
structuredBlocker: route.structuredBlocker === true,
idsPresent: route.idsPresent === true,
accepted: route.accepted ?? null,
controlReady: route.controlReady ?? null,
readback: route.readback ?? null,
safety: route.safety ?? null
},
exactRouteEvidenceContract: routeEvidenceContract,
evidenceIds: Object.fromEntries(Object.entries(ids).filter(([, value]) => Boolean(value))),
blockers: report.blockers.map((blocker) => ({
scope: blocker.scope,
@@ -1383,6 +1410,10 @@ export function renderMarkdown(report) {
`- liveMutationUsed: ${report.liveMutationUsed === true}`,
`- allowM3Write: ${report.runPolicy?.allowM3Write === true}`,
`- skillCliRoute: ${report.commanderSummary?.m3RouteResult?.route ?? "not_run"}`,
`- skillCliMethod: ${report.commanderSummary?.m3RouteResult?.method ?? "not_run"}`,
`- operationId: ${report.commanderSummary?.evidenceIds?.operationId ?? "none"}`,
`- traceId: ${report.commanderSummary?.evidenceIds?.traceId ?? "none"}`,
`- auditId: ${report.commanderSummary?.evidenceIds?.auditId ?? "none"}`,
`- evidenceId: ${report.commanderSummary?.evidenceIds?.evidenceId ?? "none"}`,
"",
"| Dimension | Status | Summary |",
+47 -1
View File
@@ -291,7 +291,11 @@ test("Skill CLI M3 route passes with operation/audit/evidence ids and no direct
assert.equal(result.status, "pass");
assert.equal(result.observations.route, "/v1/m3/io");
assert.equal(result.observations.method, "POST");
assert.equal(result.observations.idsPresent, true);
assert.equal(result.observations.accepted, true);
assert.equal(result.observations.controlReady, true);
assert.equal(result.observations.readback.value, true);
assert.equal(result.observations.safety.directGatewayCalls, false);
assert.equal(result.observations.safety.fallbackUsed, false);
});
@@ -327,6 +331,14 @@ test("Skill CLI M3 route blocks missing route and direct gateway evidence", () =
assert.equal(result.observations.safety.directGatewayCalls, true);
});
test("Skill CLI M3 route requires explicit POST method evidence", () => {
const result = classifySkillCliM3Route(skillCliPayload({ method: null }), { httpStatus: 200, httpOk: true });
assert.equal(result.status, "blocked");
assert.match(result.summary, /method=POST/u);
assert.ok(result.observations.missing.includes("method=POST"));
});
test("RPT-004 live report defaults to read-only M3 route checks and records no live mutation", async () => {
const report = await buildRpt004Report({
...parseArgs([
@@ -387,6 +399,20 @@ test("RPT-004 live report defaults to read-only M3 route checks and records no l
assert.equal(report.commanderSummary.sessionResult.status, "pass");
assert.equal(report.commanderSummary.skillCliResult.status, "pass");
assert.equal(report.commanderSummary.m3RouteResult.route, "/v1/m3/io");
assert.equal(report.commanderSummary.m3RouteResult.method, "POST");
assert.equal(report.commanderSummary.m3RouteResult.accepted, true);
assert.equal(report.commanderSummary.m3RouteResult.controlReady, true);
assert.equal(report.commanderSummary.m3RouteResult.readback.value, true);
assert.equal(report.commanderSummary.exactRouteEvidenceContract.visible, true);
assert.equal(report.commanderSummary.exactRouteEvidenceContract.route, "/v1/m3/io");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.method, "POST");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.operationId, "op_rpt004_skill_m3");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.traceId, "trc_rpt004_skill");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.auditId, "aud_rpt004_skill_m3");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.evidenceId, "evd_rpt004_skill_m3");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.accepted, true);
assert.equal(report.commanderSummary.exactRouteEvidenceContract.status, "completed");
assert.equal(report.commanderSummary.exactRouteEvidenceContract.readback.value, true);
assert.equal(report.commanderSummary.evidenceIds.evidenceId, "evd_rpt004_skill_m3");
assert.equal(report.dimensions.skillCliM3Route.status, "pass");
assert.equal(report.dimensions.m3TrustedLoop.observations.skippedWriteLoop, true);
@@ -597,6 +623,7 @@ function openAiFallbackPayload({ reply = "text fallback" } = {}) {
function skillCliPayload({
route = "/v1/m3/io",
method = "POST",
accepted = true,
status = "succeeded",
operationId = "op_rpt004_skill_m3",
@@ -632,6 +659,7 @@ function skillCliPayload({
runnerTrace: {
traceId: "trc_rpt004_skill",
runnerKind: "hwlab-m3-io-skill-cli",
method,
route,
fallbackUsed: false,
directGatewayCalls,
@@ -641,22 +669,34 @@ function skillCliPayload({
providerTrace: {
runnerKind: "hwlab-m3-io-skill-cli",
route,
method,
traceId: "trc_rpt004_skill",
operationId,
auditId,
evidenceId,
accepted,
controlReady: accepted,
fallbackUsed: false
fallbackUsed: false,
readback: {
status: "succeeded",
value: true,
resourceId: "res_boxsimu_2",
port: "DI1"
}
},
toolCalls: [{
name: "hwlab-agent-runtime.m3-io",
status: accepted ? "completed" : "blocked",
type: "skill-cli",
route,
method,
accepted,
controlReady: accepted,
capabilityLevel: accepted ? "hwlab-api-control-ready" : "hwlab-api-control-blocked",
operationId,
traceId: "trc_rpt004_skill",
auditId,
evidenceId,
audit: {
auditId,
status
@@ -672,6 +712,12 @@ function skillCliPayload({
status: "degraded",
blocker: blocker?.code ?? "runtime_durable_not_green"
},
readback: {
status: "succeeded",
value: true,
resourceId: "res_boxsimu_2",
port: "DI1"
},
blocker,
blockers: blocker ? [blocker] : [{
code: "runtime_durable_not_green",