import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs"; import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs"; export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const blockerTypes = new Set([ "contract_blocker", "environment_blocker", "network_blocker", "runtime_blocker", "agent_blocker", "observability_blocker", "safety_blocker" ]); const validationCommands = Object.freeze([ "node scripts/repo-reports-guard.mjs", "node --check scripts/dev-evidence-blocker-aggregator.mjs", "node --check scripts/src/dev-evidence-blocker-aggregator.mjs", "node scripts/dev-evidence-blocker-aggregator.mjs --check", "node scripts/dev-evidence-blocker-aggregator.mjs --markdown", "node --check scripts/validate-dev-gate-report.mjs", "node scripts/validate-dev-gate-report.mjs" ]); function issue(id) { return `pikasTech/HWLAB#${id}`; } function gitCommit() { try { return execFileSync("git", ["rev-parse", "--short=12", "HEAD"], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim(); } catch { return "unknown"; } } function sourceEntry({ milestone, issue: issueId, level, status, category, sources, commands, summary }) { return { milestone, issue: issueId, level, status, category, lifecycleState: "active", sources, commands, summary }; } function groupByLevel(evidence, blockers) { const grouped = Object.fromEntries(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED"].map((level) => [level, []])); for (const entry of evidence) { grouped[entry.level]?.push({ milestone: entry.milestone, category: entry.category, status: entry.status, summary: entry.summary }); } for (const blocker of blockers) { grouped.BLOCKED.push({ milestone: blocker.milestone ?? "M5", category: blocker.scope, status: blocker.status, summary: blocker.summary }); } return grouped; } function governanceBlocker() { return { id: "repo-report-directory:removed", priority: "P0", unblockOrder: 1, type: "observability_blocker", scope: "repo-report-directory-removed", status: "open", sourceIssue: issue(411), summary: "The repository report directory has been removed; durable progress and acceptance evidence must be recorded in GitHub issue or PR comments, with temporary JSON limited to /tmp, .state, or CI artifacts.", nextTask: "Rerun any live or CD checks with explicit non-repository output paths and post the operational conclusion to the owning issue or PR." }; } function runtimeDurableBlocker() { return { id: "m4:runtime-durable-adapter", priority: "P1", unblockOrder: 2, type: "runtime_blocker", scope: "runtime-durable-adapter", status: "open", sourceIssue: issue(164), summary: "M4/M5 remain blocked until cloud-api durable runtime readiness proves schema, migration, and read-query durability; DB live evidence alone is not acceptance.", nextTask: "Repair runtime durability and rerun the read-only M4/M5 checks with non-repository output." }; } function skillsBlocker() { return { id: "m4:skills-commit-version-injection", priority: "P1", unblockOrder: 3, type: "agent_blocker", scope: "skills-commit-version-injection", status: "open", sourceIssue: issue(164), summary: "M4/M5 agent evidence still requires deployed manager, skills, and worker paths to expose explicit skills commit and version.", nextTask: "Inject and surface skills commit/version on the live DEV agent stack before claiming agent-loop acceptance." }; } function hardwareLoopBlocker() { return { id: "m3:m3-hardware-loop-runtime", priority: "P0", unblockOrder: 4, type: "runtime_blocker", scope: "m3-hardware-loop-runtime", status: "open", sourceIssue: issue(38), summary: "M3 remains blocked until the real DEV hardware trusted loop proves DO1 to patch-panel to DI1 with operation, trace, audit, and evidence identifiers.", nextTask: "Run the DEV M3 trusted-loop smoke after topology repair and record the conclusion on the owning GitHub issue." }; } function buildDoD(blockers) { const checks = [ { id: "repo-report-directory-absent", status: "pass", evidenceLevel: "SOURCE", summary: "Repository report directory is forbidden and guarded by repo checks." }, { id: "cloud-api-db-ready", status: "blocked", evidenceLevel: "BLOCKED", summary: "Current checked source cannot claim DB readiness from deleted repository report files; rerun live readiness and post the result outside the repo." }, { id: "runtime-durable-ready", status: "blocked", evidenceLevel: "BLOCKED", summary: "Durable adapter migration/query readiness still blocks M4/M5 promotion." }, { id: "m3-trusted-loop-ready", status: "blocked", evidenceLevel: "BLOCKED", summary: "M3 trusted-loop live operation evidence is still required." } ]; return { status: blockers.length === 0 ? "pass" : "blocked", green: blockers.length === 0, checks }; } function buildCurrentDevLayering() { return { publicRoute: { label: "DEV public route", status: "blocked", evidenceLevel: "BLOCKED", summary: "Repository-stored route snapshots were removed; only live checks or issue/PR comments may establish current route state.", nextRequired: "Run read-only route checks with /tmp, .state, or CI artifact output." }, dbLive: { label: "Cloud API DB live", status: "blocked", evidenceLevel: "BLOCKED", summary: "Deleted repository report files cannot be used as DB-live acceptance evidence.", nextRequired: "Rerun the current read-only health check and record the conclusion in GitHub." }, runtimeDurability: { label: "Runtime durability", status: "blocked", evidenceLevel: "BLOCKED", summary: "Durable runtime adapter readiness remains the current M4/M5 blocker.", nextRequired: "Prove schema, migration, and read-query readiness." }, skillsInjection: { label: "Skills commit/version injection", status: "blocked", evidenceLevel: "BLOCKED", summary: "Skills commit/version visibility remains required before agent-loop promotion.", nextRequired: "Expose skills commit and version through live DEV manager/worker health." }, m3TrustedLoop: { label: "M3 trusted loop", status: "blocked", evidenceLevel: "BLOCKED", summary: "M3 must prove the real trusted loop through HWLAB runtime, not a generated file.", nextRequired: "Capture operation, trace, audit, and evidence identifiers in the owning issue." } }; } function buildMilestones() { return [ { id: "M0", status: "pass", highestVisibleLevel: "SOURCE", liveEvidence: false, summary: "Source contracts remain parseable." }, { id: "M1", status: "pass", highestVisibleLevel: "LOCAL", liveEvidence: false, summary: "Local smoke remains source/local only." }, { id: "M2", status: "blocked", highestVisibleLevel: "BLOCKED", liveEvidence: false, summary: "M2 live route and artifact conclusions must be rerun without repository report outputs." }, { id: "M3", status: "blocked", highestVisibleLevel: "BLOCKED", liveEvidence: false, summary: "M3 trusted-loop live evidence remains required." }, { id: "M4", status: "blocked", highestVisibleLevel: "BLOCKED", liveEvidence: false, summary: "M4 remains blocked by durable runtime readiness and skills injection." }, { id: "M5", status: "blocked", highestVisibleLevel: "BLOCKED", liveEvidence: false, summary: "M5 cannot promote until M3/M4 blockers clear and current live checks are recorded outside the repo." } ]; } function buildMilestoneLevelClassification(milestones) { return milestones.map((milestone) => ({ milestone: milestone.id, currentLevel: milestone.highestVisibleLevel, status: milestone.status, strongestEvidenceLevel: milestone.highestVisibleLevel, liveEvidence: milestone.liveEvidence, summary: milestone.summary })); } function buildMilestoneBlockerClassification() { return [ { milestone: "M3", status: "blocked", currentLevel: "BLOCKED", blockerClass: "m3-hardware-loop-runtime", dependency: "M3 trusted hardware loop operation, trace, audit, and evidence identifiers", nonPromotionReason: "M3 live trusted-loop evidence is not currently established after repository report removal.", nextRequired: "Repair topology and rerun the live M3 trusted-loop smoke with non-repository output." }, { milestone: "M4", status: "blocked", currentLevel: "BLOCKED", blockerClass: "runtime_durable_adapter_query_blocked", dependency: "durable runtime adapter readiness and skill commit and version injection", nonPromotionReason: "M4 cannot promote without durable agent runtime readiness and skill version injection.", nextRequired: "Repair runtime durability and skills metadata, then rerun M4 read-only preflight." }, { milestone: "M5", status: "blocked", currentLevel: "BLOCKED", blockerClass: "m3-m4-current-blockers", dependency: "runtime durable adapter readiness, skills commit/version injection, and M3 trusted-loop evidence", nonPromotionReason: "M5 cannot promote while M3/M4 blockers remain open and current evidence is not recorded in GitHub.", nextRequired: "Clear M3/M4 blockers and post live DEV acceptance conclusions to the owning issues or PR." } ]; } function buildEvidence() { return [ sourceEntry({ milestone: "M0", issue: issue(31), category: "contract", level: "SOURCE", status: "pass", sources: ["protocol/README.md", "docs/reference/MVP-e2e-acceptance.md"], commands: ["node scripts/validate-contract.mjs"], summary: "Frozen contracts remain the source-ready baseline." }), sourceEntry({ milestone: "M1", issue: issue(7), category: "local-smoke", level: "LOCAL", status: "pass", sources: ["scripts/validate-contract.mjs"], commands: ["node scripts/validate-contract.mjs"], summary: "Contract validation remains local-only source evidence." }), sourceEntry({ milestone: "M5", issue: issue(411), category: "repository-governance", level: "SOURCE", status: "pass", sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/gitops-render.mjs"], commands: ["node scripts/repo-reports-guard.mjs"], summary: "Repository report files are removed and guarded against recurrence." }) ]; } function buildNextSteps(blockers) { return blockers.map((blocker, index) => ({ order: index + 1, action: blocker.nextTask, evidenceRequired: "Post the real operational conclusion in the owning GitHub issue or PR; do not commit generated report files." })); } export async function buildReport() { const blockers = [ governanceBlocker(), runtimeDurableBlocker(), skillsBlocker(), hardwareLoopBlocker() ]; const milestones = buildMilestones(); const evidence = buildEvidence(); const dod = buildDoD(blockers); const currentDevLayering = buildCurrentDevLayering(); const milestoneLevelClassification = buildMilestoneLevelClassification(milestones); const milestoneBlockerClassification = buildMilestoneBlockerClassification(); const report = { "$schema": "https://hwlab.pikastech.local/schemas/dev-m5-gate-aggregator-v2.schema.json", "$id": "https://hwlab.pikastech.local/dev-m5-gate-aggregator-v2.json", reportVersion: "v2", reportKind: "dev-m5-gate-aggregator", issue: issue(58), supports: [issue(7), issue(9), issue(23), issue(31), issue(37), issue(38), issue(39), issue(58), issue(411)], generatedAt: new Date().toISOString(), generatedFromCommit: gitCommit(), environment: ENVIRONMENT_DEV, endpoint: DEV_ENDPOINT, frontendEndpoint: DEV_FRONTEND_ENDPOINT, activeEndpoints: { frontend: `${DEV_FRONTEND_ENDPOINT}/`, apiLive: `${DEV_ENDPOINT}/health/live` }, devOnly: true, prodDisabled: true, reportLifecycle: activeReportLifecycle( "Current aggregator is governance-only and does not persist evidence in repository report files." ), safety: { reportOnly: true, noDeploy: true, noProd: true, noSecretRead: true, noRuntimeRestart: true, noLiveProbe: true, noHeavyE2E: true, noRepoReportDirectory: true, noUniDeskRuntimeSubstitute: true }, sourceReports: { status: "removed", policy: "Generated progress and acceptance snapshots are not source files. Use GitHub issue or PR comments plus temporary /tmp, .state, or CI artifact output." }, overall: { status: "blocked", green: false, reason: "The old repository report mechanism has been removed. M3/M4/M5 still require real DEV checks and GitHub-hosted conclusions before any green acceptance claim." }, latestFrontendDevFact: { revision: "not_claimed_by_repo_report", issue: issue(7), supports: [issue(7), issue(411)], endpoint: `${DEV_FRONTEND_ENDPOINT}/`, evidenceLevel: "BLOCKED", promotesM3M4M5: false, summary: "Frontend visibility must be established by current live checks or issue/PR comments, not by committed report snapshots." }, dod, currentDevLayering, milestoneLevelClassification, milestoneBlockerClassification, milestones, evidence, levels: groupByLevel(evidence, blockers), blockers, nextSteps: buildNextSteps(blockers), validationCommands }; validateReport(report); return report; } export function validateReport(report) { assert.equal(report.reportKind, "dev-m5-gate-aggregator"); assert.equal(report.overall.status, "blocked"); assert.equal(report.overall.green, false); assert.deepEqual( report.milestoneBlockerClassification.map((item) => item.milestone), ["M3", "M4", "M5"], "milestoneBlockerClassification" ); assert.ok(report.blockers.length >= 1, "must remain blocked without live DEV acceptance"); for (const blocker of report.blockers) { assert.ok(blockerTypes.has(blocker.type), `unknown blocker type ${blocker.type}`); assert.match(blocker.priority, /^P[0-3]$/u, `invalid priority ${blocker.priority}`); assert.equal(blocker.status, "open", "blocker status"); } } export function formatCheckSummary(report) { return { ok: true, taskId: report.reportKind, issue: report.issue, status: report.overall.status, green: report.overall.green, blockers: report.blockers.length, activeEndpoints: report.activeEndpoints, m3m4m5: Object.fromEntries( report.milestoneBlockerClassification.map((item) => [item.milestone, item.status]) ), nextStep: report.nextSteps[0]?.action ?? null, levels: Object.fromEntries( Object.entries(report.levels).map(([level, entries]) => [level, entries.length]) ) }; } 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.milestoneBlockerClassification .map((item) => `| ${item.milestone} | ${item.status} | ${item.currentLevel} | ${item.blockerClass} | ${item.dependency} | ${item.nextRequired} |`) .join("\n"); const blockerLines = report.blockers .map((blocker) => `| ${blocker.priority} | ${blocker.unblockOrder} | ${blocker.type} | ${blocker.scope} | ${blocker.summary} |`) .join("\n"); const dodLines = report.dod.checks .map((check) => `| ${check.id} | ${check.status} | ${check.evidenceLevel} | ${check.summary} |`) .join("\n"); return `# HWLAB M5 DEV Gate Aggregator v2 Status: ${report.overall.status} Generated from: \`${report.generatedFromCommit}\` Scope: DEV only, no repository report output Active frontend: \`${report.activeEndpoints.frontend}\` Active API/live: \`${report.activeEndpoints.apiLive}\` ## Summary ${report.overall.reason} ## Current DEV Layering | Layer | Status | Evidence level | Current conclusion | Required next proof | | --- | --- | --- | --- | --- | ${layeringLines} ## DoD Checks | Check | Status | Evidence level | Summary | | --- | --- | --- | --- | ${dodLines} ## M3/M4/M5 Blocker Classification | Milestone | Status | Current level | Blocker class | Dependency | Required next proof | | --- | --- | --- | --- | --- | --- | ${milestoneLines} ## Blockers | Priority | Order | Type | Scope | Summary | | --- | ---: | --- | --- | --- | ${blockerLines} ## Validation ${report.validationCommands.map((command) => `- \`${command}\``).join("\n")} ## Boundary This aggregator is governance-only. It reads source contracts, does not deploy, does not call DEV or PROD, does not read secrets, and does not persist generated evidence in the repository. `; }