kibi-cli 2.31.1 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-register-proof.d.ts.map +1 -1
- package/dist/cli-register-proof.js +19 -7
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +2 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +14 -5
- package/dist/commands/discovery-table.d.ts.map +1 -1
- package/dist/commands/discovery-table.js +51 -4
- package/dist/commands/doctor.js +19 -4
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +89 -3
- package/dist/commands/prove.d.ts +48 -1
- package/dist/commands/prove.d.ts.map +1 -1
- package/dist/commands/prove.js +293 -8
- package/dist/commands/report.d.ts +1 -0
- package/dist/commands/report.d.ts.map +1 -1
- package/dist/commands/report.js +70 -5
- package/dist/commands/signoff.d.ts.map +1 -1
- package/dist/commands/signoff.js +20 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +20 -4
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +16 -5
- package/dist/diagnostics.js +1 -1
- package/dist/engine.js +1 -1
- package/dist/extractors/markdown.d.ts +3 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +17 -0
- package/dist/operations/bootstrap/candidate-helpers.d.ts +3 -0
- package/dist/operations/bootstrap/candidate-helpers.d.ts.map +1 -1
- package/dist/operations/bootstrap/candidate-helpers.js +4 -0
- package/dist/operations/migration/evidence-floor.d.ts +41 -0
- package/dist/operations/migration/evidence-floor.d.ts.map +1 -0
- package/dist/operations/migration/evidence-floor.js +230 -0
- package/dist/operations/migration/proof-contract-claims.d.ts +59 -0
- package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -0
- package/dist/operations/migration/proof-contract-claims.js +182 -0
- package/dist/operations/migration/schema6.d.ts +6 -0
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +94 -0
- package/dist/operations/mutation/omitted-properties.d.ts +11 -0
- package/dist/operations/mutation/omitted-properties.d.ts.map +1 -0
- package/dist/operations/mutation/omitted-properties.js +76 -0
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +1 -0
- package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
- package/dist/operations/mutation/strict-fact.js +6 -2
- package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
- package/dist/operations/mutation/symbol-granularity.js +5 -3
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +7 -1
- package/dist/operations/mutation/validation.d.ts +2 -0
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +4 -0
- package/dist/operations/proof/claim-bindings.d.ts +23 -0
- package/dist/operations/proof/claim-bindings.d.ts.map +1 -0
- package/dist/operations/proof/claim-bindings.js +211 -0
- package/dist/operations/proof/code-certificates.d.ts +135 -0
- package/dist/operations/proof/code-certificates.d.ts.map +1 -0
- package/dist/operations/proof/code-certificates.js +785 -0
- package/dist/operations/proof/code-scope.d.ts +11 -1
- package/dist/operations/proof/code-scope.d.ts.map +1 -1
- package/dist/operations/proof/code-scope.js +20 -2
- package/dist/operations/proof/compact-receipts.d.ts.map +1 -1
- package/dist/operations/proof/compact-receipts.js +4 -3
- package/dist/operations/proof/explain.d.ts +32 -0
- package/dist/operations/proof/explain.d.ts.map +1 -1
- package/dist/operations/proof/explain.js +91 -0
- package/dist/operations/proof/ingest-proof.d.ts +9 -1
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +41 -2
- package/dist/operations/proof/receipt-compaction.d.ts +2 -0
- package/dist/operations/proof/receipt-compaction.d.ts.map +1 -1
- package/dist/operations/proof/receipt-compaction.js +6 -2
- package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +4 -0
- package/dist/operations/semantic-advisor/strict-rules.js +3 -3
- package/dist/operations/semantic-advisor/types.d.ts +2 -1
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/plugins/approved-source-analyzers.generated.d.ts +6 -6
- package/dist/plugins/approved-source-analyzers.generated.js +6 -6
- package/dist/proof/command-test-report.d.ts +4 -3
- package/dist/proof/command-test-report.d.ts.map +1 -1
- package/dist/proof/command-test-report.js +4 -10
- package/dist/proof/evaluate.d.ts.map +1 -1
- package/dist/proof/evaluate.js +18 -2
- package/dist/proof/inspect.d.ts.map +1 -1
- package/dist/proof/inspect.js +58 -0
- package/dist/proof/producers/playwright-reporter.d.ts +3 -0
- package/dist/proof/producers/playwright-reporter.d.ts.map +1 -1
- package/dist/proof/producers/playwright-reporter.js +25 -2
- package/dist/proof/step-runner.d.ts +36 -0
- package/dist/proof/step-runner.d.ts.map +1 -0
- package/dist/proof/step-runner.js +234 -0
- package/dist/public/evidence-levels.d.ts +12 -0
- package/dist/public/evidence-levels.d.ts.map +1 -0
- package/dist/public/evidence-levels.js +19 -0
- package/dist/public/impact/coverage-depth-quality.d.ts +7 -0
- package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
- package/dist/public/impact/coverage-depth-quality.js +59 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +3 -1
- package/dist/public/operations/catalog.d.ts +167 -6
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts +2 -0
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +6 -0
- package/dist/public/operations/evidence-floor.d.ts +48 -0
- package/dist/public/operations/evidence-floor.d.ts.map +1 -0
- package/dist/public/operations/evidence-floor.js +143 -0
- package/dist/public/operations/migration-plan.d.ts +19 -5
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +22 -5
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +4 -1
- package/dist/public/operations/proof-contract-claims.d.ts +26 -0
- package/dist/public/operations/proof-contract-claims.d.ts.map +1 -0
- package/dist/public/operations/proof-contract-claims.js +71 -0
- package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
- package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
- package/dist/public/operations/specs/check-rules.generated.js +3 -1
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +157 -5
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +15 -2
- package/dist/public/operations/specs/proof.d.ts +9 -0
- package/dist/public/operations/specs/proof.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +19 -10
- package/dist/public/operations/specs/upsert.d.ts +157 -5
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/proof-certificates.d.ts +89 -0
- package/dist/public/proof-certificates.d.ts.map +1 -0
- package/dist/public/proof-certificates.js +207 -0
- package/dist/public/proof-protocol.d.ts +126 -3
- package/dist/public/proof-protocol.d.ts.map +1 -1
- package/dist/public/proof-protocol.js +239 -3
- package/dist/public/proof-receipt.d.ts +89 -6
- package/dist/public/proof-receipt.d.ts.map +1 -1
- package/dist/public/proof-receipt.js +83 -7
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +18 -1
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-commit-review/SKILL.md +1 -1
- package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +6 -1
- package/dist/public/skills/kibi-decisions/SKILL.md +10 -2
- package/dist/public/skills/kibi-decisions/resources/decision-records.md +6 -1
- package/dist/public/skills/kibi-freshness/SKILL.md +5 -1
- package/dist/public/skills/kibi-traceability/SKILL.md +11 -1
- package/dist/public/skills/kibi-usage/SKILL.md +6 -4
- package/dist/public/skills/kibi-usage/resources/proof.md +124 -19
- package/dist/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/dist/report/html-report.css +38 -1
- package/dist/report/html-report.d.ts +8 -0
- package/dist/report/html-report.d.ts.map +1 -1
- package/dist/report/html-report.js +141 -6
- package/dist/report/report-view.d.ts +1 -1
- package/dist/report/report-view.d.ts.map +1 -1
- package/dist/report/report-view.js +7 -0
- package/dist/schemas/entity.schema.json +212 -4
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +1 -0
- package/dist/signoff/html.d.ts.map +1 -1
- package/dist/signoff/html.js +3 -0
- package/dist/signoff/markdown.d.ts.map +1 -1
- package/dist/signoff/markdown.js +3 -0
- package/dist/signoff/model.d.ts +20 -0
- package/dist/signoff/model.d.ts.map +1 -1
- package/dist/signoff/model.js +20 -1
- package/dist/traceability/impact-knowledge.d.ts.map +1 -1
- package/dist/traceability/impact-knowledge.js +1 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +1 -0
- package/dist/types/entities.d.ts +4 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/merge-driver-setup.d.ts +8 -1
- package/dist/utils/merge-driver-setup.d.ts.map +1 -1
- package/dist/utils/merge-driver-setup.js +23 -2
- package/dist/utils/rule-registry.generated.d.ts +10 -0
- package/dist/utils/rule-registry.generated.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.js +12 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.d.ts.map +1 -1
- package/dist/utils/schema-version.js +5 -1
- package/package.json +6 -2
- package/schema/entities.pl +6 -0
- package/schema/validation.pl +4 -1
- package/src/public/evidence-levels.ts +23 -0
- package/src/public/impact/coverage-depth-quality.ts +73 -0
- package/src/public/impact/full-kb-quality.ts +6 -1
- package/src/public/operations/check-executor.ts +13 -0
- package/src/public/operations/evidence-floor.ts +203 -0
- package/src/public/operations/migration-plan.ts +39 -6
- package/src/public/operations/node-ports.ts +3 -1
- package/src/public/operations/proof-contract-claims.ts +103 -0
- package/src/public/operations/specs/check-rules.generated.ts +3 -1
- package/src/public/operations/specs/mutation-schemas.ts +17 -2
- package/src/public/operations/specs/reporting.ts +20 -10
- package/src/public/proof-certificates.ts +271 -0
- package/src/public/proof-protocol-types.d.ts +17 -1
- package/src/public/proof-protocol.ts +318 -3
- package/src/public/proof-receipt.ts +127 -7
- package/src/public/schemas/entity.ts +19 -1
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-commit-review/SKILL.md +1 -1
- package/src/public/skills/kibi-commit-review/resources/staged-gate.md +6 -1
- package/src/public/skills/kibi-decisions/SKILL.md +10 -2
- package/src/public/skills/kibi-decisions/resources/decision-records.md +6 -1
- package/src/public/skills/kibi-freshness/SKILL.md +5 -1
- package/src/public/skills/kibi-traceability/SKILL.md +11 -1
- package/src/public/skills/kibi-usage/SKILL.md +6 -4
- package/src/public/skills/kibi-usage/resources/proof.md +124 -19
- package/src/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/src/schemas/entity.schema.json +212 -4
- package/dist/commands/proof-impact.d.ts +0 -7
- package/dist/commands/proof-impact.d.ts.map +0 -1
- package/dist/commands/proof-impact.js +0 -39
- package/dist/operations/proof/baseline-diff.d.ts +0 -41
- package/dist/operations/proof/baseline-diff.d.ts.map +0 -1
- package/dist/operations/proof/baseline-diff.js +0 -153
- package/dist/operations/proof/impact.d.ts +0 -51
- package/dist/operations/proof/impact.d.ts.map +0 -1
- package/dist/operations/proof/impact.js +0 -138
package/dist/commands/prove.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { executeIngestProof } from "../operations/proof/ingest-proof.js";
|
|
5
5
|
import { PROOF_TEST_REPORT_ENV, PROOF_TEST_REPORT_VERSION, attributeCommandRun, proofTestReportPath, } from "../proof/command-test-report.js";
|
|
@@ -7,9 +7,13 @@ import { PROOF_RUNS_DIR, loadProofIntegrations, resolveIntegration, } from "../p
|
|
|
7
7
|
import { convertJUnitXml } from "../proof/producers/junit-adapter.js";
|
|
8
8
|
import { convertTap } from "../proof/producers/tap-adapter.js";
|
|
9
9
|
import { loadEntities } from "../public/operations/discovery-entities.js";
|
|
10
|
+
import { runOperationJsonQuery, toPrologAtom, } from "../public/operations/prolog-json.js";
|
|
10
11
|
import { ingestProofSpec } from "../public/operations/specs/proof.js";
|
|
12
|
+
import { currentProofBindingMode, perContractTestBindings, } from "../public/operations/specs/reporting.js";
|
|
11
13
|
import { readWorkspaceSnapshot } from "../public/operations/workspace-snapshot.js";
|
|
14
|
+
import { PROOF_CERTIFICATES_ENV } from "../public/proof-certificates.js";
|
|
12
15
|
import { PROOF_RUN_VERSION, proofBindingsErrors, proofRunArtifactErrors, } from "../public/proof-protocol.js";
|
|
16
|
+
import { PROOF_RECEIPT_MAX_AGE_SECONDS } from "../public/proof-receipt.js";
|
|
13
17
|
import { createCliRuntime } from "../runtime/cli-runtime.js";
|
|
14
18
|
/**
|
|
15
19
|
* Describe which paths moved when a proof snapshot refusal fires, so
|
|
@@ -70,19 +74,56 @@ function parseContract(test) {
|
|
|
70
74
|
return null;
|
|
71
75
|
return contract;
|
|
72
76
|
}
|
|
73
|
-
|
|
77
|
+
/** Run a proof integration's command and resolve with its exit code. */
|
|
78
|
+
// implements REQ-kibi-verification-evidence-contract
|
|
79
|
+
export async function runChild(commandArgv, cwd, env) {
|
|
74
80
|
const [command, ...args] = commandArgv;
|
|
75
81
|
if (!command)
|
|
76
82
|
throw new Error("prove: integration command argv must be non-empty");
|
|
77
83
|
return await new Promise((resolve, reject) => {
|
|
84
|
+
// The producer runs in its own process group, so stopping kibi prove
|
|
85
|
+
// stops the whole suite it started (a test runner and its workers), not
|
|
86
|
+
// only kibi prove itself.
|
|
87
|
+
const ownGroup = process.platform !== "win32";
|
|
78
88
|
const child = spawn(command, args, {
|
|
79
89
|
cwd,
|
|
80
90
|
env,
|
|
81
91
|
shell: false,
|
|
82
92
|
stdio: "inherit",
|
|
93
|
+
detached: ownGroup,
|
|
94
|
+
});
|
|
95
|
+
const forward = (signal) => {
|
|
96
|
+
const pid = child.pid;
|
|
97
|
+
try {
|
|
98
|
+
if (ownGroup && pid !== undefined)
|
|
99
|
+
process.kill(-pid, signal);
|
|
100
|
+
else
|
|
101
|
+
child.kill(signal);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// The producer already exited.
|
|
105
|
+
}
|
|
106
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
107
|
+
};
|
|
108
|
+
const onInt = () => forward("SIGINT");
|
|
109
|
+
const onTerm = () => forward("SIGTERM");
|
|
110
|
+
const onHup = () => forward("SIGHUP");
|
|
111
|
+
process.once("SIGINT", onInt);
|
|
112
|
+
process.once("SIGTERM", onTerm);
|
|
113
|
+
process.once("SIGHUP", onHup);
|
|
114
|
+
const settle = () => {
|
|
115
|
+
process.removeListener("SIGINT", onInt);
|
|
116
|
+
process.removeListener("SIGTERM", onTerm);
|
|
117
|
+
process.removeListener("SIGHUP", onHup);
|
|
118
|
+
};
|
|
119
|
+
child.once("error", (error) => {
|
|
120
|
+
settle();
|
|
121
|
+
reject(error);
|
|
122
|
+
});
|
|
123
|
+
child.once("close", (code) => {
|
|
124
|
+
settle();
|
|
125
|
+
resolve(code ?? 1);
|
|
83
126
|
});
|
|
84
|
-
child.once("error", reject);
|
|
85
|
-
child.once("close", (code) => resolve(code ?? 1));
|
|
86
127
|
});
|
|
87
128
|
}
|
|
88
129
|
// implements REQ-kibi-verification-evidence-contract
|
|
@@ -98,6 +139,84 @@ export function commandEnvironment(integration, commandArgv, snapshot, workspace
|
|
|
98
139
|
KIBI_PROOF_TEST_IDS: JSON.stringify(testIds),
|
|
99
140
|
};
|
|
100
141
|
}
|
|
142
|
+
/** Where `kibi prove` records which integrations it has ingested. */
|
|
143
|
+
export const PROVE_PROGRESS_FILE = "prove-progress.json";
|
|
144
|
+
/** Read the progress file; anything unreadable counts as no progress. */
|
|
145
|
+
export async function readProveProgress(runsDir) {
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(await readFile(path.join(runsDir, PROVE_PROGRESS_FILE), "utf8"));
|
|
148
|
+
if (parsed.version !== "kibi.prove-progress.v1" ||
|
|
149
|
+
typeof parsed.snapshot !== "string" ||
|
|
150
|
+
parsed.ingested === null ||
|
|
151
|
+
typeof parsed.ingested !== "object")
|
|
152
|
+
return null;
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Start a fresh progress record for a run that is not resuming. */
|
|
160
|
+
async function recordProgressReset(runsDir, progress) {
|
|
161
|
+
await writeFile(path.join(runsDir, PROVE_PROGRESS_FILE), `${JSON.stringify(progress, null, 2)}\n`, "utf8");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Integrations a resumed run can skip: ingested at the same snapshot, for
|
|
165
|
+
* every test this run selects for them.
|
|
166
|
+
*/
|
|
167
|
+
export function resumableIntegrations(progress, snapshot, groups) {
|
|
168
|
+
const skip = new Set();
|
|
169
|
+
if (progress === null || progress.snapshot !== snapshot)
|
|
170
|
+
return skip;
|
|
171
|
+
for (const [integrationId, tests] of groups) {
|
|
172
|
+
const done = new Set(progress.ingested[integrationId] ?? []);
|
|
173
|
+
if (done.size > 0 && tests.every((test) => done.has(String(test.id))))
|
|
174
|
+
skip.add(integrationId);
|
|
175
|
+
}
|
|
176
|
+
return skip;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Narrow a Playwright integration's command to the spec files the selected
|
|
180
|
+
* tests are bound to, so `kibi prove --test X` runs X's specs instead of the
|
|
181
|
+
* whole suite. Only a direct `playwright test` invocation is narrowed, and
|
|
182
|
+
* only when every selected test names its `proof_bindings[].source_file`;
|
|
183
|
+
* otherwise the command runs unchanged and the reason says why.
|
|
184
|
+
*/
|
|
185
|
+
// implements REQ-kibi-verification-evidence-contract
|
|
186
|
+
export function narrowedPlaywrightCommand(command, tests) {
|
|
187
|
+
const argv = [...command];
|
|
188
|
+
const runner = argv.findIndex((arg, index) => /(^|[\\/])playwright(\.c?js)?$/.test(arg) && argv[index + 1] === "test");
|
|
189
|
+
if (runner < 0)
|
|
190
|
+
return {
|
|
191
|
+
argv,
|
|
192
|
+
narrowed: false,
|
|
193
|
+
reason: "the command is not a direct `playwright test` invocation; narrow it from KIBI_PROOF_SOURCE_FILES",
|
|
194
|
+
};
|
|
195
|
+
const files = new Set();
|
|
196
|
+
for (const test of tests) {
|
|
197
|
+
const own = proofSourceFiles(test);
|
|
198
|
+
if (own.length === 0)
|
|
199
|
+
return {
|
|
200
|
+
argv,
|
|
201
|
+
narrowed: false,
|
|
202
|
+
reason: `test ${String(test.id)} has no proof_bindings[].source_file`,
|
|
203
|
+
};
|
|
204
|
+
for (const file of own)
|
|
205
|
+
files.add(file);
|
|
206
|
+
}
|
|
207
|
+
return { argv: [...argv, ...[...files].sort()], narrowed: true };
|
|
208
|
+
}
|
|
209
|
+
function proofSourceFiles(test) {
|
|
210
|
+
const raw = test.proof_bindings;
|
|
211
|
+
if (!Array.isArray(raw))
|
|
212
|
+
return [];
|
|
213
|
+
return raw.flatMap((entry) => {
|
|
214
|
+
const file = entry !== null && typeof entry === "object"
|
|
215
|
+
? entry.source_file
|
|
216
|
+
: undefined;
|
|
217
|
+
return typeof file === "string" && file !== "" ? [file] : [];
|
|
218
|
+
});
|
|
219
|
+
}
|
|
101
220
|
function collectBindings(tests) {
|
|
102
221
|
const bindings = [];
|
|
103
222
|
for (const test of tests) {
|
|
@@ -151,7 +270,30 @@ function dedupeObligations(tests) {
|
|
|
151
270
|
function markAggregateFailure(results) {
|
|
152
271
|
return results.map((result) => ({ ...result, outcome: "failed" }));
|
|
153
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Receipt state per proof-bearing test under the binding mode reports use.
|
|
275
|
+
* `passed` means the test already has a current passing receipt; anything
|
|
276
|
+
* else (missing, stale, contract_mismatch, invalid, failed) needs a run.
|
|
277
|
+
*/
|
|
278
|
+
// implements REQ-kibi-verification-evidence-contract
|
|
279
|
+
export async function readTestReceiptStates(context) {
|
|
280
|
+
const snapshotEvidence = await readWorkspaceSnapshot(context);
|
|
281
|
+
const snapshot = snapshotEvidence.available
|
|
282
|
+
? snapshotEvidence.snapshot.hash
|
|
283
|
+
: "unknown";
|
|
284
|
+
const bindingsDict = await perContractTestBindings(context);
|
|
285
|
+
const bindingMode = bindingsDict !== null ? "per_contract" : currentProofBindingMode();
|
|
286
|
+
const payload = await runOperationJsonQuery(context.prolog, "discovery.pl", `discovery:test_receipt_states_json(${bindingMode}, ${bindingsDict ?? "_{}"}, ${toPrologAtom(snapshot)}, ${toPrologAtom(context.clock().toISOString())}, ${PROOF_RECEIPT_MAX_AGE_SECONDS}, JsonString)`, "prove --stale");
|
|
287
|
+
return new Map(Object.entries(payload.states ?? {}));
|
|
288
|
+
}
|
|
154
289
|
async function selectTests(context, options) {
|
|
290
|
+
const selected = await selectTestsBySelector(context, options);
|
|
291
|
+
if (!options.stale || selected.length === 0)
|
|
292
|
+
return selected;
|
|
293
|
+
const states = await readTestReceiptStates(context);
|
|
294
|
+
return selected.filter((entity) => states.get(String(entity.id)) !== "passed");
|
|
295
|
+
}
|
|
296
|
+
async function selectTestsBySelector(context, options) {
|
|
155
297
|
// Contract plus bindings only. Receipt histories are loaded again at ingest
|
|
156
298
|
// and blow the Prolog output buffer when selected up front. JUnit and TAP
|
|
157
299
|
// conversion needs the bindings before that reload.
|
|
@@ -227,6 +369,22 @@ async function selectTests(context, options) {
|
|
|
227
369
|
}
|
|
228
370
|
return filtered;
|
|
229
371
|
}
|
|
372
|
+
// implements REQ-kibi-claim-bound-proofs
|
|
373
|
+
// One line per claim a certificate test names in proves: derived (with its
|
|
374
|
+
// scope), attested, or rejected with the reason.
|
|
375
|
+
function claimBindingLines(result) {
|
|
376
|
+
return result.results.flatMap((row) => (row.claimBindings ?? []).map((binding) => `${row.testId} ${binding.claim_key}${binding.requirement_id ? ` (${binding.requirement_id})` : ""}: ${binding.binding}${binding.binding === "derived" && binding.scope ? `, ${binding.scope} scope` : ""}${binding.reason ? `: ${binding.reason}` : ""}`));
|
|
377
|
+
}
|
|
378
|
+
// implements REQ-kibi-code-proof-kinds
|
|
379
|
+
// One line per certificate test whose certificate did not re-check: the test
|
|
380
|
+
// passed, but its receipt supports tested, not proved.
|
|
381
|
+
function certificateRejectionLines(result) {
|
|
382
|
+
return result.results.flatMap((row) => row.certificateRejection === undefined
|
|
383
|
+
? []
|
|
384
|
+
: [
|
|
385
|
+
`${row.testId} ${row.kind ?? "certificate"}: certificate rejected, ${row.certificateRejection}`,
|
|
386
|
+
]);
|
|
387
|
+
}
|
|
230
388
|
function summarize(result) {
|
|
231
389
|
const parts = [`passed ${result.passed}`, `failed ${result.failed}`];
|
|
232
390
|
if (result.unchanged > 0)
|
|
@@ -249,7 +407,7 @@ export async function proveCommand(options, deps = {}) {
|
|
|
249
407
|
throw new Error(`prove: ${integrations.error}`);
|
|
250
408
|
const selected = await selectTests(context, options);
|
|
251
409
|
if (selected.length === 0) {
|
|
252
|
-
process.stdout.write(`${JSON.stringify({
|
|
410
|
+
process.stdout.write(`${JSON.stringify({ passed: 0, certified: 0, failed: 0, message: options.stale ? "every selected test already has a current passing receipt" : "no proof-bearing tests selected" })}\n`);
|
|
253
411
|
completed = true;
|
|
254
412
|
return { exitCode: 0 };
|
|
255
413
|
}
|
|
@@ -273,16 +431,51 @@ export async function proveCommand(options, deps = {}) {
|
|
|
273
431
|
}
|
|
274
432
|
const runsDir = path.join(context.workspaceRoot, PROOF_RUNS_DIR);
|
|
275
433
|
await mkdir(runsDir, { recursive: true });
|
|
434
|
+
// Each integration's receipts are written as soon as it finishes; the
|
|
435
|
+
// progress file lets an interrupted run continue with --resume.
|
|
436
|
+
const previous = options.resume ? await readProveProgress(runsDir) : null;
|
|
437
|
+
const skipped = options.resume
|
|
438
|
+
? resumableIntegrations(previous, snapshot, groups)
|
|
439
|
+
: new Set();
|
|
440
|
+
if (options.resume && previous !== null && previous.snapshot !== snapshot)
|
|
441
|
+
process.stderr.write("prove: the workspace changed since the interrupted run; running every selected integration\n");
|
|
442
|
+
const progress = {
|
|
443
|
+
version: "kibi.prove-progress.v1",
|
|
444
|
+
snapshot,
|
|
445
|
+
ingested: previous !== null && previous.snapshot === snapshot
|
|
446
|
+
? { ...previous.ingested }
|
|
447
|
+
: {},
|
|
448
|
+
};
|
|
449
|
+
const recordProgress = async (integrationId, testIds) => {
|
|
450
|
+
const done = new Set(progress.ingested[integrationId] ?? []);
|
|
451
|
+
for (const id of testIds)
|
|
452
|
+
done.add(id);
|
|
453
|
+
progress.ingested[integrationId] = [...done].sort();
|
|
454
|
+
await writeFile(path.join(runsDir, PROVE_PROGRESS_FILE), `${JSON.stringify(progress, null, 2)}\n`, "utf8");
|
|
455
|
+
};
|
|
456
|
+
if (!options.resume)
|
|
457
|
+
await recordProgressReset(runsDir, progress);
|
|
458
|
+
if (skipped.size > 0)
|
|
459
|
+
process.stderr.write(`prove: resuming; skipping integrations already ingested at this snapshot: ${[...skipped].join(", ")}\n`);
|
|
460
|
+
const narrowSelection = Boolean(options.testId || options.requirement || options.stale);
|
|
461
|
+
const pending = [...groups].filter(([id]) => !skipped.has(id));
|
|
276
462
|
const ingestResults = [];
|
|
277
463
|
const runAttribution = new Map();
|
|
278
464
|
const failures = [];
|
|
279
|
-
for (const [integrationId, tests] of
|
|
465
|
+
for (const [index, [integrationId, tests]] of pending.entries()) {
|
|
466
|
+
process.stderr.write(`prove: running integration '${integrationId}' (${index + 1}/${pending.length}, ${tests.length} test${tests.length === 1 ? "" : "s"})\n`);
|
|
280
467
|
const integration = resolveIntegration(integrations.integrations, integrationId);
|
|
281
468
|
if (!integration) {
|
|
282
469
|
failures.push(`integration '${integrationId}' (required by ${tests.map((t) => t.id).join(", ")}) is not configured in .kb/proof/integrations.json; plan it with kibi proof inspect --update ${integrationId} --json and apply the plan with kb_apply_plan`);
|
|
283
470
|
continue;
|
|
284
471
|
}
|
|
285
|
-
|
|
472
|
+
let commandArgv = [...integration.command];
|
|
473
|
+
if (narrowSelection && integration.producer === "playwright") {
|
|
474
|
+
const narrowed = narrowedPlaywrightCommand(commandArgv, tests);
|
|
475
|
+
commandArgv = narrowed.argv;
|
|
476
|
+
if (!narrowed.narrowed)
|
|
477
|
+
process.stderr.write(`prove: running the whole '${integrationId}' integration: ${narrowed.reason}\n`);
|
|
478
|
+
}
|
|
286
479
|
const artifactPath = path.resolve(context.workspaceRoot, integration.artifact ??
|
|
287
480
|
path.join(PROOF_RUNS_DIR, `${integrationId}.json`));
|
|
288
481
|
await mkdir(path.dirname(artifactPath), { recursive: true });
|
|
@@ -298,6 +491,13 @@ export async function proveCommand(options, deps = {}) {
|
|
|
298
491
|
const env = commandEnvironment(integration, commandArgv, snapshot, context.workspaceRoot, selectedTestIds, artifactPath);
|
|
299
492
|
if (testReportPath !== undefined)
|
|
300
493
|
env[PROOF_TEST_REPORT_ENV] = testReportPath;
|
|
494
|
+
env.KIBI_PROOF_SOURCE_FILES = JSON.stringify([...new Set(tests.flatMap(proofSourceFiles))].sort());
|
|
495
|
+
// Certificate-kind tests leave one certificate per test here; any left
|
|
496
|
+
// by an earlier run must never be read as this run's proof.
|
|
497
|
+
const certificateDir = path.join(runsDir, "certificates", integrationId);
|
|
498
|
+
await rm(certificateDir, { recursive: true, force: true });
|
|
499
|
+
await mkdir(certificateDir, { recursive: true });
|
|
500
|
+
env[PROOF_CERTIFICATES_ENV] = certificateDir;
|
|
301
501
|
const startedAt = new Date().toISOString();
|
|
302
502
|
let exitCode;
|
|
303
503
|
try {
|
|
@@ -344,6 +544,13 @@ export async function proveCommand(options, deps = {}) {
|
|
|
344
544
|
failures.push(`integration '${integrationId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
345
545
|
continue;
|
|
346
546
|
}
|
|
547
|
+
try {
|
|
548
|
+
evaluated = attachCertificates(evaluated, await readCertificateDir(certificateDir));
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
failures.push(`integration '${integrationId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
347
554
|
const artifactErrors = [
|
|
348
555
|
...(evaluated.wholeRun === undefined ? [] : [evaluated.wholeRun]),
|
|
349
556
|
...evaluated.artifacts.map((entry) => entry.artifact),
|
|
@@ -379,6 +586,13 @@ export async function proveCommand(options, deps = {}) {
|
|
|
379
586
|
if (merged !== undefined) {
|
|
380
587
|
ingestResults.push(merged);
|
|
381
588
|
runAttribution.set(merged, evaluated);
|
|
589
|
+
await recordProgress(integrationId, selectedTestIds);
|
|
590
|
+
process.stderr.write(`prove: ingested integration '${integrationId}' (${summarize(merged)})\n`);
|
|
591
|
+
for (const line of [
|
|
592
|
+
...certificateRejectionLines(merged),
|
|
593
|
+
...claimBindingLines(merged),
|
|
594
|
+
])
|
|
595
|
+
process.stderr.write(`prove: ${line}\n`);
|
|
382
596
|
}
|
|
383
597
|
const afterIngest = await readWorkspaceSnapshot(context);
|
|
384
598
|
if (!afterIngest.available || afterIngest.snapshot.hash !== snapshot) {
|
|
@@ -388,8 +602,14 @@ export async function proveCommand(options, deps = {}) {
|
|
|
388
602
|
const passed = ingestResults.reduce((sum, result) => sum + result.passed, 0);
|
|
389
603
|
const failed = ingestResults.reduce((sum, result) => sum + result.failed, 0);
|
|
390
604
|
const unchanged = ingestResults.reduce((sum, result) => sum + result.unchanged, 0);
|
|
605
|
+
// implements REQ-kibi-code-proof-kinds
|
|
606
|
+
// passed counts passing receipts; certified counts those whose certificate
|
|
607
|
+
// re-checked, the only receipts that can support proved.
|
|
608
|
+
const certified = ingestResults.reduce((sum, result) => sum +
|
|
609
|
+
result.results.filter((row) => row.outcome === "passed" && row.evidenceLevel === "proved").length, 0);
|
|
391
610
|
const summary = {
|
|
392
|
-
|
|
611
|
+
passed,
|
|
612
|
+
certified,
|
|
393
613
|
failed,
|
|
394
614
|
unchanged,
|
|
395
615
|
runs: ingestResults.map((result) => {
|
|
@@ -413,6 +633,8 @@ export async function proveCommand(options, deps = {}) {
|
|
|
413
633
|
}),
|
|
414
634
|
...(failures.length > 0 ? { failures } : {}),
|
|
415
635
|
};
|
|
636
|
+
if (failures.length > 0 && Object.keys(progress.ingested).length > 0)
|
|
637
|
+
process.stderr.write("prove: integrations that failed to produce receipts run again with the same selector plus --resume\n");
|
|
416
638
|
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
417
639
|
completed = true;
|
|
418
640
|
await runtime.afterSuccess(ingestProofSpec, context);
|
|
@@ -424,6 +646,69 @@ export async function proveCommand(options, deps = {}) {
|
|
|
424
646
|
: { status: "error", error: 1 });
|
|
425
647
|
}
|
|
426
648
|
}
|
|
649
|
+
/** Read every certificate a run left in its certificate directory. */
|
|
650
|
+
// implements REQ-kibi-code-proof-kinds
|
|
651
|
+
export async function readCertificateDir(directory) {
|
|
652
|
+
let names;
|
|
653
|
+
try {
|
|
654
|
+
names = (await readdir(directory)).filter((name) => name.endsWith(".json"));
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
const certificates = [];
|
|
660
|
+
for (const name of names.sort()) {
|
|
661
|
+
let parsed;
|
|
662
|
+
try {
|
|
663
|
+
parsed = JSON.parse(await readFile(path.join(directory, name), "utf8"));
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
throw new Error(`certificate ${name} is not valid JSON`);
|
|
667
|
+
}
|
|
668
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
669
|
+
throw new Error(`certificate ${name} is not a JSON object`);
|
|
670
|
+
certificates.push(parsed);
|
|
671
|
+
}
|
|
672
|
+
return certificates;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Give each artifact the certificates of its own tests, so a partition is
|
|
676
|
+
* never evaluated against another test's certificate. A test may leave at
|
|
677
|
+
* most one certificate per run.
|
|
678
|
+
*/
|
|
679
|
+
// implements REQ-kibi-code-proof-kinds
|
|
680
|
+
export function attachCertificates(evaluated, certificates) {
|
|
681
|
+
if (certificates.length === 0)
|
|
682
|
+
return evaluated;
|
|
683
|
+
const seen = new Set();
|
|
684
|
+
for (const certificate of certificates) {
|
|
685
|
+
const id = String(certificate.test_id ?? "");
|
|
686
|
+
if (seen.has(id))
|
|
687
|
+
throw new Error(`the run left more than one certificate for ${id}`);
|
|
688
|
+
seen.add(id);
|
|
689
|
+
}
|
|
690
|
+
const withOwn = (artifact, testIds) => {
|
|
691
|
+
const own = certificates.filter((certificate) => testIds.includes(String(certificate.test_id ?? "")));
|
|
692
|
+
return own.length === 0
|
|
693
|
+
? artifact
|
|
694
|
+
: {
|
|
695
|
+
...artifact,
|
|
696
|
+
certificates: [...(artifact.certificates ?? []), ...own],
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
return {
|
|
700
|
+
...evaluated,
|
|
701
|
+
artifacts: evaluated.artifacts.map((entry) => ({
|
|
702
|
+
...entry,
|
|
703
|
+
artifact: withOwn(entry.artifact, entry.testIds),
|
|
704
|
+
})),
|
|
705
|
+
...(evaluated.wholeRun === undefined
|
|
706
|
+
? {}
|
|
707
|
+
: {
|
|
708
|
+
wholeRun: withOwn(evaluated.wholeRun, evaluated.artifacts.flatMap((entry) => entry.testIds)),
|
|
709
|
+
}),
|
|
710
|
+
};
|
|
711
|
+
}
|
|
427
712
|
/** Read a command integration's optional per-test report and attribute it. */
|
|
428
713
|
// implements REQ-kibi-fresh-verification-receipts-v2
|
|
429
714
|
export async function readCommandAttribution(reportPath, selectedTestIds, exitCode) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,kBAAkB,EAGxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC;AAEH,KAAK,cAAc,GAAG,QAAQ,CAAC;IAC7B,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,YAAY,CAAC,EAAE,CACb,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC,CAAC;AA+HH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChE;AA8DD,wBAAsB,aAAa,CACjC,OAAO,EAAE,aAAa,EACtB,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAgHjB"}
|
package/dist/commands/report.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync, spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { coverageSpec } from "../public/operations/specs/reporting.js";
|
|
@@ -146,6 +146,41 @@ function assertCompleteRequirementRows(coverage, limit) {
|
|
|
146
146
|
throw new Error(`Report requires complete requirement rows, but ${coverage.rows.length} of ${total} were returned. Increase --limit above ${limit}.`);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
const KB_CERTIFICATES_VERSION = "kibi.certificates.v1";
|
|
150
|
+
/**
|
|
151
|
+
* Read a saved kibi check --write-certificates bundle, so the report can
|
|
152
|
+
* publish it next to the page and say how many certificates it holds.
|
|
153
|
+
*/
|
|
154
|
+
// implements REQ-kibi-kb-certificates
|
|
155
|
+
async function readKbCertificates(bundlePath) {
|
|
156
|
+
const raw = await readFile(bundlePath, "utf8");
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(raw);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
throw new Error(`report: ${bundlePath} is not JSON`);
|
|
163
|
+
}
|
|
164
|
+
const bundle = parsed !== null && typeof parsed === "object"
|
|
165
|
+
? parsed
|
|
166
|
+
: {};
|
|
167
|
+
if (bundle.version !== KB_CERTIFICATES_VERSION ||
|
|
168
|
+
!Array.isArray(bundle.certificates))
|
|
169
|
+
throw new Error(`report: ${bundlePath} is not a ${KB_CERTIFICATES_VERSION} bundle (write one with kibi check --write-certificates)`);
|
|
170
|
+
return { raw, certificates: bundle.certificates };
|
|
171
|
+
}
|
|
172
|
+
function certificateKinds(certificates) {
|
|
173
|
+
const kinds = {};
|
|
174
|
+
for (const certificate of certificates) {
|
|
175
|
+
const kind = certificate !== null &&
|
|
176
|
+
typeof certificate === "object" &&
|
|
177
|
+
typeof certificate.kind === "string"
|
|
178
|
+
? certificate.kind
|
|
179
|
+
: "unknown";
|
|
180
|
+
kinds[kind] = (kinds[kind] ?? 0) + 1;
|
|
181
|
+
}
|
|
182
|
+
return kinds;
|
|
183
|
+
}
|
|
149
184
|
// implements REQ-kibi-html-health-report
|
|
150
185
|
export async function reportCommand(options, deps = {}) {
|
|
151
186
|
const limit = parseLimit(options.limit);
|
|
@@ -154,16 +189,31 @@ export async function reportCommand(options, deps = {}) {
|
|
|
154
189
|
assertCompleteRequirementRows(coverage.requirements, limit);
|
|
155
190
|
const now = (deps.now ?? (() => new Date()))();
|
|
156
191
|
const workspace = (deps.cwd ?? (() => process.cwd()))();
|
|
192
|
+
const outputPath = reportTarget(workspace, options.output);
|
|
193
|
+
const requestedOutput = options.output?.trim() || "kibi-report";
|
|
194
|
+
const outputIsHtmlFile = [".html", ".htm"].includes(path.extname(requestedOutput).toLowerCase());
|
|
195
|
+
const kbCertificatesSource = options.certificates?.trim() === undefined ||
|
|
196
|
+
options.certificates.trim() === ""
|
|
197
|
+
? undefined
|
|
198
|
+
: await readKbCertificates(path.resolve(workspace, options.certificates.trim()));
|
|
199
|
+
const kbCertificatesPath = outputIsHtmlFile
|
|
200
|
+
? path.join(path.dirname(outputPath), `${path.basename(outputPath, path.extname(outputPath))}.kb-certificates.json`)
|
|
201
|
+
: path.join(path.dirname(outputPath), "kb-certificates.json");
|
|
202
|
+
const kbCertificates = kbCertificatesSource === undefined
|
|
203
|
+
? undefined
|
|
204
|
+
: {
|
|
205
|
+
count: kbCertificatesSource.certificates.length,
|
|
206
|
+
kinds: certificateKinds(kbCertificatesSource.certificates),
|
|
207
|
+
href: path.basename(kbCertificatesPath),
|
|
208
|
+
};
|
|
157
209
|
const html = renderHtmlReport({
|
|
158
210
|
requirements: coverage.requirements,
|
|
159
211
|
symbols: coverage.symbols,
|
|
160
212
|
branch: coverage.branch,
|
|
161
213
|
generatedAt: now,
|
|
162
214
|
repository: deps.repository ?? loadReportRepository(workspace, coverage.branch),
|
|
215
|
+
...(kbCertificates === undefined ? {} : { kbCertificates }),
|
|
163
216
|
});
|
|
164
|
-
const outputPath = reportTarget(workspace, options.output);
|
|
165
|
-
const requestedOutput = options.output?.trim() || "kibi-report";
|
|
166
|
-
const outputIsHtmlFile = [".html", ".htm"].includes(path.extname(requestedOutput).toLowerCase());
|
|
167
217
|
const badgePath = outputIsHtmlFile
|
|
168
218
|
? path.join(path.dirname(outputPath), `${path.basename(outputPath, path.extname(outputPath))}.badge.svg`)
|
|
169
219
|
: path.join(path.dirname(outputPath), "badge.svg");
|
|
@@ -177,12 +227,23 @@ export async function reportCommand(options, deps = {}) {
|
|
|
177
227
|
: Math.round((tested / currentRequirements) * 100);
|
|
178
228
|
const hasContradiction = coverage.requirements.rows.some((row) => Array.isArray(row.proofGaps) &&
|
|
179
229
|
row.proofGaps.includes("blocking_contradiction"));
|
|
230
|
+
// implements REQ-kibi-evidence-floors
|
|
231
|
+
// A requirement below its evidence floor fails kibi check, like a
|
|
232
|
+
// contradiction, so the badge turns red whatever the tested share.
|
|
233
|
+
const belowFloor = coverage.requirements.rows.some((row) => {
|
|
234
|
+
const floor = row.evidenceFloor;
|
|
235
|
+
return (row.proofStatus !== "not_applicable" &&
|
|
236
|
+
floor !== null &&
|
|
237
|
+
typeof floor === "object" &&
|
|
238
|
+
floor.status === "below" &&
|
|
239
|
+
floor.required !== "unverified");
|
|
240
|
+
});
|
|
180
241
|
const snapshotStale = coverage.requirements.meta?.dirty === true ||
|
|
181
242
|
coverage.requirements.meta?.proofSnapshotDirty === true;
|
|
182
243
|
const badgeMessage = currentRequirements === 0 ? "no requirements" : `${proofPercent}% tested`;
|
|
183
244
|
const badgeColor = currentRequirements === 0
|
|
184
245
|
? KIBI_BRAND.rail
|
|
185
|
-
: hasContradiction
|
|
246
|
+
: hasContradiction || belowFloor
|
|
186
247
|
? KIBI_BRAND.danger
|
|
187
248
|
: snapshotStale
|
|
188
249
|
? KIBI_BRAND.warning
|
|
@@ -196,8 +257,12 @@ export async function reportCommand(options, deps = {}) {
|
|
|
196
257
|
const badge = renderKibiBadge(badgeMessage, badgeColor);
|
|
197
258
|
await writeAtomically(outputPath, html);
|
|
198
259
|
await writeAtomically(badgePath, badge);
|
|
260
|
+
if (kbCertificatesSource !== undefined)
|
|
261
|
+
await writeAtomically(kbCertificatesPath, kbCertificatesSource.raw);
|
|
199
262
|
console.log(`Kibi report written to ${outputPath}`);
|
|
200
263
|
console.log(`Kibi badge written to ${badgePath}`);
|
|
264
|
+
if (kbCertificatesSource !== undefined)
|
|
265
|
+
console.log(`KB certificates written to ${kbCertificatesPath}`);
|
|
201
266
|
if (options.open === true) {
|
|
202
267
|
await (deps.openReport ?? openReport)(outputPath);
|
|
203
268
|
console.log("Opened Kibi report in the default browser");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signoff.d.ts","sourceRoot":"","sources":["../../src/commands/signoff.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"signoff.d.ts","sourceRoot":"","sources":["../../src/commands/signoff.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC;AAGH,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GACjD,QAAQ,CAAC;IACP,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD,CAAC,CAAC;AAKL,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAGxD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAGnE;AA6GD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,WAAW,CAAC,CA4EtB"}
|
package/dist/commands/signoff.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { coverageSpec } from "../public/operations/specs/reporting.js";
|
|
6
|
-
import { earliestUnmetGate } from "../report/html-report.js";
|
|
6
|
+
import { certificateRejectionLines, earliestUnmetGate, } from "../report/html-report.js";
|
|
7
7
|
import { collectSignoffQuestions, collectSignoffSummary, parseSignoffQuestionsInput, } from "../signoff/collect.js";
|
|
8
8
|
import { renderSignoffHtml } from "../signoff/html.js";
|
|
9
9
|
import { renderQuestionsMarkdownBody, renderSignoffChatMarkdown, renderSignoffMarkdown, renderSummaryMarkdownBody, } from "../signoff/markdown.js";
|
|
@@ -37,6 +37,21 @@ async function writeAtomically(filePath, contents) {
|
|
|
37
37
|
throw error;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
// implements REQ-kibi-evidence-floors
|
|
41
|
+
function signoffFloor(value) {
|
|
42
|
+
const floor = record(value);
|
|
43
|
+
return typeof floor.required === "string" &&
|
|
44
|
+
floor.required !== "unverified" &&
|
|
45
|
+
typeof floor.status === "string"
|
|
46
|
+
? {
|
|
47
|
+
evidenceFloor: {
|
|
48
|
+
required: floor.required,
|
|
49
|
+
source: typeof floor.source === "string" ? floor.source : "explicit",
|
|
50
|
+
status: floor.status,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
: {};
|
|
54
|
+
}
|
|
40
55
|
function record(value) {
|
|
41
56
|
return value !== null && typeof value === "object"
|
|
42
57
|
? value
|
|
@@ -78,6 +93,10 @@ async function loadProof() {
|
|
|
78
93
|
...(typeof row.evidenceLevel === "string"
|
|
79
94
|
? { evidenceLevel: row.evidenceLevel }
|
|
80
95
|
: {}),
|
|
96
|
+
...signoffFloor(row.evidenceFloor),
|
|
97
|
+
...(certificateRejectionLines(row).length > 0
|
|
98
|
+
? { certificateRejections: certificateRejectionLines(row) }
|
|
99
|
+
: {}),
|
|
81
100
|
contradictions: conflicts
|
|
82
101
|
.map(record)
|
|
83
102
|
.filter((conflict) => conflict.status === "contradiction")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/manifest.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,UAAU,EACV,UAAU,EACV,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC;AAE9D,OAAO,EAML,uBAAuB,EACxB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,KAAK,mBAAmB,EACxB,uBAAuB,EACxB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/manifest.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,UAAU,EACV,UAAU,EACV,aAAa,EACd,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC;AAE9D,OAAO,EAML,uBAAuB,EACxB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,KAAK,mBAAmB,EACxB,uBAAuB,EACxB,MAAM,yCAAyC,CAAC;AACjD,OAAO,EACL,0BAA0B,EAG3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAI5E,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,2BAA2B,EAAE,OAAO,2BAA2B,CAAC;CACjE;AAkBD,eAAO,MAAM,8BAA8B,QAc1C,CAAC;AA0BF;;;;;;;;;GASG;AACH,OAAO,EAAE,0BAA0B,EAAE,CAAC;AAEtC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAG7E;AAyHD,wBAAsB,0BAA0B,CAE9C,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAC7B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACA,OAAO,CAAC,IAAI,CAAC,CAsNf;AAED;;;;;;;;GAQG;AAEH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC3B,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,0BAA0B,CAExC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAOT;AAED,wBAAgB,8BAA8B,CAE5C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC3B,OAAO,CAST"}
|