518 lines
17 KiB
JavaScript
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.
|
|
`;
|
|
}
|