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.
Files changed (164) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +5 -1
  3. package/dist/commands/check-generated.d.ts +5 -1
  4. package/dist/commands/check-generated.d.ts.map +1 -1
  5. package/dist/commands/check-generated.js +24 -35
  6. package/dist/commands/init-helpers.d.ts.map +1 -1
  7. package/dist/commands/init-helpers.js +8 -5
  8. package/dist/commands/migrate.d.ts.map +1 -1
  9. package/dist/commands/migrate.js +23 -5
  10. package/dist/commands/sync/manifest.d.ts +10 -1
  11. package/dist/commands/sync/manifest.d.ts.map +1 -1
  12. package/dist/commands/sync/manifest.js +87 -14
  13. package/dist/commands/sync.d.ts.map +1 -1
  14. package/dist/commands/sync.js +19 -0
  15. package/dist/extractors/markdown.d.ts +1 -1
  16. package/dist/extractors/markdown.d.ts.map +1 -1
  17. package/dist/extractors/symbol-coordinates.js +2 -2
  18. package/dist/extractors/symbols-coordinator.d.ts +2 -4
  19. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  20. package/dist/extractors/symbols-coordinator.js +1 -2
  21. package/dist/operations/migration/proof-contract-claims.d.ts +23 -10
  22. package/dist/operations/migration/proof-contract-claims.d.ts.map +1 -1
  23. package/dist/operations/migration/proof-contract-claims.js +35 -2
  24. package/dist/operations/migration/schema6.d.ts.map +1 -1
  25. package/dist/operations/migration/schema6.js +2 -4
  26. package/dist/operations/migration/symbol-coordinates-refresh.d.ts +3 -2
  27. package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -1
  28. package/dist/operations/migration/symbol-coordinates-refresh.js +4 -3
  29. package/dist/operations/migration/untrack-symbol-coordinates.d.ts +23 -0
  30. package/dist/operations/migration/untrack-symbol-coordinates.d.ts.map +1 -0
  31. package/dist/operations/migration/untrack-symbol-coordinates.js +67 -0
  32. package/dist/operations/modeling/requirement-modeler.d.ts.map +1 -1
  33. package/dist/operations/modeling/requirement-modeler.js +48 -1
  34. package/dist/operations/modeling/requirement-types.d.ts +2 -1
  35. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  36. package/dist/operations/modeling/requirement-utils.d.ts +1 -1
  37. package/dist/operations/modeling/requirement-utils.d.ts.map +1 -1
  38. package/dist/operations/modeling/requirement-utils.js +8 -1
  39. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  40. package/dist/operations/mutation/source-authoring.js +39 -20
  41. package/dist/operations/proof/claim-bindings.d.ts +18 -0
  42. package/dist/operations/proof/claim-bindings.d.ts.map +1 -1
  43. package/dist/operations/proof/claim-bindings.js +157 -4
  44. package/dist/operations/proof/code-certificates.d.ts +34 -14
  45. package/dist/operations/proof/code-certificates.d.ts.map +1 -1
  46. package/dist/operations/proof/code-certificates.js +266 -157
  47. package/dist/operations/proof/code-models.d.ts +96 -0
  48. package/dist/operations/proof/code-models.d.ts.map +1 -0
  49. package/dist/operations/proof/code-models.js +601 -0
  50. package/dist/operations/proof/code-runner.d.ts +40 -0
  51. package/dist/operations/proof/code-runner.d.ts.map +1 -0
  52. package/dist/operations/proof/code-runner.js +164 -0
  53. package/dist/operations/proof/code-scope.d.ts +3 -0
  54. package/dist/operations/proof/code-scope.d.ts.map +1 -1
  55. package/dist/operations/proof/code-scope.js +26 -2
  56. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  57. package/dist/operations/proof/ingest-proof.js +2 -1
  58. package/dist/operations/proof/regular-language.d.ts +89 -0
  59. package/dist/operations/proof/regular-language.d.ts.map +1 -0
  60. package/dist/operations/proof/regular-language.js +943 -0
  61. package/dist/operations/proof/source-code-readers.d.ts +12 -0
  62. package/dist/operations/proof/source-code-readers.d.ts.map +1 -0
  63. package/dist/operations/proof/source-code-readers.js +699 -0
  64. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  65. package/dist/operations/semantic-advisor/analyze-prose.js +2 -1
  66. package/dist/operations/semantic-advisor/strict-count-rules.d.ts.map +1 -1
  67. package/dist/operations/semantic-advisor/strict-count-rules.js +8 -2
  68. package/dist/operations/semantic-advisor/strict-rules.d.ts +8 -0
  69. package/dist/operations/semantic-advisor/strict-rules.d.ts.map +1 -1
  70. package/dist/operations/semantic-advisor/strict-rules.js +231 -1
  71. package/dist/operations/semantic-advisor/types.d.ts +1 -1
  72. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  73. package/dist/prolog.d.ts.map +1 -1
  74. package/dist/prolog.js +13 -3
  75. package/dist/public/operations/catalog.d.ts +156 -13
  76. package/dist/public/operations/catalog.d.ts.map +1 -1
  77. package/dist/public/operations/evidence-floor.js +1 -1
  78. package/dist/public/operations/proof-contract-claims.js +1 -1
  79. package/dist/public/operations/specs/composite.d.ts +16 -3
  80. package/dist/public/operations/specs/composite.d.ts.map +1 -1
  81. package/dist/public/operations/specs/modeling.d.ts +16 -3
  82. package/dist/public/operations/specs/modeling.d.ts.map +1 -1
  83. package/dist/public/operations/specs/modeling.js +22 -3
  84. package/dist/public/operations/specs/mutation-schemas.d.ts +140 -10
  85. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  86. package/dist/public/operations/specs/mutation-schemas.js +14 -3
  87. package/dist/public/operations/specs/upsert.d.ts +140 -10
  88. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  89. package/dist/public/proof-certificates.d.ts +75 -1
  90. package/dist/public/proof-certificates.d.ts.map +1 -1
  91. package/dist/public/proof-certificates.js +212 -0
  92. package/dist/public/proof-protocol.d.ts +143 -10
  93. package/dist/public/proof-protocol.d.ts.map +1 -1
  94. package/dist/public/proof-protocol.js +301 -16
  95. package/dist/public/proof-receipt.d.ts +4 -3
  96. package/dist/public/proof-receipt.d.ts.map +1 -1
  97. package/dist/public/proof-receipt.js +6 -1
  98. package/dist/public/proof-sources.d.ts +102 -0
  99. package/dist/public/proof-sources.d.ts.map +1 -0
  100. package/dist/public/proof-sources.js +1292 -0
  101. package/dist/public/schemas/entity.d.ts.map +1 -1
  102. package/dist/public/schemas/entity.js +21 -1
  103. package/dist/public/skills/kibi-commit-review/SKILL.md +8 -8
  104. package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +6 -6
  105. package/dist/public/skills/kibi-decisions/SKILL.md +5 -2
  106. package/dist/public/skills/kibi-decisions/resources/decision-records.md +4 -1
  107. package/dist/public/skills/kibi-freshness/SKILL.md +2 -2
  108. package/dist/public/skills/kibi-traceability/SKILL.md +3 -2
  109. package/dist/public/skills/kibi-usage/SKILL.md +5 -3
  110. package/dist/public/skills/kibi-usage/resources/proof.md +105 -9
  111. package/dist/report/html-report.css +45 -10
  112. package/dist/report/html-report.d.ts.map +1 -1
  113. package/dist/report/html-report.js +74 -17
  114. package/dist/report/report-view.d.ts +1 -1
  115. package/dist/report/report-view.d.ts.map +1 -1
  116. package/dist/report/report-view.js +1 -0
  117. package/dist/schemas/entity.schema.json +199 -8
  118. package/dist/traceability/evidence-model.d.ts +13 -12
  119. package/dist/traceability/evidence-model.d.ts.map +1 -1
  120. package/dist/traceability/evidence-model.js +1 -1
  121. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  122. package/dist/traceability/staged-diagnostics.js +24 -20
  123. package/dist/traceability/staged-impact-contract.d.ts +4 -4
  124. package/dist/traceability/staged-impact-contract.js +9 -9
  125. package/dist/traceability/staged-symbols-manifest.d.ts +11 -3
  126. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  127. package/dist/traceability/staged-symbols-manifest.js +47 -140
  128. package/dist/types/entities.d.ts +1 -1
  129. package/dist/types/entities.d.ts.map +1 -1
  130. package/dist/utils/rule-registry.generated.d.ts +1 -1
  131. package/dist/utils/rule-registry.generated.js +1 -1
  132. package/dist/utils/schema-version.d.ts +1 -1
  133. package/dist/utils/schema-version.d.ts.map +1 -1
  134. package/dist/utils/schema-version.js +3 -1
  135. package/dist/utils/strict-modeling.d.ts +3 -2
  136. package/dist/utils/strict-modeling.d.ts.map +1 -1
  137. package/dist/utils/strict-modeling.js +37 -4
  138. package/dist/workspace-cwd.d.ts +15 -0
  139. package/dist/workspace-cwd.d.ts.map +1 -0
  140. package/dist/workspace-cwd.js +86 -0
  141. package/package.json +2 -2
  142. package/schema/validation.pl +3 -1
  143. package/src/public/operations/evidence-floor.ts +1 -1
  144. package/src/public/operations/proof-contract-claims.ts +1 -1
  145. package/src/public/operations/specs/modeling.ts +24 -5
  146. package/src/public/operations/specs/mutation-schemas.ts +14 -3
  147. package/src/public/proof-certificates.ts +296 -1
  148. package/src/public/proof-protocol-types.d.ts +54 -2
  149. package/src/public/proof-protocol.ts +396 -20
  150. package/src/public/proof-receipt.ts +8 -2
  151. package/src/public/proof-sources.ts +1656 -0
  152. package/src/public/schemas/entity.ts +32 -2
  153. package/src/public/skills/kibi-commit-review/SKILL.md +8 -8
  154. package/src/public/skills/kibi-commit-review/resources/staged-gate.md +6 -6
  155. package/src/public/skills/kibi-decisions/SKILL.md +5 -2
  156. package/src/public/skills/kibi-decisions/resources/decision-records.md +4 -1
  157. package/src/public/skills/kibi-freshness/SKILL.md +2 -2
  158. package/src/public/skills/kibi-traceability/SKILL.md +3 -2
  159. package/src/public/skills/kibi-usage/SKILL.md +5 -3
  160. package/src/public/skills/kibi-usage/resources/proof.md +105 -9
  161. package/src/schemas/entity.schema.json +199 -8
  162. package/dist/traceability/reviewed-coordinate-refresh.d.ts +0 -10
  163. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +0 -1
  164. package/dist/traceability/reviewed-coordinate-refresh.js +0 -49
