docs: refresh DEV gate evidence layering
This commit is contained in:
@@ -6,6 +6,7 @@ import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { activeReportLifecycle } from "../internal/dev-report-lifecycle.mjs";
|
||||
import {
|
||||
DEV_ENDPOINT,
|
||||
DEV_FRONTEND_ENDPOINT,
|
||||
@@ -299,6 +300,9 @@ function buildActiveReport({ fixture, probes, generatedAt, commitId }) {
|
||||
acceptanceLevel: "dev_m2_deploy_smoke",
|
||||
devOnly: true,
|
||||
prodDisabled: true,
|
||||
reportLifecycle: activeReportLifecycle(
|
||||
"Active M2 read-only public endpoint smoke; route reachability is not M5 DEV-LIVE acceptance."
|
||||
),
|
||||
status,
|
||||
generatedAt,
|
||||
endpoint: DEV_ENDPOINT,
|
||||
|
||||
@@ -113,15 +113,20 @@ async function run(command, args = [], options = {}) {
|
||||
timeout: options.timeoutMs ?? 5000,
|
||||
maxBuffer: 1024 * 1024
|
||||
});
|
||||
return {
|
||||
const probe = {
|
||||
ok: true,
|
||||
command: commandText(command, args),
|
||||
exitCode: 0,
|
||||
stdout: redacted(result.stdout.trim()),
|
||||
stderr: redacted(result.stderr.trim())
|
||||
};
|
||||
Object.defineProperty(probe, "rawStdout", {
|
||||
value: result.stdout.trim(),
|
||||
enumerable: false
|
||||
});
|
||||
return probe;
|
||||
} catch (error) {
|
||||
return {
|
||||
const probe = {
|
||||
ok: false,
|
||||
command: commandText(command, args),
|
||||
exitCode: typeof error.code === "number" ? error.code : 1,
|
||||
@@ -129,6 +134,11 @@ async function run(command, args = [], options = {}) {
|
||||
stderr: redacted(String(error.stderr ?? "").trim()),
|
||||
error: oneLine(error.message)
|
||||
};
|
||||
Object.defineProperty(probe, "rawStdout", {
|
||||
value: String(error.stdout ?? "").trim(),
|
||||
enumerable: false
|
||||
});
|
||||
return probe;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,9 +249,10 @@ async function collectSshBridge(binaries) {
|
||||
}
|
||||
|
||||
function parseJsonProbe(probe) {
|
||||
if (!probe.ok || !probe.stdout) return null;
|
||||
const raw = probe.rawStdout ?? probe.stdout;
|
||||
if (!probe.ok || !raw) return null;
|
||||
try {
|
||||
return JSON.parse(probe.stdout);
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -407,7 +418,7 @@ function buildBlockers({ binaries, kubeconfig, sshBridge, cluster }) {
|
||||
nextTask: "Mount a read-only kubeconfig for hwlab-dev, or document the approved k3s local kubeconfig path without exposing token material."
|
||||
});
|
||||
}
|
||||
if (!sshBridge.configured) {
|
||||
if (!sshBridge.configured && !cluster.readable) {
|
||||
addBlocker(blockers, {
|
||||
type: "environment_blocker",
|
||||
scope: "d601-maintenance-ssh-bridge",
|
||||
|
||||
@@ -5,7 +5,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { activeReportLifecycle, reportIsHistorical, reportLifecycleState } from "../../internal/dev-report-lifecycle.mjs";
|
||||
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
|
||||
import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
|
||||
|
||||
export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
|
||||
@@ -42,6 +42,7 @@ const reportPaths = Object.freeze({
|
||||
devDeploy: "reports/dev-gate/dev-deploy-report.json",
|
||||
devArtifacts: "reports/dev-gate/dev-artifacts.json",
|
||||
devEdgeHealth: "reports/dev-gate/dev-edge-health.json",
|
||||
devM2Smoke: "reports/dev-gate/dev-m2-deploy-smoke-active.json",
|
||||
devM3Hardware: "reports/dev-gate/dev-m3-hardware-loop.json",
|
||||
devM4Agent: "reports/dev-gate/dev-m4-agent-loop.json",
|
||||
devM5Gate: "reports/dev-gate/dev-mvp-gate-report.json",
|
||||
@@ -189,6 +190,19 @@ function hasBlocker(blockers, predicate) {
|
||||
return blockers.some(predicate);
|
||||
}
|
||||
|
||||
function isRouteTransportBlocker(blocker) {
|
||||
const scope = blocker.scope ?? "";
|
||||
return scope === "m2-public-endpoints" ||
|
||||
scope === "dev-edge" ||
|
||||
scope === "dev-edge-health" ||
|
||||
scope === "hwlab-router" ||
|
||||
scope === "hwlab-tunnel-client" ||
|
||||
scope === "hwlab-edge-proxy" ||
|
||||
scope.includes("edge-frp") ||
|
||||
scope.includes("ingress") ||
|
||||
scope.includes("frp");
|
||||
}
|
||||
|
||||
function applyPriority(blocker) {
|
||||
if (blocker.scope === "base-image") {
|
||||
return {
|
||||
@@ -234,6 +248,16 @@ function applyPriority(blocker) {
|
||||
};
|
||||
}
|
||||
|
||||
if (blocker.scope === "hwlab-dev-readonly-rbac") {
|
||||
return {
|
||||
...blocker,
|
||||
priority: "P1",
|
||||
unblockOrder: 4,
|
||||
unblocks: [issue(34), issue(33), issue(36), issue(38), issue(39)],
|
||||
rationale: "D601 has a client path, but read-only hwlab-dev pods/services/configmaps must be observable before live acceptance evidence can be trusted."
|
||||
};
|
||||
}
|
||||
|
||||
if (blocker.scope.includes("cloud-api-db")) {
|
||||
return {
|
||||
...blocker,
|
||||
@@ -244,6 +268,16 @@ function applyPriority(blocker) {
|
||||
};
|
||||
}
|
||||
|
||||
if (blocker.scope === "db-live") {
|
||||
return {
|
||||
...blocker,
|
||||
priority: "P1",
|
||||
unblockOrder: 5,
|
||||
unblocks: [issue(37), issue(39)],
|
||||
rationale: "M4 and M5 cannot claim live agent or MVP evidence until cloud-api /health/live proves DB readiness with redacted live evidence."
|
||||
};
|
||||
}
|
||||
|
||||
if (isEdgeOrFrpBlocker(blocker)) {
|
||||
return {
|
||||
...blocker,
|
||||
@@ -254,6 +288,16 @@ function applyPriority(blocker) {
|
||||
};
|
||||
}
|
||||
|
||||
if (blocker.scope === "m3-hardware-loop-runtime") {
|
||||
return {
|
||||
...blocker,
|
||||
priority: "P0",
|
||||
unblockOrder: 6,
|
||||
unblocks: [issue(38), issue(39), issue(64)],
|
||||
rationale: "M3 remains blocked until the real DEV hardware trusted loop proves DO1 -> patch-panel -> DI1 with operation, trace, audit, and evidence identifiers."
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...blocker,
|
||||
priority: blocker.priority ?? "P3",
|
||||
@@ -264,11 +308,7 @@ function applyPriority(blocker) {
|
||||
}
|
||||
|
||||
function isEdgeOrFrpBlocker(blocker) {
|
||||
return blocker.scope.includes("edge") ||
|
||||
blocker.scope.includes("ingress") ||
|
||||
blocker.scope.includes("frp") ||
|
||||
blocker.summary.includes(":16667") ||
|
||||
blocker.summary.includes("16667");
|
||||
return isRouteTransportBlocker(blocker);
|
||||
}
|
||||
|
||||
function sourceEntry(entry) {
|
||||
@@ -309,9 +349,11 @@ function collectM2Evidence(reports) {
|
||||
const deploy = reports.devDeploy;
|
||||
const artifacts = reports.devArtifacts;
|
||||
const edge = reports.devEdgeHealth;
|
||||
const m2Smoke = reports.devM2Smoke;
|
||||
const d601 = reports.d601Observability;
|
||||
const artifactIdentity = preflight.artifactIdentity;
|
||||
const edgeLive = hasCurrentLiveEdgeEvidence(edge);
|
||||
const m2EndpointLive = hasCurrentM2EndpointEvidence(m2Smoke);
|
||||
|
||||
return [
|
||||
reportEvidence({
|
||||
@@ -352,6 +394,18 @@ function collectM2Evidence(reports) {
|
||||
commands: artifacts.validationCommands,
|
||||
summary: "Artifact publish preflight exists but is blocked before any real image publish."
|
||||
}),
|
||||
reportEvidence({
|
||||
milestone: "M2",
|
||||
level: m2EndpointLive ? "DEV-LIVE" : "BLOCKED",
|
||||
report: m2Smoke,
|
||||
status: m2Smoke.runtimeSmoke?.status ?? primaryStatus(m2Smoke),
|
||||
category: "public-entrypoints-read-only",
|
||||
evidence: publicEndpointEvidence(m2Smoke),
|
||||
commands: m2Smoke.devPreconditions?.commands ?? [],
|
||||
summary: m2EndpointLive
|
||||
? "Read-only probes prove the frozen public DEV entrypoints on :16666/:16667 are reachable; this is EDGE/ROUTE evidence only."
|
||||
: "The active M2 public endpoint smoke does not prove the frozen :16666/:16667 entrypoints."
|
||||
}),
|
||||
reportEvidence({
|
||||
milestone: "M2",
|
||||
level: edgeLive ? "DEV-LIVE" : "SOURCE",
|
||||
@@ -388,6 +442,7 @@ function collectM2Evidence(reports) {
|
||||
function collectM3Evidence(reports) {
|
||||
const m3 = reports.devM3Hardware;
|
||||
const m5Milestone = reports.devM5Gate.milestones?.find((item) => item.id === "M3");
|
||||
const liveSummary = m3TrustedLoopSummary(m3);
|
||||
return [
|
||||
reportEvidence({
|
||||
milestone: "M3",
|
||||
@@ -422,11 +477,20 @@ function collectM3Evidence(reports) {
|
||||
`evidenceId=${m3.liveOperation?.evidenceId ?? "not_observed"}`
|
||||
],
|
||||
commands: ["node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"],
|
||||
summary: m3.liveOperation?.summary ?? "No live M3 hardware operation was observed."
|
||||
summary: liveSummary
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
function m3TrustedLoopSummary(m3Report) {
|
||||
const operationSummary = m3Report.liveOperation?.summary ?? "No live M3 hardware operation was observed.";
|
||||
const blockerSummary = m3Report.blockers?.find((blocker) => blocker.scope === "m3-hardware-loop-runtime")?.summary;
|
||||
if (statusIsPass(m3Report.liveOperation?.status) || !blockerSummary) {
|
||||
return operationSummary;
|
||||
}
|
||||
return `${operationSummary} Blocker: ${blockerSummary}.`;
|
||||
}
|
||||
|
||||
function collectM4Evidence(reports) {
|
||||
const m4 = reports.devM4Agent;
|
||||
return [
|
||||
@@ -537,6 +601,25 @@ function hasCurrentLiveEdgeEvidence(edgeReport) {
|
||||
publicHttp.some((probe) => probe.url === `${DEV_ENDPOINT}/health/live` && probe.ok === true && probe.status === 200);
|
||||
}
|
||||
|
||||
function hasCurrentM2EndpointEvidence(m2Report) {
|
||||
const probes = m2Report?.runtimeSmoke?.probes ?? [];
|
||||
return m2Report?.endpoint === DEV_ENDPOINT &&
|
||||
m2Report?.frontendEndpoint === DEV_FRONTEND_ENDPOINT &&
|
||||
m2Report?.runtimeSmoke?.mode === "live-read-only" &&
|
||||
m2Report?.runtimeSmoke?.status === "pass" &&
|
||||
probes.some((probe) => probe.url === `${DEV_ENDPOINT}/health` && probe.ok === true && probe.status === 200) &&
|
||||
probes.some((probe) => probe.url === `${DEV_ENDPOINT}/health/live` && probe.ok === true && probe.status === 200) &&
|
||||
probes.some((probe) => probe.url === `${DEV_FRONTEND_ENDPOINT}/` && probe.ok === true && probe.status === 200);
|
||||
}
|
||||
|
||||
function publicEndpointEvidence(m2Report) {
|
||||
return (m2Report?.runtimeSmoke?.probes ?? []).map((probe) => {
|
||||
const identity = probe.json?.serviceId ?? probe.title ?? "unknown";
|
||||
const status = probe.json?.status ?? (probe.ok ? "ok" : "failed");
|
||||
return `${probe.url} -> HTTP ${probe.status ?? "none"} identity=${identity} status=${status}`;
|
||||
});
|
||||
}
|
||||
|
||||
function isStaleLegacyIngressBlocker(blocker, sourceReport, reports) {
|
||||
const text = `${blocker.scope ?? ""} ${blocker.summary ?? ""}`;
|
||||
const staleLegacyPort = text.includes(deprecatedLegacyPublicEndpoint);
|
||||
@@ -571,6 +654,7 @@ function deriveMilestones(evidence, blockers) {
|
||||
const liveItems = items.filter((item) => item.level === "DEV-LIVE");
|
||||
const highestVisibleLevel = levels.findLast((level) => items.some((item) => item.level === level && level !== "BLOCKED" && statusHasEvidence(item.status))) ?? "BLOCKED";
|
||||
const hasPassingLive = liveItems.some((item) => statusIsPass(item.status));
|
||||
const hasBlockedEvidence = items.some((item) => item.level === "BLOCKED" && ["blocked", "failed", "degraded"].includes(item.status));
|
||||
const milestoneBlockers = blockers.filter((blocker) => {
|
||||
if (milestone === "M2") return blocker.unblocks.includes(issue(33)) || blocker.unblocks.includes(issue(35)) || blocker.unblocks.includes(issue(36));
|
||||
if (milestone === "M3") return blocker.unblocks.includes(issue(38));
|
||||
@@ -578,7 +662,7 @@ function deriveMilestones(evidence, blockers) {
|
||||
if (milestone === "M5") return blocker.unblocks.includes(issue(39));
|
||||
return false;
|
||||
});
|
||||
const status = milestoneBlockers.length > 0 || (liveItems.length > 0 && !hasPassingLive)
|
||||
const status = milestoneBlockers.length > 0 || hasBlockedEvidence || (liveItems.length > 0 && !hasPassingLive)
|
||||
? "blocked"
|
||||
: worstStatus(items.map((item) => item.status));
|
||||
|
||||
@@ -642,7 +726,16 @@ function buildDoD(reports, milestones, blockers) {
|
||||
const preflight = reports.devPreflight;
|
||||
const artifactIdentity = preflight.artifactIdentity;
|
||||
const d601 = reports.d601Observability;
|
||||
const d601HasClient = d601.environment?.binaries?.kubectl?.available === true ||
|
||||
d601.environment?.binaries?.k3s?.available === true;
|
||||
const edgeLive = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth);
|
||||
const artifactCurrent = artifactIdentity.publishVerified === true &&
|
||||
artifactIdentity.targetCoverage?.covered !== false &&
|
||||
artifactIdentity.artifactCatalog?.matchesTarget !== false;
|
||||
const cloudApiDb = cloudApiDbStatus(reports);
|
||||
const dbReady = cloudApiDb.ready === true && cloudApiDb.connected === true && cloudApiDb.liveDbEvidence === true;
|
||||
const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status);
|
||||
const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status);
|
||||
|
||||
return {
|
||||
status: blockers.length === 0 && milestones.every((item) => item.status === "pass") ? "green" : "blocked",
|
||||
@@ -662,9 +755,9 @@ function buildDoD(reports, milestones, blockers) {
|
||||
},
|
||||
{
|
||||
id: "artifact-publish-digests",
|
||||
status: artifactIdentity.publishVerified ? "pass" : "blocked",
|
||||
evidenceLevel: artifactIdentity.publishVerified ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: `artifactState=${artifactIdentity.artifactCatalog.artifactState}, ciPublished=${artifactIdentity.artifactCatalog.ciPublished}, registryVerified=${artifactIdentity.artifactCatalog.registryVerified}, sha256=${artifactIdentity.artifactCatalog.digestCounts.sha256}, not_published=${artifactIdentity.artifactCatalog.digestCounts.notPublished}`
|
||||
status: artifactCurrent ? "pass" : "blocked",
|
||||
evidenceLevel: artifactCurrent ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: `artifactState=${artifactIdentity.artifactCatalog.artifactState}, ciPublished=${artifactIdentity.artifactCatalog.ciPublished}, registryVerified=${artifactIdentity.artifactCatalog.registryVerified}, sha256=${artifactIdentity.artifactCatalog.digestCounts.sha256}, not_published=${artifactIdentity.artifactCatalog.digestCounts.notPublished}, targetCovered=${artifactIdentity.targetCoverage?.covered ?? "unknown"}`
|
||||
},
|
||||
{
|
||||
id: "d601-k3s-observability",
|
||||
@@ -672,21 +765,39 @@ function buildDoD(reports, milestones, blockers) {
|
||||
evidenceLevel: d601.cluster?.readable ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: d601.cluster?.readable
|
||||
? "D601 hwlab-dev cluster was readable."
|
||||
: "D601 runner lacks kubectl/k3s/kubeconfig observability for hwlab-dev."
|
||||
: d601HasClient
|
||||
? "D601 kubectl/k3s clients are present, but read-only hwlab-dev pods/services/configmaps probes are still blocked."
|
||||
: "D601 runner lacks kubectl/k3s/kubeconfig observability for hwlab-dev."
|
||||
},
|
||||
{
|
||||
id: "dev-edge-frp-16667",
|
||||
status: edgeLive && !hasBlocker(blockers, isEdgeOrFrpBlocker) ? "pass" : "blocked",
|
||||
evidenceLevel: edgeLive && !hasBlocker(blockers, isEdgeOrFrpBlocker) ? "DEV-LIVE" : "BLOCKED",
|
||||
status: edgeLive && !hasBlocker(blockers, isRouteTransportBlocker) ? "pass" : "blocked",
|
||||
evidenceLevel: edgeLive && !hasBlocker(blockers, isRouteTransportBlocker) ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: edgeLive
|
||||
? "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; old preflight network probe blockers are stale."
|
||||
? "Committed edge report proves read-only public HTTP on :16667 /health and /health/live; this is route evidence, not DB/M3/M4/M5 acceptance."
|
||||
: "No committed report proves live HTTP 200/JSON on http://74.48.78.17:16667."
|
||||
},
|
||||
{
|
||||
id: "cloud-api-db-ready",
|
||||
status: hasBlocker(blockers, (blocker) => blocker.scope.includes("cloud-api-db")) ? "blocked" : "pass",
|
||||
evidenceLevel: "BLOCKED",
|
||||
summary: "Manifest-level DB env exists, but live DB health readiness is still blocked/missing."
|
||||
status: dbReady ? "pass" : "blocked",
|
||||
evidenceLevel: dbReady ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: `cloud-api DB status=${cloudApiDb.status}; ready=${cloudApiDb.ready}; connected=${cloudApiDb.connected}; liveDbEvidence=${cloudApiDb.liveDbEvidence}.`
|
||||
},
|
||||
{
|
||||
id: "m3-hardware-trusted-loop",
|
||||
status: m3Live ? "pass" : "blocked",
|
||||
evidenceLevel: m3Live ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: m3Live
|
||||
? "M3 hardware trusted loop has operation, trace, audit, and evidence identifiers."
|
||||
: "M3 trusted loop is blocked until res_boxsimu_1:DO1 -> patch-panel -> res_boxsimu_2:DI1 is proven with operation/trace/audit/evidence."
|
||||
},
|
||||
{
|
||||
id: "m4-agent-loop-live",
|
||||
status: m4Live ? "pass" : "blocked",
|
||||
evidenceLevel: m4Live ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: m4Live
|
||||
? "M4 agent loop live preflight passed."
|
||||
: "M4 agent loop live path is blocked before accepted agent scheduling/evidence closure."
|
||||
},
|
||||
{
|
||||
id: "m5-mvp-dev-live",
|
||||
@@ -698,6 +809,99 @@ function buildDoD(reports, milestones, blockers) {
|
||||
};
|
||||
}
|
||||
|
||||
function cloudApiDbStatus(reports) {
|
||||
const db = reports.devEdgeHealth.edgeHealth?.contracts?.deploy?.cloudApiDb ?? {};
|
||||
return {
|
||||
status: db.status ?? "unknown",
|
||||
ready: db.ready === true,
|
||||
connected: db.connected === true || db.liveConnected === true,
|
||||
configReady: db.configReady === true,
|
||||
connectionChecked: db.connectionChecked === true,
|
||||
liveDbEvidence: db.liveDbEvidence === true
|
||||
};
|
||||
}
|
||||
|
||||
function buildCurrentDevLayering(reports, blockers) {
|
||||
const m2EndpointLive = hasCurrentM2EndpointEvidence(reports.devM2Smoke);
|
||||
const edgeLive = hasCurrentLiveEdgeEvidence(reports.devEdgeHealth);
|
||||
const cloudDb = cloudApiDbStatus(reports);
|
||||
const dbReady = cloudDb.ready && cloudDb.connected && cloudDb.liveDbEvidence;
|
||||
const m3Live = statusIsPass(reports.devM3Hardware.liveOperation?.status);
|
||||
const m4Live = statusIsPass(reports.devM4Agent.livePreflight?.status);
|
||||
const artifactIdentity = reports.devPreflight.artifactIdentity;
|
||||
const artifactCurrent = artifactIdentity.publishVerified === true &&
|
||||
artifactIdentity.targetCoverage?.covered !== false &&
|
||||
artifactIdentity.artifactCatalog?.matchesTarget !== false;
|
||||
const desired = reports.devDeploy.devDeployApply ?? {};
|
||||
const artifactSourceStates = artifactSourceStateCounts(reports.devArtifacts);
|
||||
|
||||
return {
|
||||
edgeRoute: {
|
||||
label: "EDGE/ROUTE live",
|
||||
status: m2EndpointLive || edgeLive ? "pass" : "blocked",
|
||||
evidenceLevel: m2EndpointLive || edgeLive ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: m2EndpointLive
|
||||
? "16666 browser, 16667 /health, and 16667 /health/live returned accepted HWLAB DEV responses in the active M2 read-only smoke."
|
||||
: "No active read-only public endpoint report proves both 16666 and 16667.",
|
||||
evidence: publicEndpointEvidence(reports.devM2Smoke),
|
||||
nextRequired: "Keep this separated from DB readiness, M3/M4 loop evidence, and M5 acceptance."
|
||||
},
|
||||
dbLive: {
|
||||
label: "DB live/degraded",
|
||||
status: dbReady ? "pass" : "blocked",
|
||||
evidenceLevel: dbReady ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: `cloud-api DB status=${cloudDb.status}; configReady=${cloudDb.configReady}; ready=${cloudDb.ready}; connected=${cloudDb.connected}; liveDbEvidence=${cloudDb.liveDbEvidence}.`,
|
||||
evidence: reports.devM5Gate.devPreconditions?.evidence?.filter((line) => line.includes("/health/live") || line.includes("DB")) ?? [],
|
||||
nextRequired: "Provide live DB connection evidence through redacted health output; route reachability alone is insufficient."
|
||||
},
|
||||
m3HardwareTrustedLoop: {
|
||||
label: "M3 hardware trusted loop",
|
||||
status: m3Live ? "pass" : "blocked",
|
||||
evidenceLevel: m3Live ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: m3TrustedLoopSummary(reports.devM3Hardware),
|
||||
evidence: [
|
||||
`operationId=${reports.devM3Hardware.liveOperation?.operationId ?? "not_observed"}`,
|
||||
`traceId=${reports.devM3Hardware.liveOperation?.traceId ?? "not_observed"}`,
|
||||
`auditId=${reports.devM3Hardware.liveOperation?.auditId ?? "not_observed"}`,
|
||||
`evidenceId=${reports.devM3Hardware.liveOperation?.evidenceId ?? "not_observed"}`
|
||||
],
|
||||
nextRequired: "Only a real DEV res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 observation with operation/trace/audit/evidence can clear M3."
|
||||
},
|
||||
m4AgentLoop: {
|
||||
label: "M4 agent loop",
|
||||
status: m4Live ? "pass" : "blocked",
|
||||
evidenceLevel: m4Live ? "DEV-LIVE" : "BLOCKED",
|
||||
summary: reports.devM4Agent.livePreflight?.summary ?? reports.devM4Agent.devPreconditions?.summary ?? "No live M4 observation was recorded.",
|
||||
evidence: reports.devM4Agent.livePreflight?.evidence ?? [],
|
||||
nextRequired: "Do not schedule or claim the agent loop as live until DB live and required runtime/evidence preconditions pass."
|
||||
},
|
||||
artifactDesiredStateSource: {
|
||||
label: "artifact/desired-state source",
|
||||
status: artifactCurrent && desired.conclusion?.status === "ready" ? "pass" : "blocked",
|
||||
evidenceLevel: artifactCurrent ? "SOURCE" : "BLOCKED",
|
||||
summary: `artifact targetCovered=${artifactIdentity.targetCoverage?.covered ?? "unknown"}; artifactSource=${short(artifactIdentity.artifactSource?.commitId)}; target=${short(artifactIdentity.source?.commitId)}; desiredApplyMode=${desired.mode ?? "unknown"}; mutationAttempted=${desired.mutationAttempted === true}.`,
|
||||
evidence: [
|
||||
`artifactState=${artifactIdentity.artifactCatalog?.artifactState ?? "unknown"}`,
|
||||
`sourceStates=${artifactSourceStates.sourcePresent} source-present, ${artifactSourceStates.intentionallyDisabled} intentionally-disabled`,
|
||||
`desiredState=${desired.conclusion?.status ?? "not_reported"} dry-run-only`
|
||||
],
|
||||
nextRequired: "Refresh artifact/source coverage for current origin/main and keep desired-state apply separate from read-only route proof."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function artifactSourceStateCounts(artifactReport) {
|
||||
const counts = { sourcePresent: 0, intentionallyDisabled: 0 };
|
||||
for (const service of artifactReport.artifactPublish?.services ?? []) {
|
||||
if (service.sourceState === "source-present") {
|
||||
counts.sourcePresent += 1;
|
||||
} else if (service.sourceState === "intentionally-disabled") {
|
||||
counts.intentionallyDisabled += 1;
|
||||
}
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function buildNextSteps(blockers) {
|
||||
const byOrder = new Map();
|
||||
for (const blocker of blockers) {
|
||||
@@ -737,6 +941,8 @@ function fallbackAction(scope) {
|
||||
if (scope.includes("artifact")) return "Publish DEV artifacts for every frozen HWLAB service and record immutable registry digests.";
|
||||
if (scope.includes("edge") || scope.includes("ingress") || scope.includes("frp")) return "Repair frp/master-edge/D601 router path and rerun read-only DEV edge health.";
|
||||
if (scope.includes("cloud-api-db")) return "Configure DEV cloud-api DB env readiness and rerun health/preflight without exposing secrets.";
|
||||
if (scope === "db-live") return "Repair DEV cloud-api DB live readiness, then rerun the read-only health and M4 preflight reports without exposing secret values.";
|
||||
if (scope === "m3-hardware-loop-runtime") return "Prove the real DEV M3 trusted loop res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1 with operation, trace, audit, and evidence identifiers.";
|
||||
if (scope.includes("kubectl") || scope.includes("k3s")) return "Provide read-only kubectl/kubeconfig observability for hwlab-dev.";
|
||||
return "Resolve the blocker and attach source/local/dry-run/DEV-live evidence at the correct level.";
|
||||
}
|
||||
@@ -747,6 +953,8 @@ function evidenceRequiredFor(scope) {
|
||||
if (scope.includes("artifact") || scope === "ghcr") return "Artifact publish report with ciPublished=true, registryVerified=true, and sha256 digest for each frozen service ID.";
|
||||
if (scope.includes("kubectl") || scope.includes("k3s")) return "Read-only kubectl/k3s report proving pods/services/configmaps are observable in hwlab-dev without reading Secrets.";
|
||||
if (scope.includes("cloud-api-db")) return "Cloud API health/live output showing DB env ready and redacted secret references, without secret material.";
|
||||
if (scope === "db-live") return "Cloud API /health/live output with ready=true, connected=true, liveDbEvidence=true, and redacted secret references.";
|
||||
if (scope === "m3-hardware-loop-runtime") return "Operation, trace, audit, and evidence IDs from the real DEV DO1 -> patch-panel -> DI1 trusted loop.";
|
||||
if (scope.includes("edge") || scope.includes("ingress") || scope.includes("frp")) return "Read-only DEV route observation for :16667/frp/edge/router with HWLAB service identity and artifact identity.";
|
||||
return "A committed report with the exact evidence level and command used.";
|
||||
}
|
||||
@@ -787,6 +995,7 @@ export async function buildReport() {
|
||||
const blockers = collectBlockers(reports);
|
||||
const milestones = deriveMilestones(evidence, blockers);
|
||||
const dod = buildDoD(reports, milestones, blockers);
|
||||
const currentDevLayering = buildCurrentDevLayering(reports, blockers);
|
||||
const nextSteps = buildNextSteps(blockers);
|
||||
const report = {
|
||||
"$schema": "https://hwlab.pikastech.local/schemas/dev-m5-gate-aggregator-v2.schema.json",
|
||||
@@ -794,7 +1003,7 @@ export async function buildReport() {
|
||||
reportVersion: "v2",
|
||||
reportKind: "dev-m5-gate-aggregator",
|
||||
issue: issue(58),
|
||||
supports: [issue(7), issue(9), issue(31), issue(33), issue(34), issue(35), issue(36), issue(37), issue(38), issue(39), issue(46)],
|
||||
supports: [issue(7), issue(9), issue(23), issue(26), issue(31), issue(33), issue(34), issue(35), issue(36), issue(37), issue(38), issue(39), issue(46), issue(64)],
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedFromCommit: gitCommit(),
|
||||
environment: ENVIRONMENT_DEV,
|
||||
@@ -832,9 +1041,10 @@ export async function buildReport() {
|
||||
green: dod.green,
|
||||
reason: dod.green
|
||||
? "All #9 DEV DoD checks are green."
|
||||
: "SOURCE, LOCAL, DRY-RUN, and edge DEV-LIVE evidence exists, but acceptance is still blocked by artifact, observability, DB, and loop evidence gaps."
|
||||
: "EDGE/ROUTE DEV-LIVE evidence exists on :16666/:16667, but M5 remains blocked by artifact source drift, DB live degradation, missing M3 trusted loop operation evidence, and blocked M4 agent-loop preflight."
|
||||
},
|
||||
dod,
|
||||
currentDevLayering,
|
||||
milestones,
|
||||
evidence,
|
||||
levels: groupByLevel(evidence, blockers),
|
||||
@@ -862,6 +1072,9 @@ export function formatCheckSummary(report) {
|
||||
}
|
||||
|
||||
export function renderMarkdown(report) {
|
||||
const layeringLines = Object.entries(report.currentDevLayering)
|
||||
.map(([, layer]) => `| ${layer.label} | ${layer.status} | ${layer.evidenceLevel} | ${layer.summary} | ${layer.nextRequired} |`)
|
||||
.join("\n");
|
||||
const milestoneLines = report.milestones
|
||||
.map((milestone) => `| ${milestone.id} | ${milestone.status} | ${milestone.highestVisibleLevel} | ${milestone.liveEvidence} | ${milestone.summary} |`)
|
||||
.join("\n");
|
||||
@@ -885,6 +1098,12 @@ Scope: DEV only, report-only
|
||||
|
||||
${report.overall.reason}
|
||||
|
||||
## Current DEV Layering
|
||||
|
||||
| Layer | Status | Evidence level | Current conclusion | Required next proof |
|
||||
| --- | --- | --- | --- | --- |
|
||||
${layeringLines}
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | Status | Highest visible level | Live evidence | Summary |
|
||||
|
||||
Reference in New Issue
Block a user