kibi-cli 2.31.1 → 2.35.1
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/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/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/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 +109 -5
- 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 +10 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +106 -17
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +16 -5
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +19 -0
- package/dist/diagnostics.js +1 -1
- package/dist/engine.js +1 -1
- package/dist/extractors/markdown.d.ts +4 -2
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +17 -0
- 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/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 +72 -0
- package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -0
- package/dist/operations/migration/proof-contract-claims.js +215 -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 +92 -0
- 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/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/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +39 -20
- 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 +41 -0
- package/dist/operations/proof/claim-bindings.d.ts.map +1 -0
- package/dist/operations/proof/claim-bindings.js +364 -0
- package/dist/operations/proof/code-certificates.d.ts +155 -0
- package/dist/operations/proof/code-certificates.d.ts.map +1 -0
- package/dist/operations/proof/code-certificates.js +894 -0
- 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 +14 -1
- package/dist/operations/proof/code-scope.d.ts.map +1 -1
- package/dist/operations/proof/code-scope.js +44 -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 +42 -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/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/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +4 -0
- 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 +234 -4
- package/dist/operations/semantic-advisor/types.d.ts +3 -2
- 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/prolog.d.ts.map +1 -1
- package/dist/prolog.js +13 -3
- 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 +315 -11
- 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/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 +289 -7
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +28 -4
- 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 +289 -7
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/proof-certificates.d.ts +163 -0
- package/dist/public/proof-certificates.d.ts.map +1 -0
- package/dist/public/proof-certificates.js +419 -0
- package/dist/public/proof-protocol.d.ts +259 -3
- package/dist/public/proof-protocol.d.ts.map +1 -1
- package/dist/public/proof-protocol.js +524 -3
- package/dist/public/proof-receipt.d.ts +90 -6
- package/dist/public/proof-receipt.d.ts.map +1 -1
- package/dist/public/proof-receipt.js +88 -7
- 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 +39 -2
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-commit-review/SKILL.md +8 -8
- package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
- package/dist/public/skills/kibi-decisions/SKILL.md +13 -2
- package/dist/public/skills/kibi-decisions/resources/decision-records.md +9 -1
- package/dist/public/skills/kibi-freshness/SKILL.md +5 -1
- package/dist/public/skills/kibi-traceability/SKILL.md +12 -1
- package/dist/public/skills/kibi-usage/SKILL.md +8 -4
- package/dist/public/skills/kibi-usage/resources/proof.md +221 -20
- package/dist/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/dist/report/html-report.css +83 -11
- 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 +214 -22
- package/dist/report/report-view.d.ts +2 -2
- package/dist/report/report-view.d.ts.map +1 -1
- package/dist/report/report-view.js +8 -0
- package/dist/schemas/entity.schema.json +404 -5
- 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/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/impact-knowledge.d.ts.map +1 -1
- package/dist/traceability/impact-knowledge.js +1 -0
- 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/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +1 -0
- package/dist/types/entities.d.ts +5 -2
- 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 +7 -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 +6 -2
- package/schema/entities.pl +6 -0
- package/schema/validation.pl +7 -2
- 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/modeling.ts +24 -5
- package/src/public/operations/specs/mutation-schemas.ts +30 -4
- package/src/public/operations/specs/reporting.ts +20 -10
- package/src/public/proof-certificates.ts +566 -0
- package/src/public/proof-protocol-types.d.ts +69 -1
- package/src/public/proof-protocol.ts +694 -3
- package/src/public/proof-receipt.ts +133 -7
- package/src/public/proof-sources.ts +1656 -0
- package/src/public/schemas/entity.ts +51 -3
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-commit-review/SKILL.md +8 -8
- package/src/public/skills/kibi-commit-review/resources/staged-gate.md +12 -7
- package/src/public/skills/kibi-decisions/SKILL.md +13 -2
- package/src/public/skills/kibi-decisions/resources/decision-records.md +9 -1
- package/src/public/skills/kibi-freshness/SKILL.md +5 -1
- package/src/public/skills/kibi-traceability/SKILL.md +12 -1
- package/src/public/skills/kibi-usage/SKILL.md +8 -4
- package/src/public/skills/kibi-usage/resources/proof.md +221 -20
- package/src/public/skills/kibi-usage/resources/workflows.md +4 -4
- package/src/schemas/entity.schema.json +404 -5
- 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/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
|
@@ -0,0 +1,601 @@
|
|
|
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
|
+
/**
|
|
19
|
+
* The finite models behind the rule, state_machine and regex proof kinds,
|
|
20
|
+
* read from the code by Kibi itself. A certificate from the test run records
|
|
21
|
+
* what the test saw; at ingest Kibi imports the same exports, recomputes the
|
|
22
|
+
* model, and accepts the certificate only when the two agree, so a test
|
|
23
|
+
* cannot hand Kibi a table, a machine or a pattern the code does not have.
|
|
24
|
+
*/
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { languageAccepts, languagesDisjoint, languagesEqual, UnsupportedPattern, } from "./regular-language.js";
|
|
27
|
+
import { loadSymbol } from "./code-certificates.js";
|
|
28
|
+
import { describeSource, readSource } from "../../public/proof-sources.js";
|
|
29
|
+
import { runCodeEvaluator, TS_RESOLVE_HOOKS } from "./code-runner.js";
|
|
30
|
+
import { evaluateSourceCode } from "./source-code-readers.js";
|
|
31
|
+
/** The most predicate calls one rule certificate may need. */
|
|
32
|
+
export const MAX_TABLE_ROWS = 100_000;
|
|
33
|
+
// Built on first use: code-certificates.ts imports this module, so its
|
|
34
|
+
// TS_RESOLVE_HOOKS is not initialized yet while this module loads.
|
|
35
|
+
const jsModelEvaluator = () => `
|
|
36
|
+
const plan = JSON.parse(process.argv.at(-1));
|
|
37
|
+
const { pathToFileURL } = await import("node:url");
|
|
38
|
+
if (typeof globalThis.Bun === "undefined") {
|
|
39
|
+
const { register } = await import("node:module");
|
|
40
|
+
register?.("data:text/javascript," + encodeURIComponent(${JSON.stringify(TS_RESOLVE_HOOKS)}));
|
|
41
|
+
}
|
|
42
|
+
const modules = new Map();
|
|
43
|
+
async function load(ref) {
|
|
44
|
+
if (!modules.has(ref.file)) modules.set(ref.file, await import(pathToFileURL(ref.file).href));
|
|
45
|
+
const value = modules.get(ref.file)[ref.export];
|
|
46
|
+
if (value === undefined) throw new Error(ref.export + " is not exported from " + ref.file);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function items(value, name) {
|
|
50
|
+
if (Array.isArray(value)) return value;
|
|
51
|
+
if (value instanceof Set) return [...value];
|
|
52
|
+
if (value instanceof Map) return [...value.keys()];
|
|
53
|
+
if (value !== null && typeof value === "object") return Object.keys(value);
|
|
54
|
+
throw new Error(name + " is not an array, set, map or object");
|
|
55
|
+
}
|
|
56
|
+
const text = (value) => (typeof value === "string" ? value : String(value));
|
|
57
|
+
const message = (error) => (error instanceof Error ? error.message : String(error));
|
|
58
|
+
async function main() {
|
|
59
|
+
if (plan.mode === "table") {
|
|
60
|
+
const values = {};
|
|
61
|
+
const domains = {};
|
|
62
|
+
for (const [type, ref] of Object.entries(plan.domains)) {
|
|
63
|
+
values[type] = ref.values ?? items(await load(ref), ref.export);
|
|
64
|
+
domains[type] = values[type].map(text);
|
|
65
|
+
}
|
|
66
|
+
const predicates = {};
|
|
67
|
+
let total = 0;
|
|
68
|
+
for (const [name, ref] of Object.entries(plan.predicates)) {
|
|
69
|
+
const decide = await load(ref);
|
|
70
|
+
if (typeof decide !== "function") throw new Error(ref.export + " is not a function");
|
|
71
|
+
for (const type of ref.arg_types)
|
|
72
|
+
if (!(type in values)) throw new Error("no domain for argument type " + type + " of " + name);
|
|
73
|
+
const lists = ref.arg_types.map((type) => values[type]);
|
|
74
|
+
const count = lists.reduce((product, list) => product * list.length, 1);
|
|
75
|
+
total += count;
|
|
76
|
+
if (total > plan.limit) throw new Error("the predicate tables need more than " + plan.limit + " calls");
|
|
77
|
+
const rows = [];
|
|
78
|
+
for (let index = 0; index < count; index++) {
|
|
79
|
+
let rest = index;
|
|
80
|
+
const tuple = [];
|
|
81
|
+
for (let position = lists.length - 1; position >= 0; position--) {
|
|
82
|
+
const list = lists[position];
|
|
83
|
+
tuple[position] = list[rest % list.length];
|
|
84
|
+
rest = Math.floor(rest / list.length);
|
|
85
|
+
}
|
|
86
|
+
const args = tuple.map(text);
|
|
87
|
+
try {
|
|
88
|
+
const value = await decide(...tuple);
|
|
89
|
+
rows.push(typeof value === "boolean" ? { args, value } : { args, error: "returned " + typeof value + ", not a boolean" });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
rows.push({ args, error: message(error) });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
predicates[name] = rows;
|
|
95
|
+
}
|
|
96
|
+
return { domains, predicates };
|
|
97
|
+
}
|
|
98
|
+
if (plan.mode === "machine") {
|
|
99
|
+
const table = await load(plan.transitions);
|
|
100
|
+
const edges = new Map();
|
|
101
|
+
const states = new Set();
|
|
102
|
+
const edge = (from, to) => {
|
|
103
|
+
const pair = [text(from), text(to)];
|
|
104
|
+
states.add(pair[0]);
|
|
105
|
+
states.add(pair[1]);
|
|
106
|
+
edges.set(pair.join("\\u0000"), pair);
|
|
107
|
+
};
|
|
108
|
+
const targets = (next) => {
|
|
109
|
+
if (next === null || next === undefined || next === false) return [];
|
|
110
|
+
if (Array.isArray(next)) return next;
|
|
111
|
+
if (next instanceof Set) return [...next];
|
|
112
|
+
if (next instanceof Map) return [...next.values()];
|
|
113
|
+
if (typeof next === "object") return Object.values(next);
|
|
114
|
+
return [next];
|
|
115
|
+
};
|
|
116
|
+
if (typeof table === "function") {
|
|
117
|
+
const stateList = items(await load(plan.domains.state), plan.domains.state.export);
|
|
118
|
+
const eventList = items(await load(plan.domains.event), plan.domains.event.export);
|
|
119
|
+
for (const state of stateList) {
|
|
120
|
+
states.add(text(state));
|
|
121
|
+
for (const event of eventList) {
|
|
122
|
+
let next;
|
|
123
|
+
try {
|
|
124
|
+
next = await table(state, event);
|
|
125
|
+
} catch {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const target of targets(next)) edge(state, target);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else if (Array.isArray(table)) {
|
|
132
|
+
for (const entry of table) {
|
|
133
|
+
if (Array.isArray(entry) && entry.length === 2) edge(entry[0], entry[1]);
|
|
134
|
+
else if (entry !== null && typeof entry === "object" && "from" in entry && "to" in entry) edge(entry.from, entry.to);
|
|
135
|
+
else throw new Error(plan.transitions.export + " lists an entry that is not a [from, to] pair or {from, to}");
|
|
136
|
+
}
|
|
137
|
+
} else if (table instanceof Map || (table !== null && typeof table === "object")) {
|
|
138
|
+
const entries = table instanceof Map ? [...table.entries()] : Object.entries(table);
|
|
139
|
+
for (const [from, next] of entries) {
|
|
140
|
+
states.add(text(from));
|
|
141
|
+
for (const target of targets(next)) edge(from, target);
|
|
142
|
+
}
|
|
143
|
+
} else throw new Error(plan.transitions.export + " is not a transition table or a next-state function");
|
|
144
|
+
const out = { states: [...states].sort(), transitions: [...edges.values()].sort((a, b) => (a.join("\\u0000") < b.join("\\u0000") ? -1 : 1)) };
|
|
145
|
+
if (plan.initial) {
|
|
146
|
+
const initial = await load(plan.initial);
|
|
147
|
+
out.initial = (typeof initial === "object" && initial !== null ? items(initial, plan.initial.export) : [initial]).map(text).sort();
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
if (plan.mode === "pattern") {
|
|
152
|
+
const value = await load(plan.pattern);
|
|
153
|
+
if (value instanceof RegExp) return { type: "regexp", source: value.source, flags: value.flags, flavor: "js" };
|
|
154
|
+
if (typeof value === "string") return { type: "string", value };
|
|
155
|
+
throw new Error(plan.pattern.export + " is not a RegExp or a string");
|
|
156
|
+
}
|
|
157
|
+
throw new Error("unknown plan mode " + plan.mode);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
console.log(JSON.stringify({ ok: true, model: await main() }));
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.log(JSON.stringify({ ok: false, error: message(error) }));
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
const PYTHON_MODEL_EVALUATOR = `
|
|
166
|
+
import enum, importlib, inspect, itertools, json, os, re, sys
|
|
167
|
+
plan = json.loads(sys.argv[-1])
|
|
168
|
+
modules = {}
|
|
169
|
+
def module_for(file):
|
|
170
|
+
if file not in modules:
|
|
171
|
+
directory, module_name = os.path.split(os.path.splitext(file)[0])
|
|
172
|
+
parts = [] if module_name == "__init__" else [module_name]
|
|
173
|
+
while os.path.exists(os.path.join(directory, "__init__.py")):
|
|
174
|
+
directory, package = os.path.split(directory)
|
|
175
|
+
parts.insert(0, package)
|
|
176
|
+
if directory not in sys.path:
|
|
177
|
+
sys.path.insert(0, directory)
|
|
178
|
+
modules[file] = importlib.import_module(".".join(parts))
|
|
179
|
+
return modules[file]
|
|
180
|
+
def load(ref):
|
|
181
|
+
module = module_for(ref["file"])
|
|
182
|
+
if not hasattr(module, ref["export"]):
|
|
183
|
+
raise ValueError(ref["export"] + " is not defined in " + ref["file"])
|
|
184
|
+
return getattr(module, ref["export"])
|
|
185
|
+
def text(item):
|
|
186
|
+
if isinstance(item, enum.Enum):
|
|
187
|
+
return text(item.value)
|
|
188
|
+
if isinstance(item, bool):
|
|
189
|
+
return "true" if item else "false"
|
|
190
|
+
return str(item)
|
|
191
|
+
def items(value, name):
|
|
192
|
+
if isinstance(value, type) and issubclass(value, enum.Enum):
|
|
193
|
+
return list(value)
|
|
194
|
+
if isinstance(value, dict):
|
|
195
|
+
return list(value.keys())
|
|
196
|
+
if isinstance(value, (list, tuple)):
|
|
197
|
+
return list(value)
|
|
198
|
+
if isinstance(value, (set, frozenset)):
|
|
199
|
+
return sorted(value, key=text)
|
|
200
|
+
raise ValueError(name + " is not a list, tuple, set, dict or Enum")
|
|
201
|
+
def targets(nxt):
|
|
202
|
+
if nxt is None or nxt is False:
|
|
203
|
+
return []
|
|
204
|
+
if isinstance(nxt, dict):
|
|
205
|
+
return list(nxt.values())
|
|
206
|
+
if isinstance(nxt, (list, tuple, set, frozenset)):
|
|
207
|
+
return list(nxt)
|
|
208
|
+
return [nxt]
|
|
209
|
+
FLAG_LETTERS = [(re.IGNORECASE, "i"), (re.DOTALL, "s"), (re.ASCII, "a"), (re.MULTILINE, "m"), (re.VERBOSE, "x"), (re.LOCALE, "L")]
|
|
210
|
+
def main():
|
|
211
|
+
mode = plan["mode"]
|
|
212
|
+
if mode == "table":
|
|
213
|
+
values, domains = {}, {}
|
|
214
|
+
for kind, ref in plan["domains"].items():
|
|
215
|
+
values[kind] = list(ref["values"]) if "values" in ref else items(load(ref), ref["export"])
|
|
216
|
+
domains[kind] = [text(v) for v in values[kind]]
|
|
217
|
+
predicates, total = {}, 0
|
|
218
|
+
for name, ref in plan["predicates"].items():
|
|
219
|
+
decide = load(ref)
|
|
220
|
+
if not callable(decide):
|
|
221
|
+
raise ValueError(ref["export"] + " is not callable")
|
|
222
|
+
for kind in ref["arg_types"]:
|
|
223
|
+
if kind not in values:
|
|
224
|
+
raise ValueError("no domain for argument type " + kind + " of " + name)
|
|
225
|
+
lists = [values[kind] for kind in ref["arg_types"]]
|
|
226
|
+
count = 1
|
|
227
|
+
for lst in lists:
|
|
228
|
+
count *= len(lst)
|
|
229
|
+
total += count
|
|
230
|
+
if total > plan["limit"]:
|
|
231
|
+
raise ValueError("the predicate tables need more than " + str(plan["limit"]) + " calls")
|
|
232
|
+
rows = []
|
|
233
|
+
for combo in itertools.product(*lists):
|
|
234
|
+
args = [text(v) for v in combo]
|
|
235
|
+
try:
|
|
236
|
+
result = decide(*combo)
|
|
237
|
+
if inspect.isawaitable(result):
|
|
238
|
+
import asyncio
|
|
239
|
+
result = asyncio.run(result)
|
|
240
|
+
if isinstance(result, bool):
|
|
241
|
+
rows.append({"args": args, "value": result})
|
|
242
|
+
else:
|
|
243
|
+
rows.append({"args": args, "error": "returned " + type(result).__name__ + ", not a bool"})
|
|
244
|
+
except Exception as error:
|
|
245
|
+
rows.append({"args": args, "error": str(error) or type(error).__name__})
|
|
246
|
+
predicates[name] = rows
|
|
247
|
+
return {"domains": domains, "predicates": predicates}
|
|
248
|
+
if mode == "machine":
|
|
249
|
+
table = load(plan["transitions"])
|
|
250
|
+
edges, states = {}, set()
|
|
251
|
+
def edge(source, target):
|
|
252
|
+
pair = [text(source), text(target)]
|
|
253
|
+
states.update(pair)
|
|
254
|
+
edges["\\0".join(pair)] = pair
|
|
255
|
+
if callable(table) and not isinstance(table, type):
|
|
256
|
+
state_list = items(load(plan["domains"]["state"]), plan["domains"]["state"]["export"])
|
|
257
|
+
event_list = items(load(plan["domains"]["event"]), plan["domains"]["event"]["export"])
|
|
258
|
+
for state in state_list:
|
|
259
|
+
states.add(text(state))
|
|
260
|
+
for event in event_list:
|
|
261
|
+
try:
|
|
262
|
+
nxt = table(state, event)
|
|
263
|
+
except Exception:
|
|
264
|
+
continue
|
|
265
|
+
for target in targets(nxt):
|
|
266
|
+
edge(state, target)
|
|
267
|
+
elif isinstance(table, dict):
|
|
268
|
+
for source, nxt in table.items():
|
|
269
|
+
states.add(text(source))
|
|
270
|
+
for target in targets(nxt):
|
|
271
|
+
edge(source, target)
|
|
272
|
+
elif isinstance(table, (list, tuple, set, frozenset)):
|
|
273
|
+
for entry in table:
|
|
274
|
+
if isinstance(entry, (list, tuple)) and len(entry) == 2:
|
|
275
|
+
edge(entry[0], entry[1])
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError(plan["transitions"]["export"] + " lists an entry that is not a (from, to) pair")
|
|
278
|
+
else:
|
|
279
|
+
raise ValueError(plan["transitions"]["export"] + " is not a transition table or a next-state function")
|
|
280
|
+
out = {"states": sorted(states), "transitions": [edges[key] for key in sorted(edges)]}
|
|
281
|
+
if plan.get("initial"):
|
|
282
|
+
initial = load(plan["initial"])
|
|
283
|
+
listed = items(initial, plan["initial"]["export"]) if isinstance(initial, (list, tuple, set, frozenset, dict)) else [initial]
|
|
284
|
+
out["initial"] = sorted(text(v) for v in listed)
|
|
285
|
+
return out
|
|
286
|
+
if mode == "pattern":
|
|
287
|
+
value = load(plan["pattern"])
|
|
288
|
+
if isinstance(value, re.Pattern):
|
|
289
|
+
if isinstance(value.pattern, bytes):
|
|
290
|
+
raise ValueError(plan["pattern"]["export"] + " is a bytes pattern")
|
|
291
|
+
flags = "".join(letter for flag, letter in FLAG_LETTERS if value.flags & flag)
|
|
292
|
+
return {"type": "regexp", "source": value.pattern, "flags": flags, "flavor": "python"}
|
|
293
|
+
if isinstance(value, str):
|
|
294
|
+
return {"type": "string", "value": value}
|
|
295
|
+
raise ValueError(plan["pattern"]["export"] + " is not a compiled pattern or a str")
|
|
296
|
+
raise ValueError("unknown plan mode " + mode)
|
|
297
|
+
try:
|
|
298
|
+
print(json.dumps({"ok": True, "model": main()}))
|
|
299
|
+
except Exception as error:
|
|
300
|
+
print(json.dumps({"ok": False, "error": str(error) or type(error).__name__}))
|
|
301
|
+
`;
|
|
302
|
+
async function resolveReference(context, reference, label) {
|
|
303
|
+
const symbol = await loadSymbol(context, reference.symbol_id);
|
|
304
|
+
if (!symbol || symbol.sourceFile === "")
|
|
305
|
+
return `the ${label} symbol ${reference.symbol_id} has no source file`;
|
|
306
|
+
return {
|
|
307
|
+
file: path.resolve(context.workspaceRoot, symbol.sourceFile),
|
|
308
|
+
export: reference.export ?? symbol.title,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Run one evaluation plan (table, machine or pattern mode) against the code
|
|
313
|
+
* files it names, in the language of those files.
|
|
314
|
+
*/
|
|
315
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
316
|
+
export function evaluateCodeModel(workspaceRoot, files, plan) {
|
|
317
|
+
const python = files.map((file) => /\.py$/i.test(file.file));
|
|
318
|
+
if (python.some((value) => value !== python[0]))
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
reason: "the contract's sources mix Python and JavaScript files; one certificate reads one language",
|
|
322
|
+
};
|
|
323
|
+
const first = files[0];
|
|
324
|
+
if (first === undefined)
|
|
325
|
+
return { ok: false, reason: "the contract names no source" };
|
|
326
|
+
const run = runCodeEvaluator(workspaceRoot, path.relative(workspaceRoot, first.file), { js: jsModelEvaluator(), python: PYTHON_MODEL_EVALUATOR }, [JSON.stringify(plan)], (parsed) => parsed !== null && typeof parsed === "object" && "ok" in parsed);
|
|
327
|
+
if (!run.ok)
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
reason: `Kibi could not evaluate the code${run.failure ? `: ${run.failure}` : ""}`,
|
|
331
|
+
};
|
|
332
|
+
const value = run.value;
|
|
333
|
+
return value.ok
|
|
334
|
+
? { ok: true, model: value.model }
|
|
335
|
+
: { ok: false, reason: `Kibi could not evaluate the code: ${value.error ?? "unknown error"}` };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Evaluate every predicate a rule contract names over every combination of
|
|
339
|
+
* its argument domains, with the argument types the certificate declares.
|
|
340
|
+
*/
|
|
341
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
342
|
+
export async function evaluateRuleModel(context, contract, argTypes) {
|
|
343
|
+
const domains = {};
|
|
344
|
+
const files = [];
|
|
345
|
+
const predicates = {};
|
|
346
|
+
for (const [type, reference] of Object.entries(contract.domains ?? {})) {
|
|
347
|
+
if ("source" in reference) {
|
|
348
|
+
// A source domain is the members Kibi reads from the project itself.
|
|
349
|
+
const read = await readSource(context.workspaceRoot, reference.source, evaluateSourceCode);
|
|
350
|
+
if (!read.ok)
|
|
351
|
+
return { ok: false, reason: `Kibi could not read the domain ${type} from ${describeSource(reference.source)}: ${read.reason}` };
|
|
352
|
+
domains[type] = { values: read.model.members };
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const resolved = await resolveReference(context, reference, `domains.${type}`);
|
|
356
|
+
if (typeof resolved === "string")
|
|
357
|
+
return { ok: false, reason: resolved };
|
|
358
|
+
domains[type] = resolved;
|
|
359
|
+
files.push(resolved);
|
|
360
|
+
}
|
|
361
|
+
for (const [name, reference] of Object.entries(contract.predicate_source ?? {})) {
|
|
362
|
+
const resolved = await resolveReference(context, reference, `predicate_source.${name}`);
|
|
363
|
+
if (typeof resolved === "string")
|
|
364
|
+
return { ok: false, reason: resolved };
|
|
365
|
+
const types = argTypes[name];
|
|
366
|
+
if (types === undefined)
|
|
367
|
+
return { ok: false, reason: `the certificate has no table for the predicate ${name}` };
|
|
368
|
+
predicates[name] = { ...resolved, arg_types: types };
|
|
369
|
+
}
|
|
370
|
+
return evaluateCodeModel(context.workspaceRoot, [...files, ...Object.values(predicates)], { mode: "table", domains, predicates, limit: MAX_TABLE_ROWS });
|
|
371
|
+
}
|
|
372
|
+
/** Read a state machine's states, transitions and initial states. */
|
|
373
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
374
|
+
export async function evaluateMachine(context, contract) {
|
|
375
|
+
if (contract.domain_source === undefined)
|
|
376
|
+
return { ok: false, reason: "the contract names no domain_source transition table" };
|
|
377
|
+
const transitions = await resolveReference(context, contract.domain_source, "domain_source");
|
|
378
|
+
if (typeof transitions === "string")
|
|
379
|
+
return { ok: false, reason: transitions };
|
|
380
|
+
const files = [transitions];
|
|
381
|
+
const plan = { mode: "machine", transitions };
|
|
382
|
+
if (contract.initial_source !== undefined) {
|
|
383
|
+
const initial = await resolveReference(context, contract.initial_source, "initial_source");
|
|
384
|
+
if (typeof initial === "string")
|
|
385
|
+
return { ok: false, reason: initial };
|
|
386
|
+
plan.initial = initial;
|
|
387
|
+
files.push(initial);
|
|
388
|
+
}
|
|
389
|
+
if (contract.domains !== undefined) {
|
|
390
|
+
const domains = {};
|
|
391
|
+
for (const [type, reference] of Object.entries(contract.domains)) {
|
|
392
|
+
if ("source" in reference)
|
|
393
|
+
return { ok: false, reason: `domains.${type} names a source; a state machine's state and event domains are code exports` };
|
|
394
|
+
const resolved = await resolveReference(context, reference, `domains.${type}`);
|
|
395
|
+
if (typeof resolved === "string")
|
|
396
|
+
return { ok: false, reason: resolved };
|
|
397
|
+
domains[type] = resolved;
|
|
398
|
+
files.push(resolved);
|
|
399
|
+
}
|
|
400
|
+
plan.domains = domains;
|
|
401
|
+
}
|
|
402
|
+
return evaluateCodeModel(context.workspaceRoot, files, plan);
|
|
403
|
+
}
|
|
404
|
+
/** Read the pattern or string a regex contract's pattern_source exports. */
|
|
405
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
406
|
+
export async function evaluatePattern(context, contract) {
|
|
407
|
+
if (contract.pattern_source === undefined)
|
|
408
|
+
return { ok: false, reason: "the contract names no pattern_source" };
|
|
409
|
+
const pattern = await resolveReference(context, contract.pattern_source, "pattern_source");
|
|
410
|
+
if (typeof pattern === "string")
|
|
411
|
+
return { ok: false, reason: pattern };
|
|
412
|
+
return evaluateCodeModel(context.workspaceRoot, [pattern], {
|
|
413
|
+
mode: "pattern",
|
|
414
|
+
pattern,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
function record(value) {
|
|
418
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
419
|
+
? value
|
|
420
|
+
: null;
|
|
421
|
+
}
|
|
422
|
+
function text(value) {
|
|
423
|
+
return typeof value === "string" ? value : "";
|
|
424
|
+
}
|
|
425
|
+
function strings(value) {
|
|
426
|
+
return Array.isArray(value) ? value.map((item) => String(item)) : [];
|
|
427
|
+
}
|
|
428
|
+
function sameSorted(left, right) {
|
|
429
|
+
const a = [...left].sort();
|
|
430
|
+
const b = [...right].sort();
|
|
431
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
432
|
+
}
|
|
433
|
+
function sameKeys(certificate, contract, field) {
|
|
434
|
+
const certified = Object.keys(certificate ?? {}).sort();
|
|
435
|
+
const declared = Object.keys(contract ?? {}).sort();
|
|
436
|
+
if (!sameSorted(certified, declared))
|
|
437
|
+
return `the certificate's ${field} (${certified.join(", ") || "none"}) are not the contract's (${declared.join(", ") || "none"})`;
|
|
438
|
+
for (const key of declared) {
|
|
439
|
+
const named = text(record(certificate?.[key])?.symbol_id);
|
|
440
|
+
const entry = contract?.[key];
|
|
441
|
+
const expected = entry !== undefined && "symbol_id" in entry ? entry.symbol_id : "";
|
|
442
|
+
if (named !== expected)
|
|
443
|
+
return `the certificate's ${field}.${key} is ${named || "(none)"}, not the contract's ${expected}`;
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const rowKey = (args) => JSON.stringify(args);
|
|
448
|
+
/**
|
|
449
|
+
* A rule certificate re-checks only when its domains and every predicate
|
|
450
|
+
* table are what Kibi reads from the code: the same values, and the same
|
|
451
|
+
* decision for every combination of arguments.
|
|
452
|
+
*/
|
|
453
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
454
|
+
export async function ruleCertificateProblem(context, contract, certificate) {
|
|
455
|
+
const domains = record(certificate.domains);
|
|
456
|
+
const predicates = record(certificate.predicates);
|
|
457
|
+
const keys = sameKeys(domains, contract.domains, "domains") ??
|
|
458
|
+
sameKeys(predicates, contract.predicate_source, "predicates");
|
|
459
|
+
if (keys !== null)
|
|
460
|
+
return keys;
|
|
461
|
+
const argTypes = {};
|
|
462
|
+
for (const [name, entry] of Object.entries(predicates ?? {}))
|
|
463
|
+
argTypes[name] = strings(record(entry)?.arg_types);
|
|
464
|
+
const evaluated = await evaluateRuleModel(context, contract, argTypes);
|
|
465
|
+
if (!evaluated.ok)
|
|
466
|
+
return evaluated.reason;
|
|
467
|
+
for (const [type, members] of Object.entries(evaluated.model.domains)) {
|
|
468
|
+
const certified = strings(record(domains?.[type])?.members);
|
|
469
|
+
if (!sameSorted(certified, members))
|
|
470
|
+
return `the certificate's domain ${type} is not the values Kibi read from the code (${members.slice(0, 8).join(", ")})`;
|
|
471
|
+
}
|
|
472
|
+
for (const [name, rows] of Object.entries(evaluated.model.predicates)) {
|
|
473
|
+
const failed = rows.find((row) => row.error !== undefined);
|
|
474
|
+
if (failed !== undefined)
|
|
475
|
+
return `${name}(${failed.args.join(", ")}) failed when Kibi evaluated it: ${failed.error}`;
|
|
476
|
+
const certified = new Map();
|
|
477
|
+
for (const row of Array.isArray(record(predicates?.[name])?.rows)
|
|
478
|
+
? record(predicates?.[name])?.rows
|
|
479
|
+
: []) {
|
|
480
|
+
const entry = record(row);
|
|
481
|
+
certified.set(rowKey(strings(entry?.args)), entry?.value);
|
|
482
|
+
}
|
|
483
|
+
if (certified.size !== rows.length)
|
|
484
|
+
return `the certificate's table for ${name} has ${certified.size} rows, but the code's domains give ${rows.length}`;
|
|
485
|
+
for (const row of rows) {
|
|
486
|
+
const value = certified.get(rowKey(row.args));
|
|
487
|
+
if (value !== row.value)
|
|
488
|
+
return `the certificate says ${name}(${row.args.join(", ")}) is ${String(value)}, but the code decides ${String(row.value)}`;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
/** A state_machine certificate re-checks only when it is the code's machine. */
|
|
494
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
495
|
+
export async function machineCertificateProblem(context, contract, certificate) {
|
|
496
|
+
const machine = record(certificate.machine);
|
|
497
|
+
const expected = contract.domain_source?.symbol_id ?? "";
|
|
498
|
+
if (text(machine?.symbol_id) !== expected)
|
|
499
|
+
return `the certificate's machine is ${text(machine?.symbol_id) || "(none)"}, not the contract's domain_source ${expected}`;
|
|
500
|
+
const evaluated = await evaluateMachine(context, contract);
|
|
501
|
+
if (!evaluated.ok)
|
|
502
|
+
return evaluated.reason;
|
|
503
|
+
const { model } = evaluated;
|
|
504
|
+
if (!sameSorted(strings(machine?.states), model.states))
|
|
505
|
+
return `the certificate's states are not the machine Kibi read from the code (${model.states.join(", ")})`;
|
|
506
|
+
const edges = (rows) => (Array.isArray(rows) ? rows : []).map((row) => strings(row).join("->"));
|
|
507
|
+
if (!sameSorted(edges(machine?.transitions), edges(model.transitions)))
|
|
508
|
+
return `the certificate's transitions are not the machine Kibi read from the code (${edges(model.transitions).slice(0, 8).join(", ")})`;
|
|
509
|
+
if (!sameSorted(strings(machine?.initial), [...(model.initial ?? [])]))
|
|
510
|
+
return `the certificate's initial states are not the ones Kibi read from the code (${(model.initial ?? []).join(", ") || "none"})`;
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
/** A regex certificate re-checks only when it holds the code's own pattern. */
|
|
514
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
515
|
+
export async function patternCertificateProblem(context, contract, certificate) {
|
|
516
|
+
const pattern = record(certificate.pattern);
|
|
517
|
+
const expected = contract.pattern_source?.symbol_id ?? "";
|
|
518
|
+
if (text(pattern?.symbol_id) !== expected)
|
|
519
|
+
return `the certificate's pattern is ${text(pattern?.symbol_id) || "(none)"}, not the contract's pattern_source ${expected}`;
|
|
520
|
+
const evaluated = await evaluatePattern(context, contract);
|
|
521
|
+
if (!evaluated.ok)
|
|
522
|
+
return evaluated.reason;
|
|
523
|
+
const model = evaluated.model;
|
|
524
|
+
const same = model.type === "string"
|
|
525
|
+
? text(pattern?.type) === "string" && pattern?.value === model.value
|
|
526
|
+
: text(pattern?.type) === "regexp" &&
|
|
527
|
+
pattern?.source === model.source &&
|
|
528
|
+
pattern?.flags === model.flags &&
|
|
529
|
+
pattern?.flavor === model.flavor;
|
|
530
|
+
if (!same)
|
|
531
|
+
return model.type === "string"
|
|
532
|
+
? `the certificate's value is not the string Kibi read from the code (${JSON.stringify(model.value)})`
|
|
533
|
+
: `the certificate's pattern is not the one Kibi read from the code (/${model.source}/${model.flags})`;
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
/** The fact's pattern: a JavaScript pattern with the u flag, matched whole. */
|
|
537
|
+
export function factPattern(source) {
|
|
538
|
+
return { type: "pattern", source, flags: "u", flavor: "js", mode: "full" };
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Whether the code's string or pattern meets a string fact's constraint,
|
|
542
|
+
* decided exactly. For a string constant: eq and neq compare it, matches
|
|
543
|
+
* tests it against the fact's pattern. For a pattern (the strings a
|
|
544
|
+
* validator accepts): eq means it accepts exactly that string, neq that it
|
|
545
|
+
* rejects it, matches that it accepts exactly the strings the fact's pattern
|
|
546
|
+
* describes. Polarity forbid negates the constraint; for a pattern, a
|
|
547
|
+
* forbidden match means no string matches both.
|
|
548
|
+
*/
|
|
549
|
+
// implements REQ-kibi-non-numeric-proof-kinds
|
|
550
|
+
export function stringConstraintProblem(certificate, obligation, factId) {
|
|
551
|
+
const pattern = record(certificate.pattern);
|
|
552
|
+
const operator = text(obligation.operator);
|
|
553
|
+
const value = text(obligation.value);
|
|
554
|
+
const forbid = text(obligation.polarity) === "forbid";
|
|
555
|
+
try {
|
|
556
|
+
if (text(pattern?.type) === "string") {
|
|
557
|
+
const actual = text(pattern?.value);
|
|
558
|
+
const holds = operator === "eq"
|
|
559
|
+
? actual === value
|
|
560
|
+
: operator === "neq"
|
|
561
|
+
? actual !== value
|
|
562
|
+
: languageAccepts(factPattern(value), actual);
|
|
563
|
+
if (holds !== forbid)
|
|
564
|
+
return null;
|
|
565
|
+
return `the code's string ${JSON.stringify(actual)} ${forbid ? "meets" : "does not meet"} ${factId}'s constraint ${operator} ${JSON.stringify(value)}${forbid ? ", which the fact forbids" : ""}`;
|
|
566
|
+
}
|
|
567
|
+
const code = {
|
|
568
|
+
type: "pattern",
|
|
569
|
+
source: text(pattern?.source),
|
|
570
|
+
flags: text(pattern?.flags),
|
|
571
|
+
flavor: text(pattern?.flavor) === "python" ? "python" : "js",
|
|
572
|
+
mode: "search",
|
|
573
|
+
};
|
|
574
|
+
const describe = `/${text(pattern?.source)}/${text(pattern?.flags)}`;
|
|
575
|
+
if (operator === "eq" || operator === "neq") {
|
|
576
|
+
const mustAccept = (operator === "eq") !== forbid;
|
|
577
|
+
if (operator === "eq" && !forbid) {
|
|
578
|
+
const decision = languagesEqual(code, { type: "literal", value });
|
|
579
|
+
return decision.holds
|
|
580
|
+
? null
|
|
581
|
+
: `the code's pattern ${describe} does not accept exactly ${JSON.stringify(value)}: ${decision.reason}`;
|
|
582
|
+
}
|
|
583
|
+
const accepts = languageAccepts(code, value);
|
|
584
|
+
if (accepts === mustAccept)
|
|
585
|
+
return null;
|
|
586
|
+
return `the code's pattern ${describe} ${accepts ? "accepts" : "rejects"} ${JSON.stringify(value)}, but ${factId} requires it to ${mustAccept ? "accept" : "reject"} it`;
|
|
587
|
+
}
|
|
588
|
+
const fact = factPattern(value);
|
|
589
|
+
const decision = forbid ? languagesDisjoint(code, fact) : languagesEqual(code, fact);
|
|
590
|
+
if (decision.holds)
|
|
591
|
+
return null;
|
|
592
|
+
return forbid
|
|
593
|
+
? `the code's pattern ${describe} accepts ${JSON.stringify(decision.witness)}, which ${factId} forbids (it matches /${value}/)`
|
|
594
|
+
: `the code's pattern ${describe} does not accept exactly the strings matching /${value}/: ${decision.reason.replace("the first language", "the code's pattern").replace("the second language", "the fact's pattern")}`;
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
if (error instanceof UnsupportedPattern)
|
|
598
|
+
return `Kibi cannot decide the pattern exactly: ${error.message}`;
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How Kibi runs a small evaluator script against the workspace's own code:
|
|
3
|
+
* the TypeScript and JavaScript runners (bun, the workspace's tsx, node with
|
|
4
|
+
* type stripping) and the Python interpreters a project uses. The proof kinds
|
|
5
|
+
* that read a model from the code (exhaustive domains, rule tables, state
|
|
6
|
+
* machines, patterns, data schemas, route tables) all load code this way.
|
|
7
|
+
*/
|
|
8
|
+
export declare const TS_RESOLVE_HOOKS = "\nconst SUFFIXES = [\".ts\", \".tsx\", \".mts\", \".cts\", \".js\", \".mjs\", \"/index.ts\", \"/index.tsx\", \"/index.js\"];\nexport async function resolve(specifier, context, next) {\n try {\n return await next(specifier, context);\n } catch (error) {\n if (error?.code !== \"ERR_MODULE_NOT_FOUND\" || !/^\\.\\.?\\//.test(specifier)) throw error;\n const base = specifier.replace(/\\.[cm]?js$/, \"\");\n for (const suffix of SUFFIXES) {\n try {\n return await next(base + suffix, context);\n } catch {}\n }\n throw error;\n }\n}\n";
|
|
9
|
+
export declare function workspaceBin(workspaceRoot: string, name: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* The Python interpreters a domain loads with, most specific first: the
|
|
12
|
+
* KIBI_PYTHON override, the active virtualenv, a project virtualenv (`.venv`
|
|
13
|
+
* or `venv`) between the domain file and the workspace root, then python3
|
|
14
|
+
* and python on PATH. The system interpreter usually cannot import the
|
|
15
|
+
* project's dependencies, so a project environment comes before it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function pythonDomainInterpreters(workspaceRoot: string, sourceFile: string, env?: NodeJS.ProcessEnv): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Run an evaluator script against the workspace code and return the JSON it
|
|
20
|
+
* prints last. A TypeScript or JavaScript file runs with bun when it is on
|
|
21
|
+
* PATH, else the workspace's tsx, else node's type stripping (which retries
|
|
22
|
+
* extensionless relative imports as TypeScript files); a Python file (`.py`)
|
|
23
|
+
* runs with the first interpreter pythonDomainInterpreters finds. A runner
|
|
24
|
+
* whose output `accept` rejects (or that fails to start) yields to the next.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runCodeEvaluator(workspaceRoot: string, sourceFile: string, scripts: Readonly<{
|
|
27
|
+
js: string;
|
|
28
|
+
python: string;
|
|
29
|
+
}>, args: readonly string[], accept?: (parsed: unknown) => boolean, options?: Readonly<{
|
|
30
|
+
runners?: readonly ("bun" | "tsx" | "node")[];
|
|
31
|
+
}>): {
|
|
32
|
+
ok: true;
|
|
33
|
+
value: unknown;
|
|
34
|
+
python: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
failure: string;
|
|
38
|
+
python: boolean;
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=code-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-runner.d.ts","sourceRoot":"","sources":["../../../src/operations/proof/code-runner.ts"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AAKH,eAAO,MAAM,gBAAgB,8jBAgB5B,CAAC;AAEF,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ/E;AAED;;;;;;GAMG;AAEH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,EAAE,CAwBV;AAaD;;;;;;;GAOG;AAEH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,QAAQ,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EACjD,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,GAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAoB,EACjD,OAAO,GAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,CAAC,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC;CAC/C,CAAM,GAEL;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC7C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAkDlD"}
|