@@ -22,8 +22,12 @@ import { createRequire } from "node:module";
22
22
  import path from "node:path";
23
23
  import { loadEntities } from "../../public/operations/discovery-entities.js";
24
24
  import { runOperationJsonQuery, toPrologAtom, } from "../../public/operations/prolog-json.js";
25
- import { certificateDigest } from "../../public/proof-certificates.js";
25
+ import { canonicalCertificateJson, certificateDigest, } from "../../public/proof-certificates.js";
26
26
  import { CODE_CERTIFICATE_VERSION, isCertificateProofKind, } from "../../public/proof-protocol.js";
27
+ import { describeSource, normalizeSourceSpec, readSource, } from "../../public/proof-sources.js";
28
+ import { runCodeEvaluator, TS_RESOLVE_HOOKS, } from "./code-runner.js";
29
+ import { evaluateSourceCode } from "./source-code-readers.js";
30
+ import { machineCertificateProblem, patternCertificateProblem, ruleCertificateProblem, } from "./code-models.js";
27
31
  function record(value) {
28
32
  return value !== null && typeof value === "object" && !Array.isArray(value)
29
33
  ? value
@@ -198,26 +202,6 @@ function stripExtension(file) {
198
202
  function escapeRegExp(value) {
199
203
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
200
204
  }
201
- // Node's type stripping resolves only exact specifiers. TypeScript apps
202
- // import relative modules without an extension, or with `.js` for a `.ts`
203
- // file; this resolve hook retries those as the TypeScript files they name.
204
- const TS_RESOLVE_HOOKS = `
205
- const SUFFIXES = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", "/index.ts", "/index.tsx", "/index.js"];
206
- export async function resolve(specifier, context, next) {
207
- try {
208
- return await next(specifier, context);
209
- } catch (error) {
210
- if (error?.code !== "ERR_MODULE_NOT_FOUND" || !/^\\.\\.?\\//.test(specifier)) throw error;
211
- const base = specifier.replace(/\\.[cm]?js$/, "");
212
- for (const suffix of SUFFIXES) {
213
- try {
214
- return await next(base + suffix, context);
215
- } catch {}
216
- }
217
- throw error;
218
- }
219
- }
220
- `;
221
205
  const DOMAIN_EVALUATOR = `
222
206
  const [file, name] = process.argv.slice(-2);
223
207
  const { pathToFileURL } = await import("node:url");
@@ -274,63 +258,6 @@ elif items is None:
274
258
  else:
275
259
  print(json.dumps({"count": len(items), "members": [text(i) for i in items] if all(primitive(i) for i in items) else None}))
276
260
  `;
277
- function workspaceBin(workspaceRoot, name) {
278
- let dir = path.resolve(workspaceRoot);
279
- for (;;) {
280
- const candidate = path.join(dir, "node_modules", ".bin", name);
281
- if (existsSync(candidate))
282
- return candidate;
283
- if (path.dirname(dir) === dir)
284
- return null;
285
- dir = path.dirname(dir);
286
- }
287
- }
288
- /**
289
- * The Python interpreters a domain loads with, most specific first: the
290
- * KIBI_PYTHON override, the active virtualenv, a project virtualenv (`.venv`
291
- * or `venv`) between the domain file and the workspace root, then python3
292
- * and python on PATH. The system interpreter usually cannot import the
293
- * project's dependencies, so a project environment comes before it.
294
- */
295
- // implements REQ-kibi-code-proof-kinds
296
- export function pythonDomainInterpreters(workspaceRoot, sourceFile, env = process.env) {
297
- const interpreters = [];
298
- const add = (candidate) => {
299
- if (candidate && !interpreters.includes(candidate))
300
- interpreters.push(candidate);
301
- };
302
- add(env.KIBI_PYTHON?.trim() || undefined);
303
- const venvPython = (venv) => [
304
- path.join(venv, "bin", "python"),
305
- path.join(venv, "Scripts", "python.exe"),
306
- ].find((candidate) => existsSync(candidate));
307
- if (env.VIRTUAL_ENV)
308
- add(venvPython(env.VIRTUAL_ENV));
309
- const root = path.resolve(workspaceRoot);
310
- let dir = path.dirname(path.resolve(root, sourceFile));
311
- for (;;) {
312
- for (const name of [".venv", "venv"])
313
- add(venvPython(path.join(dir, name)));
314
- if (dir === root || path.dirname(dir) === dir)
315
- break;
316
- if (!dir.startsWith(`${root}${path.sep}`))
317
- break;
318
- dir = path.dirname(dir);
319
- }
320
- add("python3");
321
- add("python");
322
- return interpreters;
323
- }
324
- /** The line of a runner's stderr that names the failure. */
325
- function failureLine(stderr, python) {
326
- const lines = stderr
327
- .trim()
328
- .split("\n")
329
- .map((line) => line.trim())
330
- .filter((line) => line !== "");
331
- // A Python traceback names the exception on its last line.
332
- return (python ? lines.at(-1) : lines[0]) ?? "";
333
- }
334
261
  /**
335
262
  * The domain as the code defines it: Kibi imports the domain_source file
336
263
  * itself and reads the named export's members, so a certificate cannot cover
@@ -366,60 +293,26 @@ export function evaluateScalar(workspaceRoot, sourceFile, exportName) {
366
293
  }
367
294
  function evaluateExport(workspaceRoot, sourceFile, exportName, options = {}) {
368
295
  const absolute = path.resolve(workspaceRoot, sourceFile);
369
- const python = /\.py$/i.test(sourceFile);
370
- const tsx = python ? null : workspaceBin(workspaceRoot, "tsx");
371
- const runners = python
372
- ? pythonDomainInterpreters(workspaceRoot, sourceFile).map((interpreter) => [
373
- interpreter,
374
- ["-c", PYTHON_DOMAIN_EVALUATOR],
375
- ])
376
- : (options.runners ?? ["bun", "tsx", "node"]).flatMap((runner) => {
377
- if (runner === "bun")
378
- return [["bun", ["-e", DOMAIN_EVALUATOR]]];
379
- if (runner === "tsx")
380
- return tsx === null
381
- ? []
382
- : [[tsx, ["--eval", DOMAIN_EVALUATOR, "--"]]];
383
- return [
384
- [
385
- process.execPath,
386
- [
387
- "--experimental-strip-types",
388
- "--no-warnings",
389
- "--input-type=module",
390
- "-e",
391
- DOMAIN_EVALUATOR,
392
- ],
393
- ],
394
- ];
395
- });
396
- let failure = "";
397
- for (const [command, argv] of runners) {
398
- const run = spawnSync(command, [...argv, absolute, exportName], {
399
- cwd: workspaceRoot,
400
- encoding: "utf8",
401
- timeout: 60_000,
402
- });
403
- if (run.error)
404
- continue;
405
- const line = (run.stdout ?? "").trim().split("\n").pop() ?? "";
406
- try {
407
- const parsed = JSON.parse(line);
408
- if (parsed.error !== undefined)
409
- return { ok: false, reason: parsed.error };
410
- if (typeof parsed.scalar === "number")
411
- return { ok: true, count: 0, members: null, scalar: parsed.scalar };
412
- if (typeof parsed.count === "number")
413
- return { ok: true, count: parsed.count, members: parsed.members ?? null };
414
- }
415
- catch {
416
- // Fall through to the next runner.
417
- }
418
- failure = failureLine(run.stderr ?? "", python);
419
- }
296
+ const run = runCodeEvaluator(workspaceRoot, sourceFile, { js: DOMAIN_EVALUATOR, python: PYTHON_DOMAIN_EVALUATOR }, [absolute, exportName], (parsed) => {
297
+ const row = parsed;
298
+ return (row.error !== undefined ||
299
+ typeof row.scalar === "number" ||
300
+ typeof row.count === "number");
301
+ }, options);
302
+ if (!run.ok)
303
+ return {
304
+ ok: false,
305
+ reason: `Kibi could not load ${exportName} from ${sourceFile}${run.failure ? `: ${run.failure}` : ""}${run.python ? " (a Python domain loads with KIBI_PYTHON, the active or project virtualenv (.venv or venv), then python3 on PATH)" : " (without bun or the workspace's tsx, node's type stripping loads the domain; a type imported without import type stops it)"}`,
306
+ };
307
+ const parsed = run.value;
308
+ if (parsed.error !== undefined)
309
+ return { ok: false, reason: parsed.error };
310
+ if (typeof parsed.scalar === "number")
311
+ return { ok: true, count: 0, members: null, scalar: parsed.scalar };
420
312
  return {
421
- ok: false,
422
- reason: `Kibi could not load ${exportName} from ${sourceFile}${failure ? `: ${failure}` : ""}${python ? " (a Python domain loads with KIBI_PYTHON, the active or project virtualenv (.venv or venv), then python3 on PATH)" : " (without bun or the workspace's tsx, node's type stripping loads the domain; a type imported without import type stops it)"}`,
313
+ ok: true,
314
+ count: parsed.count,
315
+ members: parsed.members ?? null,
423
316
  };
424
317
  }
425
318
  /**
@@ -447,37 +340,135 @@ export function domainCoverageProblem(exportName, evaluated, certified) {
447
340
  : `the certificate covers ${certified.length} members but ${exportName} has ${evaluated.count}`;
448
341
  }
449
342
  /**
450
- * Kibi compiles a typecheck witness itself, with the workspace's TypeScript
451
- * and fixed strict options (a lax project tsconfig could make the never
452
- * check vacuous), and accepts it only with no errors in the witness.
343
+ * The TypeScript compiler API for a witness: the package nearest the witness
344
+ * first, then the workspace root. TypeScript 7 ships without the JS compiler
345
+ * API, so a 7.x install is skipped for an older one further up, and named in
346
+ * the reason when none is left.
453
347
  */
454
- // implements REQ-kibi-code-proof-kinds
455
- export function compileWitness(workspaceRoot, witnessPath) {
456
- let ts;
457
- try {
458
- ts = createRequire(path.join(workspaceRoot, "package.json"))("typescript");
459
- }
460
- catch {
461
- return {
462
- ok: false,
463
- reason: "Kibi could not load TypeScript from the workspace to compile the witness (install typescript)",
464
- };
348
+ function loadTypeScript(workspaceRoot, absoluteWitness) {
349
+ const noApi = [];
350
+ const bases = [
351
+ path.join(path.dirname(absoluteWitness), "noop.js"),
352
+ path.join(workspaceRoot, "package.json"),
353
+ ];
354
+ for (const base of bases) {
355
+ let loaded;
356
+ try {
357
+ loaded = createRequire(base)("typescript");
358
+ }
359
+ catch {
360
+ continue;
361
+ }
362
+ if (typeof loaded.createProgram === "function")
363
+ return { ok: true, ts: loaded };
364
+ noApi.push(String(loaded.version ?? "unknown"));
465
365
  }
366
+ return {
367
+ ok: false,
368
+ reason: noApi.length > 0
369
+ ? `the workspace's TypeScript ${noApi[0]} has no compiler API to compile the witness (TypeScript 7 ships without it); add typescript@6 as a dev dependency for typecheck proofs`
370
+ : "Kibi could not load TypeScript from the workspace to compile the witness (install typescript)",
371
+ };
372
+ }
373
+ /** Options that decide how the witness's imports resolve, from the nearest tsconfig. */
374
+ const RESOLUTION_OPTIONS = [
375
+ "module",
376
+ "moduleResolution",
377
+ "baseUrl",
378
+ "paths",
379
+ "rootDirs",
380
+ "customConditions",
381
+ "resolvePackageJsonExports",
382
+ "resolvePackageJsonImports",
383
+ "allowImportingTsExtensions",
384
+ "resolveJsonModule",
385
+ "jsx",
386
+ "jsxImportSource",
387
+ "lib",
388
+ "target",
389
+ "esModuleInterop",
390
+ "allowSyntheticDefaultImports",
391
+ "verbatimModuleSyntax",
392
+ ];
393
+ /**
394
+ * The nearest tsconfig's resolution options, and the declaration files it
395
+ * includes: global typings such as a `reset.d.ts` that the project's types
396
+ * rely on, which a witness importing those types needs too.
397
+ */
398
+ function projectCompileSettings(ts, workspaceRoot, absoluteWitness) {
399
+ const none = { options: {}, declarations: [] };
400
+ const configPath = ts.findConfigFile(path.dirname(absoluteWitness), ts.sys.fileExists, "tsconfig.json");
401
+ if (configPath === undefined ||
402
+ path.relative(workspaceRoot, configPath).startsWith(".."))
403
+ return none;
404
+ const read = ts.readConfigFile(configPath, ts.sys.readFile);
405
+ if (read.error !== undefined)
406
+ return none;
407
+ const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configPath), undefined, configPath);
408
+ const options = {};
409
+ for (const key of RESOLUTION_OPTIONS)
410
+ if (parsed.options[key] !== undefined)
411
+ options[key] = parsed.options[key];
412
+ return {
413
+ options: options,
414
+ declarations: parsed.fileNames.filter((file) => file.endsWith(".d.ts")),
415
+ };
416
+ }
417
+ /**
418
+ * Whether a witness can stop compiling when a domain member goes unhandled:
419
+ * it uses the never type (a switch ending in a never assignment) or a
420
+ * satisfies expression (a map over the domain). Read from the syntax tree,
421
+ * so the word never in a comment or a string does not count.
422
+ */
423
+ function hasExhaustivenessCheck(ts, witness) {
424
+ const visit = (node) => node.kind === ts.SyntaxKind.NeverKeyword ||
425
+ node.kind === ts.SyntaxKind.SatisfiesExpression ||
426
+ (ts.forEachChild(node, (child) => (visit(child) ? true : undefined)) ??
427
+ false);
428
+ return visit(witness);
429
+ }
430
+ /**
431
+ * Kibi compiles a typecheck witness itself, with the workspace's TypeScript.
432
+ * Imports resolve the way the project's own tsconfig says (module,
433
+ * moduleResolution, paths, ...) and its declaration files are in the
434
+ * program, but the checking options stay fixed and
435
+ * strict, so a lax project tsconfig cannot make the never check vacuous. It
436
+ * accepts the witness only with no errors in it.
437
+ */
438
+ // implements REQ-kibi-code-proof-kinds
439
+ export function compileWitness(workspaceRoot, witnessPath, options = {}) {
466
440
  const absolute = path.resolve(workspaceRoot, witnessPath);
467
- const program = ts.createProgram([absolute], {
441
+ const loaded = loadTypeScript(workspaceRoot, absolute);
442
+ if (!loaded.ok)
443
+ return loaded;
444
+ const { ts } = loaded;
445
+ const { options: project, declarations } = projectCompileSettings(ts, workspaceRoot, absolute);
446
+ // module and moduleResolution must agree, so take both or neither.
447
+ const moduleOptions = project.module !== undefined || project.moduleResolution !== undefined
448
+ ? {}
449
+ : {
450
+ module: ts.ModuleKind.NodeNext,
451
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
452
+ };
453
+ const program = ts.createProgram([absolute, ...declarations], {
454
+ target: ts.ScriptTarget.ES2022,
455
+ ...moduleOptions,
456
+ ...project,
468
457
  strict: true,
469
458
  noEmit: true,
470
459
  skipLibCheck: true,
471
460
  allowJs: true,
472
461
  checkJs: true,
473
- target: ts.ScriptTarget.ES2022,
474
- module: ts.ModuleKind.NodeNext,
475
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
476
462
  types: [],
477
463
  });
478
464
  const witness = program.getSourceFile(absolute);
479
465
  if (witness === undefined)
480
466
  return { ok: false, reason: `Kibi could not read the witness ${witnessPath}` };
467
+ if (options.requireExhaustiveness === true && !hasExhaustivenessCheck(ts, witness))
468
+ return {
469
+ ok: false,
470
+ reason: `the witness ${witnessPath} has no exhaustiveness check (a never type or a satisfies map), so a new domain member would still compile`,
471
+ };
481
472
  const errors = [
482
473
  ...program.getSyntacticDiagnostics(witness),
483
474
  ...program.getSemanticDiagnostics(witness),
@@ -486,6 +477,13 @@ export function compileWitness(workspaceRoot, witnessPath) {
486
477
  return {
487
478
  ok: false,
488
479
  reason: `TypeScript ${ts.version} rejects the witness ${witnessPath}: ${ts.flattenDiagnosticMessageText(errors[0]?.messageText ?? "", " ")}`,
480
+ // Zero-based witness lines with an error, so a generated witness can
481
+ // say which of its checks failed.
482
+ errorLines: [
483
+ ...new Set(errors
484
+ .filter((error) => error.file === witness && error.start !== undefined)
485
+ .map((error) => witness.getLineAndCharacterOfPosition(error.start ?? 0).line)),
486
+ ],
489
487
  };
490
488
  return { ok: true, version: ts.version };
491
489
  }
@@ -561,19 +559,41 @@ export function smtAssertion(property, valueType, row) {
561
559
  }
562
560
  /**
563
561
  * The values a contract's value_source exports hold, read from the code the
564
- * same way as a domain, or why one cannot be read.
562
+ * same way as a domain (a count measure is the collection's size), or why
563
+ * one cannot be read.
565
564
  */
566
565
  // implements REQ-kibi-claim-bound-proofs
567
566
  export async function valueSourceValues(context, contract) {
568
567
  const values = [];
569
568
  for (const source of contract.value_source ?? []) {
569
+ if ("source" in source) {
570
+ // A source count reads the members Kibi reads from the project.
571
+ const read = await readSource(context.workspaceRoot, source.source, evaluateSourceCode);
572
+ if (!read.ok)
573
+ return {
574
+ ok: false,
575
+ reason: `Kibi could not read the value_source ${describeSource(source.source)}: ${read.reason}`,
576
+ };
577
+ values.push({ symbol_id: describeSource(source.source), value: read.model.members.length });
578
+ continue;
579
+ }
570
580
  const symbol = await loadSymbol(context, source.symbol_id);
571
581
  if (!symbol || symbol.sourceFile === "")
572
582
  return {
573
583
  ok: false,
574
584
  reason: `the value_source symbol ${source.symbol_id} has no source file`,
575
585
  };
576
- const evaluated = evaluateScalar(context.workspaceRoot, symbol.sourceFile, source.export ?? symbol.title);
586
+ const exportName = source.export ?? symbol.title;
587
+ if (source.measure === "count") {
588
+ // A count claim reads the collection itself, not a number kept
589
+ // beside it for the proof.
590
+ const domain = evaluateDomain(context.workspaceRoot, symbol.sourceFile, exportName);
591
+ if (!domain.ok)
592
+ return domain;
593
+ values.push({ symbol_id: source.symbol_id, value: domain.count });
594
+ continue;
595
+ }
596
+ const evaluated = evaluateScalar(context.workspaceRoot, symbol.sourceFile, exportName);
577
597
  if (!evaluated.ok)
578
598
  return evaluated;
579
599
  values.push({ symbol_id: source.symbol_id, value: evaluated.value });
@@ -710,9 +730,13 @@ async function workspaceProblem(context, contract, certificate) {
710
730
  const sha = createHash("sha256").update(content).digest("hex");
711
731
  if (text(witness?.sha256) !== sha)
712
732
  return `the witness sha256 does not match ${witnessPath} in the workspace`;
713
- if (!/\bnever\b/.test(content))
714
- return `the witness ${witnessPath} has no never check, so a new domain member would still compile`;
715
- const compiled = compileWitness(context.workspaceRoot, witnessPath);
733
+ // With proves, Kibi decides each claim with its own witness (set
734
+ // equality, required fields), so the author's witness only has to
735
+ // compile. Without proves, the receipt's proved rests on this witness
736
+ // alone, so it must be able to fail when a domain member goes unhandled.
737
+ const compiled = compileWitness(context.workspaceRoot, witnessPath, {
738
+ requireExhaustiveness: (contract.proves ?? []).length === 0,
739
+ });
716
740
  if (!compiled.ok)
717
741
  return compiled.reason;
718
742
  }
@@ -728,22 +752,107 @@ async function workspaceProblem(context, contract, certificate) {
728
752
  if (!rerun.ok)
729
753
  return rerun.reason;
730
754
  }
755
+ if (kind === "rule")
756
+ return ruleCertificateProblem(context, contract, certificate);
757
+ if (kind === "state_machine")
758
+ return machineCertificateProblem(context, contract, certificate);
759
+ if (kind === "regex")
760
+ return patternCertificateProblem(context, contract, certificate);
731
761
  return null;
732
762
  }
763
+ /**
764
+ * The certificate of a source proof: the contract's source and what Kibi
765
+ * reads from it. A certificate the run left must name the same source, and
766
+ * a model it carries must be what Kibi reads; either way the stored model is
767
+ * Kibi's own read, so the proof never rests on members the test listed.
768
+ */
769
+ // implements REQ-kibi-source-proof-kinds
770
+ export async function completeSourceCertificate(context, testId, contract, left) {
771
+ const spec = contract.source;
772
+ if (spec === undefined)
773
+ return { ok: false, reason: "the source contract names no source" };
774
+ const row = record(left);
775
+ if (left !== undefined && left !== null && row === null)
776
+ return { ok: false, reason: "the run left a source certificate that is not a JSON object" };
777
+ if (row !== null) {
778
+ if (text(row.kind) !== "source")
779
+ return { ok: false, reason: `the certificate kind ${text(row.kind) || "(none)"} is not the contract kind source` };
780
+ if (text(row.test_id) !== testId)
781
+ return { ok: false, reason: `the certificate is for ${text(row.test_id)}` };
782
+ if (row.source !== undefined &&
783
+ canonicalCertificateJson(normalizeSourceSpec(row.source) ?? row.source) !==
784
+ canonicalCertificateJson(spec))
785
+ return { ok: false, reason: `the certificate reads ${JSON.stringify(row.source)}, not the contract's source` };
786
+ }
787
+ const read = await readSource(context.workspaceRoot, spec, evaluateSourceCode);
788
+ if (!read.ok)
789
+ return { ok: false, reason: `Kibi could not read ${describeSource(spec)}: ${read.reason}` };
790
+ const model = read.model;
791
+ if (row?.model !== undefined && canonicalCertificateJson(row.model) !== canonicalCertificateJson(model))
792
+ return {
793
+ ok: false,
794
+ reason: `the certificate's model is not what Kibi reads from ${describeSource(spec)} (${model.members.slice(0, 8).join(", ") || "no members"})`,
795
+ };
796
+ return {
797
+ ok: true,
798
+ certificate: {
799
+ version: CODE_CERTIFICATE_VERSION,
800
+ kind: "source",
801
+ test_id: testId,
802
+ source: spec,
803
+ model,
804
+ },
805
+ };
806
+ }
807
+ const WITNESS_NOT_ACCEPTED = "the type checker did not accept the witness";
808
+ /**
809
+ * A typecheck certificate whose own outcome is failed says only that: add
810
+ * what Kibi's compile of the same witness reports, so the author sees the
811
+ * first error, or learns that the test compiled it differently.
812
+ */
813
+ export function withWitnessDiagnosis(context, contract, reason) {
814
+ const witnessPath = contract.witness?.path ?? "";
815
+ if (reason !== WITNESS_NOT_ACCEPTED || witnessPath === "")
816
+ return reason;
817
+ const compiled = compileWitness(context.workspaceRoot, witnessPath);
818
+ return compiled.ok
819
+ ? `${reason}, though Kibi's own compile of ${witnessPath} with TypeScript ${compiled.version} accepts it (the test compiled it with other settings)`
820
+ : `${reason}: ${compiled.reason}`;
821
+ }
822
+ export { pythonDomainInterpreters, runCodeEvaluator, TS_RESOLVE_HOOKS, } from "./code-runner.js";
733
823
  /**
734
824
  * Re-check the certificate a certificate-kind run left for one test. A
735
825
  * runner kind (e2e, example, property) supports tested evidence and needs
736
826
  * no certificate; a certificate kind supports proved evidence only when the
737
827
  * certificate re-checks: its structure and arithmetic in code_certificates.pl,
738
828
  * and here that the proof read its domain from the code, that the witness is
739
- * the workspace file, and that the solver still answers unsat.
829
+ * the workspace file, that the solver still answers unsat, and that a rule
830
+ * table, state machine or pattern is what Kibi itself reads from the code.
740
831
  */
741
832
  // implements REQ-kibi-code-proof-kinds
742
833
  export async function checkCodeCertificate(context, input) {
743
834
  const kind = input.contract.kind ?? "e2e";
744
835
  if (!isCertificateProofKind(kind))
745
836
  return { kind, evidence_level: "tested" };
746
- const certificate = record(input.certificate);
837
+ let left = input.certificate;
838
+ if (kind === "source" && input.outcome === "passed") {
839
+ // Kibi reads the source itself, so the certificate it stores is its own
840
+ // read, whatever the run left (or nothing).
841
+ const completed = await completeSourceCertificate(context, input.testId, input.contract, input.certificate);
842
+ if (!completed.ok) {
843
+ const leftRecord = record(left);
844
+ return {
845
+ kind,
846
+ evidence_level: "tested",
847
+ ...(leftRecord
848
+ ? { certificate: leftRecord, certificate_digest: certificateDigest(leftRecord) }
849
+ : {}),
850
+ certificate_rejection: completed.reason,
851
+ };
852
+ }
853
+ left = completed.certificate;
854
+ }
855
+ const certificate = record(left);
747
856
  if (!certificate)
748
857
  return {
749
858
  kind,
@@ -768,7 +877,7 @@ export async function checkCodeCertificate(context, input) {
768
877
  return rejected("no Prolog runtime is attached to re-check the certificate");
769
878
  const checked = await runOperationJsonQuery(context.prolog, "code_certificates.pl", `code_certificates:code_certificate_check_json(${toPrologAtom(JSON.stringify(certificate))}, JsonString)`, "Code certificate check");
770
879
  if (checked.accepted !== true)
771
- return rejected(checked.reason ?? "the certificate did not re-check");
880
+ return rejected(withWitnessDiagnosis(context, input.contract, checked.reason ?? "the certificate did not re-check"));
772
881
  // Every later re-check hashes the stored certificate in Prolog, so a
773
882
  // certificate whose two canonical forms differ would silently lose proved.
774
883
  if (checked.digest !== digest)
@@ -0,0 +1,96 @@
1
+ import type { OperationContext } from "../../public/operations/runtime-types.js";
2
+ import type { ProofContract } from "../../public/proof-protocol.js";
3
+ import { type LanguageSpec } from "./regular-language.js";
4
+ /** The most predicate calls one rule certificate may need. */
5
+ export declare const MAX_TABLE_ROWS = 100000;
6
+ export type Resolved = {
7
+ file: string;
8
+ export: string;
9
+ };
10
+ export type TableRow = Readonly<{
11
+ args: readonly string[];
12
+ value?: boolean;
13
+ error?: string;
14
+ }>;
15
+ /** What Kibi read for a rule certificate: domains and predicate tables. */
16
+ export type RuleModel = Readonly<{
17
+ domains: Readonly<Record<string, readonly string[]>>;
18
+ predicates: Readonly<Record<string, readonly TableRow[]>>;
19
+ }>;
20
+ /** What Kibi read for a state_machine certificate. */
21
+ export type MachineModel = Readonly<{
22
+ states: readonly string[];
23
+ transitions: readonly (readonly [string, string])[];
24
+ initial?: readonly string[];
25
+ }>;
26
+ /** What Kibi read for a regex certificate. */
27
+ export type PatternModel = Readonly<{
28
+ type: "regexp";
29
+ source: string;
30
+ flags: string;
31
+ flavor: "js" | "python";
32
+ }> | Readonly<{
33
+ type: "string";
34
+ value: string;
35
+ }>;
36
+ /**
37
+ * Run one evaluation plan (table, machine or pattern mode) against the code
38
+ * files it names, in the language of those files.
39
+ */
40
+ export declare function evaluateCodeModel<T>(workspaceRoot: string, files: readonly Resolved[], plan: Record<string, unknown>): {
41
+ ok: true;
42
+ model: T;
43
+ } | {
44
+ ok: false;
45
+ reason: string;
46
+ };
47
+ /**
48
+ * Evaluate every predicate a rule contract names over every combination of
49
+ * its argument domains, with the argument types the certificate declares.
50
+ */
51
+ export declare function evaluateRuleModel(context: OperationContext, contract: ProofContract, argTypes: Readonly<Record<string, readonly string[]>>): Promise<{
52
+ ok: true;
53
+ model: RuleModel;
54
+ } | {
55
+ ok: false;
56
+ reason: string;
57
+ }>;
58
+ /** Read a state machine's states, transitions and initial states. */
59
+ export declare function evaluateMachine(context: OperationContext, contract: ProofContract): Promise<{
60
+ ok: true;
61
+ model: MachineModel;
62
+ } | {
63
+ ok: false;
64
+ reason: string;
65
+ }>;
66
+ /** Read the pattern or string a regex contract's pattern_source exports. */
67
+ export declare function evaluatePattern(context: OperationContext, contract: ProofContract): Promise<{
68
+ ok: true;
69
+ model: PatternModel;
70
+ } | {
71
+ ok: false;
72
+ reason: string;
73
+ }>;
74
+ /**
75
+ * A rule certificate re-checks only when its domains and every predicate
76
+ * table are what Kibi reads from the code: the same values, and the same
77
+ * decision for every combination of arguments.
78
+ */
79
+ export declare function ruleCertificateProblem(context: OperationContext, contract: ProofContract, certificate: Record<string, unknown>): Promise<string | null>;
80
+ /** A state_machine certificate re-checks only when it is the code's machine. */
81
+ export declare function machineCertificateProblem(context: OperationContext, contract: ProofContract, certificate: Record<string, unknown>): Promise<string | null>;
82
+ /** A regex certificate re-checks only when it holds the code's own pattern. */
83
+ export declare function patternCertificateProblem(context: OperationContext, contract: ProofContract, certificate: Record<string, unknown>): Promise<string | null>;
84
+ /** The fact's pattern: a JavaScript pattern with the u flag, matched whole. */
85
+ export declare function factPattern(source: string): LanguageSpec;
86
+ /**
87
+ * Whether the code's string or pattern meets a string fact's constraint,
88
+ * decided exactly. For a string constant: eq and neq compare it, matches
89
+ * tests it against the fact's pattern. For a pattern (the strings a
90
+ * validator accepts): eq means it accepts exactly that string, neq that it
91
+ * rejects it, matches that it accepts exactly the strings the fact's pattern
92
+ * describes. Polarity forbid negates the constraint; for a pattern, a
93
+ * forbidden match means no string matches both.
94
+ */
95
+ export declare function stringConstraintProblem(certificate: Record<string, unknown>, obligation: Readonly<Record<string, unknown>>, factId: string): string | null;
96
+ //# sourceMappingURL=code-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-models.d.ts","sourceRoot":"","sources":["../../../src/operations/proof/code-models.ts"],"names":[],"mappings":"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"}