Files
pikasTech-HWLAB/scripts/src/dev-evidence-blocker-aggregator.mjs
T

518 lines
17 KiB
JavaScript

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/m1-contract-smoke.mjs"],
commands: ["node scripts/m1-contract-smoke.mjs"],
summary: "Local skeleton smoke remains local-only evidence."
}),
sourceEntry({
milestone: "M5",
issue: issue(411),
category: "repository-governance",
level: "SOURCE",
status: "pass",
sources: ["scripts/repo-reports-guard.mjs", "package.json", "scripts/g14-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.
`;
}