kibi-cli 2.32.0 → 2.36.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.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/commands/check-generated.d.ts +5 -1
- package/dist/commands/check-generated.d.ts.map +1 -1
- package/dist/commands/check-generated.js +24 -35
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +8 -5
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +23 -5
- package/dist/commands/sync/manifest.d.ts +10 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +87 -14
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +19 -0
- package/dist/extractors/markdown.d.ts +1 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/symbol-coordinates.js +2 -2
- package/dist/extractors/symbols-coordinator.d.ts +2 -4
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.js +1 -2
- package/dist/operations/migration/proof-contract-claims.d.ts +23 -10
- package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -1
- package/dist/operations/migration/proof-contract-claims.js +35 -2
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +2 -4
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts +3 -2
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -1
- package/dist/operations/migration/symbol-coordinates-refresh.js +4 -3
- package/dist/operations/migration/untrack-symbol-coordinates.d.ts +23 -0
- package/dist/operations/migration/untrack-symbol-coordinates.d.ts.map +1 -0
- package/dist/operations/migration/untrack-symbol-coordinates.js +67 -0
- package/dist/operations/modeling/requirement-modeler.d.ts.map +1 -1
- package/dist/operations/modeling/requirement-modeler.js +48 -1
- package/dist/operations/modeling/requirement-types.d.ts +2 -1
- package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
- package/dist/operations/modeling/requirement-utils.d.ts +1 -1
- package/dist/operations/modeling/requirement-utils.d.ts.map +1 -1
- package/dist/operations/modeling/requirement-utils.js +8 -1
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +39 -20
- package/dist/operations/proof/claim-bindings.d.ts +18 -0
- package/dist/operations/proof/claim-bindings.d.ts.map +1 -1
- package/dist/operations/proof/claim-bindings.js +157 -4
- package/dist/operations/proof/code-certificates.d.ts +34 -14
- package/dist/operations/proof/code-certificates.d.ts.map +1 -1
- package/dist/operations/proof/code-certificates.js +266 -157
- package/dist/operations/proof/code-models.d.ts +96 -0
- package/dist/operations/proof/code-models.d.ts.map +1 -0
- package/dist/operations/proof/code-models.js +601 -0
- package/dist/operations/proof/code-runner.d.ts +40 -0
- package/dist/operations/proof/code-runner.d.ts.map +1 -0
- package/dist/operations/proof/code-runner.js +164 -0
- package/dist/operations/proof/code-scope.d.ts +3 -0
- package/dist/operations/proof/code-scope.d.ts.map +1 -1
- package/dist/operations/proof/code-scope.js +26 -2
- package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
- package/dist/operations/proof/ingest-proof.js +2 -1
- package/dist/operations/proof/regular-language.d.ts +89 -0
- package/dist/operations/proof/regular-language.d.ts.map +1 -0
- package/dist/operations/proof/regular-language.js +943 -0
- package/dist/operations/proof/source-code-readers.d.ts +12 -0
- package/dist/operations/proof/source-code-readers.d.ts.map +1 -0
- package/dist/operations/proof/source-code-readers.js +699 -0
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +2 -1
- package/dist/operations/semantic-advisor/strict-count-rules.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/strict-count-rules.js +8 -2
- package/dist/operations/semantic-advisor/strict-rules.d.ts +8 -0
- package/dist/operations/semantic-advisor/strict-rules.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/strict-rules.js +231 -1
- package/dist/operations/semantic-advisor/types.d.ts +1 -1
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +13 -3
- package/dist/public/operations/catalog.d.ts +156 -13
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/evidence-floor.js +1 -1
- package/dist/public/operations/proof-contract-claims.js +1 -1
- package/dist/public/operations/specs/composite.d.ts +16 -3
- package/dist/public/operations/specs/composite.d.ts.map +1 -1
- package/dist/public/operations/specs/modeling.d.ts +16 -3
- package/dist/public/operations/specs/modeling.d.ts.map +1 -1
- package/dist/public/operations/specs/modeling.js +22 -3
- package/dist/public/operations/specs/mutation-schemas.d.ts +140 -10
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +14 -3
- package/dist/public/operations/specs/upsert.d.ts +140 -10
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/proof-certificates.d.ts +75 -1
- package/dist/public/proof-certificates.d.ts.map +1 -1
- package/dist/public/proof-certificates.js +212 -0
- package/dist/public/proof-protocol.d.ts +143 -10
- package/dist/public/proof-protocol.d.ts.map +1 -1
- package/dist/public/proof-protocol.js +301 -16
- package/dist/public/proof-receipt.d.ts +4 -3
- package/dist/public/proof-receipt.d.ts.map +1 -1
- package/dist/public/proof-receipt.js +6 -1
- package/dist/public/proof-sources.d.ts +102 -0
- package/dist/public/proof-sources.d.ts.map +1 -0
- package/dist/public/proof-sources.js +1292 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +21 -1
- package/dist/public/skills/kibi-commit-review/SKILL.md +8 -8
- package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +6 -6
- package/dist/public/skills/kibi-decisions/SKILL.md +5 -2
- package/dist/public/skills/kibi-decisions/resources/decision-records.md +4 -1
- package/dist/public/skills/kibi-freshness/SKILL.md +2 -2
- package/dist/public/skills/kibi-traceability/SKILL.md +3 -2
- package/dist/public/skills/kibi-usage/SKILL.md +5 -3
- package/dist/public/skills/kibi-usage/resources/proof.md +105 -9
- package/dist/report/html-report.css +45 -10
- package/dist/report/html-report.d.ts.map +1 -1
- package/dist/report/html-report.js +74 -17
- 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 +1 -0
- package/dist/schemas/entity.schema.json +199 -8
- package/dist/traceability/evidence-model.d.ts +13 -12
- package/dist/traceability/evidence-model.d.ts.map +1 -1
- package/dist/traceability/evidence-model.js +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-diagnostics.js +24 -20
- package/dist/traceability/staged-impact-contract.d.ts +4 -4
- package/dist/traceability/staged-impact-contract.js +9 -9
- package/dist/traceability/staged-symbols-manifest.d.ts +11 -3
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +47 -140
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/rule-registry.generated.d.ts +1 -1
- package/dist/utils/rule-registry.generated.js +1 -1
- 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 +3 -1
- package/dist/utils/strict-modeling.d.ts +3 -2
- package/dist/utils/strict-modeling.d.ts.map +1 -1
- package/dist/utils/strict-modeling.js +37 -4
- package/dist/workspace-cwd.d.ts +15 -0
- package/dist/workspace-cwd.d.ts.map +1 -0
- package/dist/workspace-cwd.js +86 -0
- package/package.json +2 -2
- package/schema/validation.pl +3 -1
- package/src/public/operations/evidence-floor.ts +1 -1
- package/src/public/operations/proof-contract-claims.ts +1 -1
- package/src/public/operations/specs/modeling.ts +24 -5
- package/src/public/operations/specs/mutation-schemas.ts +14 -3
- package/src/public/proof-certificates.ts +296 -1
- package/src/public/proof-protocol-types.d.ts +54 -2
- package/src/public/proof-protocol.ts +396 -20
- package/src/public/proof-receipt.ts +8 -2
- package/src/public/proof-sources.ts +1656 -0
- package/src/public/schemas/entity.ts +32 -2
- package/src/public/skills/kibi-commit-review/SKILL.md +8 -8
- package/src/public/skills/kibi-commit-review/resources/staged-gate.md +6 -6
- package/src/public/skills/kibi-decisions/SKILL.md +5 -2
- package/src/public/skills/kibi-decisions/resources/decision-records.md +4 -1
- package/src/public/skills/kibi-freshness/SKILL.md +2 -2
- package/src/public/skills/kibi-traceability/SKILL.md +3 -2
- package/src/public/skills/kibi-usage/SKILL.md +5 -3
- package/src/public/skills/kibi-usage/resources/proof.md +105 -9
- package/src/schemas/entity.schema.json +199 -8
- package/dist/traceability/reviewed-coordinate-refresh.d.ts +0 -10
- package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +0 -1
- package/dist/traceability/reviewed-coordinate-refresh.js +0 -49
|
@@ -226,6 +226,8 @@ export function normalizeSourceKey(value) {
|
|
|
226
226
|
return normalized;
|
|
227
227
|
}
|
|
228
228
|
function normalizeStableValue(value) {
|
|
229
|
+
if (Array.isArray(value))
|
|
230
|
+
return normalizeStableValue([...new Set(value)].sort().join("|"));
|
|
229
231
|
if (typeof value === "string") {
|
|
230
232
|
const normalized = value
|
|
231
233
|
.trim()
|
|
@@ -279,8 +281,20 @@ function buildUniqueTags(tags) {
|
|
|
279
281
|
function buildPropertyFactTitle(normalizedPropertyKey, claim) {
|
|
280
282
|
const label = humanizeKey(normalizedPropertyKey);
|
|
281
283
|
if (claim.operator === "polarity") {
|
|
282
|
-
return `${label} ${normalizePolarityValue(claim.value) === "forbid" ? "forbidden" : "required"}`;
|
|
284
|
+
return `${label} ${normalizePolarityValue(scalarValue(claim.value)) === "forbid" ? "forbidden" : "required"}`;
|
|
283
285
|
}
|
|
286
|
+
if (Array.isArray(claim.value)) {
|
|
287
|
+
const setLabel = claim.operator === "includes"
|
|
288
|
+
? "includes"
|
|
289
|
+
: claim.operator === "subset_of"
|
|
290
|
+
? "within"
|
|
291
|
+
: claim.operator === "excludes"
|
|
292
|
+
? "excludes"
|
|
293
|
+
: "=";
|
|
294
|
+
return `${label} ${setLabel} ${claim.value.join(", ")}`;
|
|
295
|
+
}
|
|
296
|
+
if (claim.operator === "matches")
|
|
297
|
+
return `${label} matches ${String(claim.value)}`;
|
|
284
298
|
const operatorLabel = claim.operator === "bool"
|
|
285
299
|
? "="
|
|
286
300
|
: claim.operator === "eq"
|
|
@@ -297,19 +311,32 @@ function buildPropertyFactTitle(normalizedPropertyKey, claim) {
|
|
|
297
311
|
return `${label} ${operatorLabel} ${String(claim.value)}`;
|
|
298
312
|
}
|
|
299
313
|
function buildPropertyFactFields(claim) {
|
|
314
|
+
// implements REQ-kibi-source-proof-kinds
|
|
315
|
+
if (Array.isArray(claim.value))
|
|
316
|
+
return {
|
|
317
|
+
operator: claim.operator,
|
|
318
|
+
value_type: "set",
|
|
319
|
+
value_set: [...new Set(claim.value)].sort(),
|
|
320
|
+
};
|
|
321
|
+
if (claim.operator === "matches")
|
|
322
|
+
return {
|
|
323
|
+
operator: "matches",
|
|
324
|
+
value_type: "string",
|
|
325
|
+
value_string: String(claim.value),
|
|
326
|
+
};
|
|
300
327
|
if (claim.operator === "polarity") {
|
|
301
328
|
return {
|
|
302
329
|
operator: "eq",
|
|
303
330
|
value_type: "bool",
|
|
304
331
|
value_bool: true,
|
|
305
|
-
polarity: normalizePolarityValue(claim.value),
|
|
332
|
+
polarity: normalizePolarityValue(scalarValue(claim.value)),
|
|
306
333
|
};
|
|
307
334
|
}
|
|
308
335
|
if (claim.operator === "bool") {
|
|
309
336
|
return {
|
|
310
337
|
operator: "eq",
|
|
311
338
|
value_type: "bool",
|
|
312
|
-
value_bool: normalizeBooleanValue(claim.value),
|
|
339
|
+
value_bool: normalizeBooleanValue(scalarValue(claim.value)),
|
|
313
340
|
};
|
|
314
341
|
}
|
|
315
342
|
return {
|
|
@@ -328,7 +355,7 @@ function numericClaimValue(claim) {
|
|
|
328
355
|
/^\s*-?\d+(?:\.\d+)?\s*$/.test(claim.value)) {
|
|
329
356
|
return Number(claim.value);
|
|
330
357
|
}
|
|
331
|
-
return claim.value;
|
|
358
|
+
return scalarValue(claim.value);
|
|
332
359
|
}
|
|
333
360
|
function buildTypedValueFields(value) {
|
|
334
361
|
if (typeof value === "boolean") {
|
|
@@ -420,3 +447,9 @@ function buildEntitySourcePath(directory, entityId) {
|
|
|
420
447
|
export function factSourcePath(factId) {
|
|
421
448
|
return buildEntitySourcePath(resolveEntitySourcePaths().facts, factId);
|
|
422
449
|
}
|
|
450
|
+
/** A claim value as one scalar: a set value joins its members. */
|
|
451
|
+
function scalarValue(value) {
|
|
452
|
+
return Array.isArray(value)
|
|
453
|
+
? value.join("|")
|
|
454
|
+
: value;
|
|
455
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run from a subdirectory of a Kibi workspace against that workspace's KB,
|
|
3
|
+
* the way git finds its repository: walk up to the nearest directory with
|
|
4
|
+
* `.kb/manifest.json`. Without this, a command run from `apps/server/`
|
|
5
|
+
* compiles a second, empty branch store there and reports an empty KB.
|
|
6
|
+
* An explicit KIBI_WORKSPACE (or alias) and `kibi init` keep the given
|
|
7
|
+
* directory. Relative path option values keep meaning what the user typed:
|
|
8
|
+
* the result is the directory to run in and argv with those values made
|
|
9
|
+
* absolute.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveWorkspaceCwd(argv: readonly string[], cwd: string, env: NodeJS.ProcessEnv): Readonly<{
|
|
12
|
+
cwd: string;
|
|
13
|
+
argv: string[];
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=workspace-cwd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-cwd.d.ts","sourceRoot":"","sources":["../src/workspace-cwd.ts"],"names":[],"mappings":"AAqEA;;;;;;;;;GASG;AAEH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAS3C"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
const WORKSPACE_ENV_KEYS = [
|
|
21
|
+
"KIBI_WORKSPACE",
|
|
22
|
+
"KIBI_PROJECT_ROOT",
|
|
23
|
+
"KIBI_ROOT",
|
|
24
|
+
];
|
|
25
|
+
/** Options whose value is a file or directory the user named from their shell. */
|
|
26
|
+
const PATH_OPTIONS = new Set([
|
|
27
|
+
"--input",
|
|
28
|
+
"--output",
|
|
29
|
+
"--certificates",
|
|
30
|
+
"--write-certificates",
|
|
31
|
+
]);
|
|
32
|
+
/** Commands that act on the current directory itself, never on an enclosing KB. */
|
|
33
|
+
const CWD_COMMANDS = new Set(["init"]);
|
|
34
|
+
function enclosingKbRoot(start) {
|
|
35
|
+
let current = path.resolve(start);
|
|
36
|
+
while (true) {
|
|
37
|
+
if (fs.existsSync(path.join(current, ".kb", "manifest.json")))
|
|
38
|
+
return current;
|
|
39
|
+
const parent = path.dirname(current);
|
|
40
|
+
if (parent === current)
|
|
41
|
+
return null;
|
|
42
|
+
current = parent;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function absolutePathArgs(argv, base) {
|
|
46
|
+
const absolute = (value) => value === "-" || path.isAbsolute(value) ? value : path.resolve(base, value);
|
|
47
|
+
const result = [];
|
|
48
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
49
|
+
const arg = argv[index] ?? "";
|
|
50
|
+
const eq = arg.indexOf("=");
|
|
51
|
+
if (eq > 0 && PATH_OPTIONS.has(arg.slice(0, eq))) {
|
|
52
|
+
result.push(`${arg.slice(0, eq)}=${absolute(arg.slice(eq + 1))}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
result.push(arg);
|
|
56
|
+
const next = argv[index + 1];
|
|
57
|
+
if (PATH_OPTIONS.has(arg) && next !== undefined && !next.startsWith("--")) {
|
|
58
|
+
result.push(absolute(next));
|
|
59
|
+
index += 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Run from a subdirectory of a Kibi workspace against that workspace's KB,
|
|
66
|
+
* the way git finds its repository: walk up to the nearest directory with
|
|
67
|
+
* `.kb/manifest.json`. Without this, a command run from `apps/server/`
|
|
68
|
+
* compiles a second, empty branch store there and reports an empty KB.
|
|
69
|
+
* An explicit KIBI_WORKSPACE (or alias) and `kibi init` keep the given
|
|
70
|
+
* directory. Relative path option values keep meaning what the user typed:
|
|
71
|
+
* the result is the directory to run in and argv with those values made
|
|
72
|
+
* absolute.
|
|
73
|
+
*/
|
|
74
|
+
// implements REQ-cli-subdirectory-workspace
|
|
75
|
+
export function resolveWorkspaceCwd(argv, cwd, env) {
|
|
76
|
+
const unchanged = { cwd, argv: [...argv] };
|
|
77
|
+
if (WORKSPACE_ENV_KEYS.some((key) => (env[key] ?? "").trim() !== ""))
|
|
78
|
+
return unchanged;
|
|
79
|
+
const command = argv.slice(2).find((arg) => !arg.startsWith("-"));
|
|
80
|
+
if (command === undefined || CWD_COMMANDS.has(command))
|
|
81
|
+
return unchanged;
|
|
82
|
+
const root = enclosingKbRoot(cwd);
|
|
83
|
+
if (root === null || root === path.resolve(cwd))
|
|
84
|
+
return unchanged;
|
|
85
|
+
return { cwd: root, argv: absolutePathArgs(argv, cwd) };
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
"fast-glob": "^3.2.12",
|
|
240
240
|
"gray-matter": "^4.0.3",
|
|
241
241
|
"js-yaml": "^4.1.0",
|
|
242
|
-
"kibi-core": "^0.
|
|
242
|
+
"kibi-core": "^0.26.1",
|
|
243
243
|
"kibi-plugin-builtin": "^0.5.3",
|
|
244
244
|
"kibi-plugin-sdk": "^0.5.0",
|
|
245
245
|
"kibi-swipl": "^1.0.1",
|
package/schema/validation.pl
CHANGED
|
@@ -157,6 +157,8 @@ validate_fact_shape(property_value, Props) :-
|
|
|
157
157
|
valid_operator(Op),
|
|
158
158
|
memberchk(value_type=VT, Props),
|
|
159
159
|
valid_value_type(VT),
|
|
160
|
+
( Op == matches -> VT == string ; true ),
|
|
161
|
+
( memberchk(Op, [includes, subset_of, excludes]) -> VT == set ; true ),
|
|
160
162
|
exactly_one_value_field(Props),
|
|
161
163
|
value_type_matches_field(VT, Props),
|
|
162
164
|
valid_optional_fact_enums(Props),
|
|
@@ -225,7 +227,7 @@ text_atom(Value, Atom) :- atom(Value), !, Atom = Value.
|
|
|
225
227
|
text_atom(Value, Atom) :- string(Value), atom_string(Atom, Value).
|
|
226
228
|
|
|
227
229
|
% valid_operator(+Op)
|
|
228
|
-
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte]), !.
|
|
230
|
+
valid_operator(Op) :- memberchk(Op, [eq, neq, lt, lte, gt, gte, matches, includes, subset_of, excludes]), !.
|
|
229
231
|
|
|
230
232
|
% valid_value_type(+VT)
|
|
231
233
|
valid_value_type(ValueType) :- memberchk(ValueType, [string, int, number, bool, set]), !.
|
|
@@ -98,7 +98,7 @@ function suggestionFor(row: EvidenceFloorRow): string {
|
|
|
98
98
|
return `Run kibi prove for the tests behind ${row.id} so its receipts match the current code, then rerun kibi check --fresh-evidence.`;
|
|
99
99
|
switch (row.requiredEvidence) {
|
|
100
100
|
case "proved":
|
|
101
|
-
return `Prove every applicable claim of ${row.id}${(row.gapDetails?.unprovedClaims ?? []).length > 0 ? ` (unproved: ${(row.gapDetails?.unprovedClaims ?? []).join(", ")})` : ""}: model each as a set fact (prove it with exhaustive or typecheck)
|
|
101
|
+
return `Prove every applicable claim of ${row.id}${(row.gapDetails?.unprovedClaims ?? []).length > 0 ? ` (unproved: ${(row.gapDetails?.unprovedClaims ?? []).join(", ")})` : ""}: model each as a set fact (prove it with exhaustive or typecheck), a numeric fact (arithmetic or smt), a rule, predicate or set fact about a predicate (rule or state_machine) or a string fact (regex), name it in a certificate test's proof_contract.proves, and run kibi prove; kibi proof explain ${row.id} shows each claim's bindings. A claim with no such fact can only be attested, so lower required_evidence to tested with the reason in the requirement if that is enough.`;
|
|
102
102
|
case "tested":
|
|
103
103
|
return `Link ${row.id} to a scenario with an end-to-end test that has a proof contract, run kibi prove, and fix its proof gaps (${row.proofGaps.join(", ") || "none listed"}${gapDetailLines(row).length > 0 ? `; ${gapDetailLines(row).join("; ")}` : ""}); kibi proof explain ${row.id} shows each one. Or lower required_evidence if a person decides it does not need tests.`;
|
|
104
104
|
default:
|
|
@@ -74,7 +74,7 @@ export function proofContractClaimViolations(
|
|
|
74
74
|
suggestion:
|
|
75
75
|
issue.claim_key === undefined
|
|
76
76
|
? `Add proof_contract.proves to ${issue.test_id} with the claim keys (CLAIM-...) of the requirement its certificate establishes; kibi proof explain on the requirement lists its claims.`
|
|
77
|
-
: `Name a modeled claim of a requirement ${issue.test_id} reaches through its scenarios, grounded by a fact this proof kind can express (a set fact for exhaustive or typecheck, a numeric fact for arithmetic or smt), or remove ${issue.claim_key} from proves.`,
|
|
77
|
+
: `Name a modeled claim of a requirement ${issue.test_id} reaches through its scenarios, grounded by a fact this proof kind can express (a set fact for exhaustive or typecheck, a numeric fact for arithmetic or smt, a rule, predicate or set fact for rule or state_machine, a string fact for regex), or remove ${issue.claim_key} from proves.`,
|
|
78
78
|
evidence: {
|
|
79
79
|
testId: issue.test_id,
|
|
80
80
|
...(issue.claim_key === undefined ? {} : { claimKey: issue.claim_key }),
|
|
@@ -55,13 +55,32 @@ export const modelRequirementSpec = {
|
|
|
55
55
|
},
|
|
56
56
|
operator: {
|
|
57
57
|
type: "string",
|
|
58
|
-
enum: [
|
|
59
|
-
|
|
60
|
-
"
|
|
58
|
+
enum: [
|
|
59
|
+
"eq",
|
|
60
|
+
"gt",
|
|
61
|
+
"gte",
|
|
62
|
+
"lt",
|
|
63
|
+
"lte",
|
|
64
|
+
"neq",
|
|
65
|
+
"bool",
|
|
66
|
+
"polarity",
|
|
67
|
+
"matches",
|
|
68
|
+
"includes",
|
|
69
|
+
"subset_of",
|
|
70
|
+
"excludes",
|
|
71
|
+
],
|
|
72
|
+
description:
|
|
73
|
+
"Optional extracted semantic claim operator. Use gt/lt for strict bounds ('greater than zero' is gt 0, not gte 0). A set claim takes a list value with eq (exactly these), includes (at least these), subset_of (only these) or excludes (none of these); matches takes a regular expression string. Example: 'eq'.",
|
|
61
74
|
},
|
|
62
75
|
value: {
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
anyOf: [
|
|
77
|
+
{ type: "string" },
|
|
78
|
+
{ type: "number" },
|
|
79
|
+
{ type: "boolean" },
|
|
80
|
+
{ type: "array", items: { type: "string" }, minItems: 1 },
|
|
81
|
+
],
|
|
82
|
+
description:
|
|
83
|
+
"Optional extracted semantic claim value: a string, number or boolean, or a list of strings for a set claim. Example: ['requireAuth'] with operator includes.",
|
|
65
84
|
},
|
|
66
85
|
provenance: {
|
|
67
86
|
type: "string",
|
|
@@ -244,15 +244,26 @@ export const ENTITY_PROPERTIES_SCHEMA = {
|
|
|
244
244
|
},
|
|
245
245
|
operator: {
|
|
246
246
|
type: "string",
|
|
247
|
-
enum: [
|
|
247
|
+
enum: [
|
|
248
|
+
"eq",
|
|
249
|
+
"neq",
|
|
250
|
+
"lt",
|
|
251
|
+
"lte",
|
|
252
|
+
"gt",
|
|
253
|
+
"gte",
|
|
254
|
+
"matches",
|
|
255
|
+
"includes",
|
|
256
|
+
"subset_of",
|
|
257
|
+
"excludes",
|
|
258
|
+
],
|
|
248
259
|
description:
|
|
249
|
-
"Optional comparison operator for property_value facts. Example: 'eq'.",
|
|
260
|
+
"Optional comparison operator for property_value facts. Example: 'eq'. 'matches' (string facts only): value_string is a JavaScript regular expression (u flag) every value must match as a whole. Set facts only: 'includes' means every member of value_set has the property and other members may have it too; 'subset_of' means every member that has the property is in value_set ('every env var the server reads is one of ...'); 'excludes' means no member of value_set has it ('never imports md5'); 'eq' on a set means exactly those members.",
|
|
250
261
|
},
|
|
251
262
|
value_type: {
|
|
252
263
|
type: "string",
|
|
253
264
|
enum: ["string", "int", "number", "bool", "set"],
|
|
254
265
|
description:
|
|
255
|
-
"Optional typed value discriminator for property_value facts. Pair with exactly one value_string, value_int, value_number, value_bool, or value_set (set:
|
|
266
|
+
"Optional typed value discriminator for property_value facts. Pair with exactly one value_string, value_int, value_number, value_bool, or value_set (set: a list of members, with operator eq, includes, subset_of or excludes); do not use generic value.",
|
|
256
267
|
},
|
|
257
268
|
value_string: {
|
|
258
269
|
type: "string",
|
|
@@ -28,7 +28,16 @@
|
|
|
28
28
|
import { createHash } from "node:crypto";
|
|
29
29
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
30
30
|
import path from "node:path";
|
|
31
|
+
import { evaluateSourceCode } from "../operations/proof/source-code-readers.js";
|
|
31
32
|
import { CODE_CERTIFICATE_VERSION } from "./proof-protocol.js";
|
|
33
|
+
import {
|
|
34
|
+
type SourceModel,
|
|
35
|
+
type SourceSpec,
|
|
36
|
+
describeSource,
|
|
37
|
+
readSource,
|
|
38
|
+
} from "./proof-sources.js";
|
|
39
|
+
|
|
40
|
+
export type { SourceModel, SourceSpec } from "./proof-sources.js";
|
|
32
41
|
|
|
33
42
|
/** Where `kibi prove` asks a run to leave its certificates. */
|
|
34
43
|
// implements REQ-kibi-code-proof-kinds
|
|
@@ -38,7 +47,15 @@ export const PROOF_CERTIFICATES_ENV = "KIBI_PROOF_CERTIFICATES";
|
|
|
38
47
|
export type CodeCertificate = Readonly<Record<string, unknown>> &
|
|
39
48
|
Readonly<{
|
|
40
49
|
version: typeof CODE_CERTIFICATE_VERSION;
|
|
41
|
-
kind:
|
|
50
|
+
kind:
|
|
51
|
+
| "exhaustive"
|
|
52
|
+
| "typecheck"
|
|
53
|
+
| "arithmetic"
|
|
54
|
+
| "smt"
|
|
55
|
+
| "rule"
|
|
56
|
+
| "state_machine"
|
|
57
|
+
| "regex"
|
|
58
|
+
| "source";
|
|
42
59
|
test_id: string;
|
|
43
60
|
}>;
|
|
44
61
|
|
|
@@ -250,6 +267,284 @@ export function smtCertificate(input: {
|
|
|
250
267
|
};
|
|
251
268
|
}
|
|
252
269
|
|
|
270
|
+
const certificateText = (value: unknown) =>
|
|
271
|
+
typeof value === "string" ? value : String(value);
|
|
272
|
+
|
|
273
|
+
/** A finite domain a rule certificate ranges over. */
|
|
274
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
275
|
+
export type RuleDomain = Readonly<{
|
|
276
|
+
/** The domain's symbol; omit it for a domain the contract reads from a source. */
|
|
277
|
+
symbolId?: string;
|
|
278
|
+
members: Iterable<unknown>;
|
|
279
|
+
}>;
|
|
280
|
+
|
|
281
|
+
/** A predicate the code decides, and the argument types it takes. */
|
|
282
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
283
|
+
export type RulePredicate = Readonly<{
|
|
284
|
+
symbolId: string;
|
|
285
|
+
argTypes: readonly string[];
|
|
286
|
+
decide: (...args: never[]) => boolean | Promise<boolean>;
|
|
287
|
+
}>;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Call every predicate on every combination of its argument domains and
|
|
291
|
+
* record what the code decides. Import the domains and functions from their
|
|
292
|
+
* source (never restate them): Kibi evaluates the same exports at ingest and
|
|
293
|
+
* rejects a certificate whose tables differ, then model checks each claim's
|
|
294
|
+
* rule, predicate or set fact against the tables.
|
|
295
|
+
*/
|
|
296
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
297
|
+
export async function ruleCertificate(input: {
|
|
298
|
+
testId: string;
|
|
299
|
+
domains: Readonly<Record<string, RuleDomain>>;
|
|
300
|
+
predicates: Readonly<Record<string, RulePredicate>>;
|
|
301
|
+
}): Promise<CodeCertificate> {
|
|
302
|
+
const values: Record<string, unknown[]> = {};
|
|
303
|
+
const domains: Record<string, { symbol_id?: string; members: string[] }> = {};
|
|
304
|
+
for (const [type, domain] of Object.entries(input.domains)) {
|
|
305
|
+
values[type] = [...domain.members];
|
|
306
|
+
domains[type] = {
|
|
307
|
+
...(domain.symbolId !== undefined ? { symbol_id: domain.symbolId } : {}),
|
|
308
|
+
members: values[type].map(certificateText),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const predicates: Record<
|
|
312
|
+
string,
|
|
313
|
+
{
|
|
314
|
+
symbol_id: string;
|
|
315
|
+
arg_types: string[];
|
|
316
|
+
rows: { args: string[]; value?: boolean; error?: string }[];
|
|
317
|
+
}
|
|
318
|
+
> = {};
|
|
319
|
+
for (const [name, predicate] of Object.entries(input.predicates)) {
|
|
320
|
+
const lists = predicate.argTypes.map((type) => {
|
|
321
|
+
const list = values[type];
|
|
322
|
+
if (list === undefined)
|
|
323
|
+
throw new Error(`no domain for argument type ${type} of ${name}`);
|
|
324
|
+
return list;
|
|
325
|
+
});
|
|
326
|
+
const rows: { args: string[]; value?: boolean; error?: string }[] = [];
|
|
327
|
+
const count = lists.reduce((product, list) => product * list.length, 1);
|
|
328
|
+
for (let index = 0; index < count; index++) {
|
|
329
|
+
let rest = index;
|
|
330
|
+
const tuple: unknown[] = [];
|
|
331
|
+
for (let position = lists.length - 1; position >= 0; position--) {
|
|
332
|
+
const list = lists[position] as unknown[];
|
|
333
|
+
tuple[position] = list[rest % list.length];
|
|
334
|
+
rest = Math.floor(rest / list.length);
|
|
335
|
+
}
|
|
336
|
+
const args = tuple.map(certificateText);
|
|
337
|
+
try {
|
|
338
|
+
const value = await (
|
|
339
|
+
predicate.decide as (...args: unknown[]) => unknown
|
|
340
|
+
)(...tuple);
|
|
341
|
+
rows.push(
|
|
342
|
+
typeof value === "boolean"
|
|
343
|
+
? { args, value }
|
|
344
|
+
: { args, error: `returned ${typeof value}, not a boolean` },
|
|
345
|
+
);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
rows.push({
|
|
348
|
+
args,
|
|
349
|
+
error: error instanceof Error ? error.message : String(error),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
predicates[name] = {
|
|
354
|
+
symbol_id: predicate.symbolId,
|
|
355
|
+
arg_types: [...predicate.argTypes],
|
|
356
|
+
rows,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
version: CODE_CERTIFICATE_VERSION,
|
|
361
|
+
kind: "rule",
|
|
362
|
+
test_id: input.testId,
|
|
363
|
+
domains,
|
|
364
|
+
predicates,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** The calls of a rule certificate that threw or returned a non-boolean. */
|
|
369
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
370
|
+
export function failedCalls(certificate: CodeCertificate): string[] {
|
|
371
|
+
const predicates = (certificate.predicates ?? {}) as Record<
|
|
372
|
+
string,
|
|
373
|
+
{ rows?: { args: string[]; error?: string }[] }
|
|
374
|
+
>;
|
|
375
|
+
return Object.entries(predicates).flatMap(([name, table]) =>
|
|
376
|
+
(table.rows ?? [])
|
|
377
|
+
.filter((row) => row.error !== undefined)
|
|
378
|
+
.map((row) => `${name}(${row.args.join(", ")}): ${row.error}`),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
type Transitions =
|
|
383
|
+
| ReadonlyMap<unknown, unknown>
|
|
384
|
+
| Readonly<Record<string, unknown>>
|
|
385
|
+
| readonly unknown[]
|
|
386
|
+
| ((state: never, event: never) => unknown);
|
|
387
|
+
|
|
388
|
+
function nextStates(next: unknown): unknown[] {
|
|
389
|
+
if (next === null || next === undefined || next === false) return [];
|
|
390
|
+
if (Array.isArray(next)) return next;
|
|
391
|
+
if (next instanceof Set) return [...next];
|
|
392
|
+
if (next instanceof Map) return [...next.values()];
|
|
393
|
+
if (typeof next === "object") return Object.values(next);
|
|
394
|
+
return [next];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Record a state machine as the code defines it: a transition table (an
|
|
399
|
+
* object or Map from each state to its next state, states, or event map; or
|
|
400
|
+
* a list of [from, to] pairs) or a next-state function called on every state
|
|
401
|
+
* and event (a throw, null or undefined means no transition). Kibi reads the
|
|
402
|
+
* same exports at ingest and checks each claim's transitions, reachability,
|
|
403
|
+
* initial and terminal states against the machine.
|
|
404
|
+
*/
|
|
405
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
406
|
+
export async function stateMachineCertificate(input: {
|
|
407
|
+
testId: string;
|
|
408
|
+
symbolId: string;
|
|
409
|
+
transitions: Transitions;
|
|
410
|
+
/** For a next-state function: the states and events it is called on. */
|
|
411
|
+
states?: Iterable<unknown>;
|
|
412
|
+
events?: Iterable<unknown>;
|
|
413
|
+
initial?: unknown;
|
|
414
|
+
}): Promise<CodeCertificate> {
|
|
415
|
+
const states = new Set<string>();
|
|
416
|
+
const edges = new Map<string, [string, string]>();
|
|
417
|
+
const edge = (from: unknown, to: unknown) => {
|
|
418
|
+
const pair: [string, string] = [certificateText(from), certificateText(to)];
|
|
419
|
+
states.add(pair[0]);
|
|
420
|
+
states.add(pair[1]);
|
|
421
|
+
edges.set(pair.join("\u0000"), pair);
|
|
422
|
+
};
|
|
423
|
+
const table = input.transitions;
|
|
424
|
+
if (typeof table === "function") {
|
|
425
|
+
const events = [...(input.events ?? [])];
|
|
426
|
+
for (const state of input.states ?? []) {
|
|
427
|
+
states.add(certificateText(state));
|
|
428
|
+
for (const event of events) {
|
|
429
|
+
let next: unknown;
|
|
430
|
+
try {
|
|
431
|
+
next = await (table as (state: unknown, event: unknown) => unknown)(
|
|
432
|
+
state,
|
|
433
|
+
event,
|
|
434
|
+
);
|
|
435
|
+
} catch {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
for (const target of nextStates(next)) edge(state, target);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} else if (Array.isArray(table)) {
|
|
442
|
+
for (const entry of table as unknown[]) {
|
|
443
|
+
if (Array.isArray(entry) && entry.length === 2) edge(entry[0], entry[1]);
|
|
444
|
+
else if (entry !== null && typeof entry === "object")
|
|
445
|
+
edge((entry as { from: unknown }).from, (entry as { to: unknown }).to);
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
const entries =
|
|
449
|
+
table instanceof Map
|
|
450
|
+
? [...table.entries()]
|
|
451
|
+
: Object.entries(table as Record<string, unknown>);
|
|
452
|
+
for (const [from, next] of entries) {
|
|
453
|
+
states.add(certificateText(from));
|
|
454
|
+
for (const target of nextStates(next)) edge(from, target);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const initial =
|
|
458
|
+
input.initial === undefined
|
|
459
|
+
? undefined
|
|
460
|
+
: (typeof input.initial === "object" && input.initial !== null
|
|
461
|
+
? [...(input.initial as Iterable<unknown>)]
|
|
462
|
+
: [input.initial]
|
|
463
|
+
)
|
|
464
|
+
.map(certificateText)
|
|
465
|
+
.sort();
|
|
466
|
+
return {
|
|
467
|
+
version: CODE_CERTIFICATE_VERSION,
|
|
468
|
+
kind: "state_machine",
|
|
469
|
+
test_id: input.testId,
|
|
470
|
+
machine: {
|
|
471
|
+
symbol_id: input.symbolId,
|
|
472
|
+
states: [...states].sort(),
|
|
473
|
+
transitions: [...edges.values()].sort((a, b) =>
|
|
474
|
+
a.join("\u0000") < b.join("\u0000") ? -1 : 1,
|
|
475
|
+
),
|
|
476
|
+
...(initial !== undefined ? { initial } : {}),
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Record the pattern (a RegExp) or string constant a string claim is about.
|
|
483
|
+
* Kibi reads the same export at ingest and decides the claim exactly: a
|
|
484
|
+
* pattern's language against the fact's, or the string against the fact.
|
|
485
|
+
*/
|
|
486
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
487
|
+
export function regexCertificate(input: {
|
|
488
|
+
testId: string;
|
|
489
|
+
symbolId: string;
|
|
490
|
+
value: RegExp | string;
|
|
491
|
+
}): CodeCertificate {
|
|
492
|
+
return {
|
|
493
|
+
version: CODE_CERTIFICATE_VERSION,
|
|
494
|
+
kind: "regex",
|
|
495
|
+
test_id: input.testId,
|
|
496
|
+
pattern:
|
|
497
|
+
typeof input.value === "string"
|
|
498
|
+
? { symbol_id: input.symbolId, type: "string", value: input.value }
|
|
499
|
+
: {
|
|
500
|
+
symbol_id: input.symbolId,
|
|
501
|
+
type: "regexp",
|
|
502
|
+
source: input.value.source,
|
|
503
|
+
flags: input.value.flags,
|
|
504
|
+
flavor: "js",
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* The certificate of a source proof. Kibi reads the contract's source
|
|
511
|
+
* itself at ingest and stores its own read, so the run only names the test
|
|
512
|
+
* and, optionally, the source (which must be the contract's). A run may also
|
|
513
|
+
* leave nothing: a passed source proof needs no certificate from the test.
|
|
514
|
+
*/
|
|
515
|
+
// implements REQ-kibi-source-proof-kinds
|
|
516
|
+
export function sourceCertificate(input: {
|
|
517
|
+
testId: string;
|
|
518
|
+
source?: SourceSpec;
|
|
519
|
+
}): CodeCertificate {
|
|
520
|
+
return {
|
|
521
|
+
version: CODE_CERTIFICATE_VERSION,
|
|
522
|
+
kind: "source",
|
|
523
|
+
test_id: input.testId,
|
|
524
|
+
...(input.source !== undefined ? { source: input.source } : {}),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Read a project source the way Kibi does at ingest: the members (and the
|
|
530
|
+
* tables, objects or routes) of a JSON or YAML file, a glob, an OpenAPI
|
|
531
|
+
* document, the references the code makes, a data schema, a route table or
|
|
532
|
+
* a validator. A rule proof whose contract reads a domain from a source
|
|
533
|
+
* uses this to range its predicates over the same members.
|
|
534
|
+
*/
|
|
535
|
+
// implements REQ-kibi-source-proof-kinds
|
|
536
|
+
export async function readProjectSource(
|
|
537
|
+
source: SourceSpec,
|
|
538
|
+
workspaceRoot: string = process.cwd(),
|
|
539
|
+
): Promise<SourceModel> {
|
|
540
|
+
const read = await readSource(workspaceRoot, source, evaluateSourceCode);
|
|
541
|
+
if (!read.ok)
|
|
542
|
+
throw new Error(
|
|
543
|
+
`Kibi could not read ${describeSource(source)}: ${read.reason}`,
|
|
544
|
+
);
|
|
545
|
+
return read.model;
|
|
546
|
+
}
|
|
547
|
+
|
|
253
548
|
/**
|
|
254
549
|
* Leave the certificate where `kibi prove` reads it. Returns the file written,
|
|
255
550
|
* or null outside a `kibi prove` run.
|