@alisio/core 0.6.0 → 0.8.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 (97) hide show
  1. package/dist/application.d.ts +15 -0
  2. package/dist/application.js +38 -3
  3. package/dist/changelog/data.js +39 -0
  4. package/dist/commands/builtins.js +7 -0
  5. package/dist/config.d.ts +30 -0
  6. package/dist/config.js +32 -0
  7. package/dist/core/runner.d.ts +4 -0
  8. package/dist/core/runner.js +57 -1
  9. package/dist/eval/canonical.d.ts +14 -0
  10. package/dist/eval/canonical.js +40 -0
  11. package/dist/eval/cassette.d.ts +180 -0
  12. package/dist/eval/cassette.js +424 -0
  13. package/dist/eval/compare.d.ts +147 -0
  14. package/dist/eval/compare.js +436 -0
  15. package/dist/eval/engine.d.ts +145 -0
  16. package/dist/eval/engine.js +731 -0
  17. package/dist/eval/export.d.ts +40 -0
  18. package/dist/eval/export.js +90 -0
  19. package/dist/eval/fingerprint.d.ts +88 -0
  20. package/dist/eval/fingerprint.js +186 -0
  21. package/dist/eval/normalize.d.ts +68 -0
  22. package/dist/eval/normalize.js +138 -0
  23. package/dist/eval/paths.d.ts +11 -0
  24. package/dist/eval/paths.js +50 -0
  25. package/dist/eval/plan.d.ts +26 -0
  26. package/dist/eval/plan.js +44 -0
  27. package/dist/eval/report.d.ts +53 -0
  28. package/dist/eval/report.js +258 -0
  29. package/dist/eval/run-suite.d.ts +68 -0
  30. package/dist/eval/run-suite.js +94 -0
  31. package/dist/eval/secrets.d.ts +14 -0
  32. package/dist/eval/secrets.js +55 -0
  33. package/dist/eval/stats.d.ts +17 -0
  34. package/dist/eval/stats.js +50 -0
  35. package/dist/eval/suite.d.ts +20 -0
  36. package/dist/eval/suite.js +165 -0
  37. package/dist/eval/variants.d.ts +35 -0
  38. package/dist/eval/variants.js +137 -0
  39. package/dist/eval/verifiers.d.ts +49 -0
  40. package/dist/eval/verifiers.js +264 -0
  41. package/dist/forge/javascript/hosts/worker-host.d.ts +9 -0
  42. package/dist/forge/javascript/hosts/worker-host.js +16 -1
  43. package/dist/forge/kernel.js +3 -2
  44. package/dist/goal/command.d.ts +7 -2
  45. package/dist/goal/command.js +10 -2
  46. package/dist/goal/format.js +2 -0
  47. package/dist/goal/machine.d.ts +50 -3
  48. package/dist/goal/machine.js +80 -2
  49. package/dist/goal/service.d.ts +22 -2
  50. package/dist/goal/service.js +94 -7
  51. package/dist/goal/store.js +22 -3
  52. package/dist/index.d.ts +18 -2
  53. package/dist/index.js +18 -2
  54. package/dist/plan/criteria.d.ts +30 -0
  55. package/dist/plan/criteria.js +256 -0
  56. package/dist/plan/exit-plan.d.ts +8 -0
  57. package/dist/plan/exit-plan.js +91 -9
  58. package/dist/plan/manifest.d.ts +4 -2
  59. package/dist/plan/manifest.js +6 -1
  60. package/dist/plan/state.d.ts +13 -2
  61. package/dist/plan/state.js +7 -1
  62. package/dist/plugins/host.d.ts +7 -0
  63. package/dist/plugins/host.js +15 -0
  64. package/dist/proof/command.d.ts +24 -0
  65. package/dist/proof/command.js +109 -0
  66. package/dist/proof/coverage.d.ts +40 -0
  67. package/dist/proof/coverage.js +102 -0
  68. package/dist/proof/divergence.d.ts +24 -0
  69. package/dist/proof/divergence.js +120 -0
  70. package/dist/proof/export.d.ts +65 -0
  71. package/dist/proof/export.js +144 -0
  72. package/dist/proof/index.d.ts +12 -0
  73. package/dist/proof/index.js +12 -0
  74. package/dist/proof/ledger.d.ts +38 -0
  75. package/dist/proof/ledger.js +57 -0
  76. package/dist/proof/plugin-verifiers.d.ts +23 -0
  77. package/dist/proof/plugin-verifiers.js +39 -0
  78. package/dist/proof/recorder.d.ts +69 -0
  79. package/dist/proof/recorder.js +347 -0
  80. package/dist/proof/scope.d.ts +12 -0
  81. package/dist/proof/scope.js +121 -0
  82. package/dist/proof/store.d.ts +52 -0
  83. package/dist/proof/store.js +192 -0
  84. package/dist/proof/tree.d.ts +6 -0
  85. package/dist/proof/tree.js +78 -0
  86. package/dist/proof/types.d.ts +21 -0
  87. package/dist/proof/types.js +1 -0
  88. package/dist/proof/verifiers.d.ts +46 -0
  89. package/dist/proof/verifiers.js +176 -0
  90. package/dist/proof/verify.d.ts +36 -0
  91. package/dist/proof/verify.js +171 -0
  92. package/dist/runtime/ids.d.ts +21 -0
  93. package/dist/runtime/ids.js +41 -1
  94. package/dist/runtime/store.js +63 -2
  95. package/dist/tools/goal.d.ts +5 -1
  96. package/dist/tools/goal.js +35 -4
  97. package/package.json +2 -2
@@ -0,0 +1,120 @@
1
+ /** Only this much of the answer is read: the cost is bounded whatever the model writes. */
2
+ const MAX_TEXT = 100_000;
3
+ const MAX_CLAIM = 200;
4
+ const H = "[ \\t]+";
5
+ const SUBJECTS = {
6
+ test: `(?:(?:unit|integration|e2e)${H})?tests?(?:${H}suite)?(?:${H}(?:unitarios|unitarias|e2e|de${H}integración))?|(?:suite${H}de${H})?(?:tests|pruebas)(?:${H}(?:unitarias|de${H}integración))?`,
7
+ typecheck: `type${H}?check(?:ing|s)?|typecheck(?:ing)?|(?:verificación|chequeo|comprobación)${H}de${H}tipos|tsc`,
8
+ lint: "lint(?:ing|er)?",
9
+ };
10
+ const ARTICLE = `(?:(?:the|el|la|los|las)${H})?`;
11
+ const SUBJECT = `${ARTICLE}(?:${Object.values(SUBJECTS).join("|")})`;
12
+ const SEPARATOR = `[ \\t]*,[ \\t]*(?:(?:and|y)${H})?|${H}(?:and|y)${H}|[ \\t]*&[ \\t]*`;
13
+ const FILLER = `(?:is|are|now|also|all|both|still|were|have|has|been|ya|ahora|todos|todas|están|está|esta|siguen)`;
14
+ const VERDICT = `(?:passe[sd]|pass(?:ing)?|ok|okay|clean|green|succeed(?:s|ed)?|successful|fine|pasan|pasa|pasó|pasaron|pasando|limpi[oa]s?|verdes?|en${H}verde|correct[oa]s?|bien)`;
15
+ const CLAIM = new RegExp(`(?<![\\p{L}\\p{N}])(?:(?:all|todos|todas)${H})?(?:(?:the|los|las|el|la)${H})?` +
16
+ `((${SUBJECT}(?:(?:${SEPARATOR})${SUBJECT}){0,4})(?:${H}${FILLER})*${H}${VERDICT})` +
17
+ "(?![\\p{L}\\p{N}])", "giu");
18
+ const KIND_IN_LIST = Object.entries(SUBJECTS).map(([kind, source]) => [
19
+ kind,
20
+ new RegExp(`(?<![\\p{L}\\p{N}])(?:${source})(?![\\p{L}\\p{N}])`, "iu"),
21
+ ]);
22
+ const WORD = (alternatives) => new RegExp(`(?<![\\p{L}\\p{N}])(?:${alternatives})(?![\\p{L}\\p{N}])|\\p{L}+n['’]t`, "iu");
23
+ /** Words that make a following "tests pass" a wish, a condition, an instruction or a denial. */
24
+ const HEDGE = WORD([
25
+ "not|never|no|cannot|unable|if|once|until|unless|whether|when|whenever|should|shall|would|could|might|may",
26
+ "hope|hoping|expect|expecting|need|needs|want|ensure|ensuring|make|makes|making|get|gets|getting",
27
+ "verify|confirm|check|let's|lets",
28
+ "nunca|jamás|sin|si|cuando|hasta|salvo|debe|deben|debería|deberían|espero|esperamos|quiero",
29
+ "necesito|necesitamos|asegura|asegúrate|asegurar|verifica|verificar|comprueba|comprobar",
30
+ "confirma|confirmar|hacer|haz",
31
+ ].join("|"));
32
+ const CLAUSE_BOUNDARY = /[.!?;:\n]|(?<![\p{L}\p{N}])(?:but|however|pero|aunque|sin embargo)(?![\p{L}\p{N}])/giu;
33
+ const CLAUSE_WINDOW = 60;
34
+ /** Removes what quotes rather than asserts: code (fenced, inline), block quotes and quoted text. */
35
+ function unquoted(text) {
36
+ return text
37
+ .slice(0, MAX_TEXT)
38
+ .replace(/(```|~~~)[\s\S]*?\1/g, "\n")
39
+ .replace(/(```|~~~)[\s\S]*$/, "\n")
40
+ .replace(/^[ \t]*>.*$/gm, "")
41
+ .replace(/`[^`\n]{0,300}`/g, " ")
42
+ .replace(/"[^"\n]{0,300}"|“[^”\n]{0,300}”|«[^»\n]{0,300}»|‘[^’\n]{0,300}’/g, " ");
43
+ }
44
+ /** Whether the words just before `index`, inside its clause, hedge or deny the claim. */
45
+ function hedged(text, index) {
46
+ const before = text.slice(Math.max(0, index - 200), index);
47
+ let start = 0;
48
+ for (const boundary of before.matchAll(CLAUSE_BOUNDARY))
49
+ start = boundary.index + boundary[0].length;
50
+ return HEDGE.test(before.slice(start).slice(-CLAUSE_WINDOW));
51
+ }
52
+ function claimsIn(finalText) {
53
+ const text = unquoted(finalText);
54
+ const found = [];
55
+ const seen = new Set();
56
+ for (const match of text.matchAll(CLAIM)) {
57
+ if (hedged(text, match.index))
58
+ continue;
59
+ const wording = (match[1] ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_CLAIM);
60
+ for (const [kind, pattern] of KIND_IN_LIST) {
61
+ if (seen.has(kind) || !pattern.test(match[2] ?? ""))
62
+ continue;
63
+ seen.add(kind);
64
+ found.push({ claim: wording, kind });
65
+ }
66
+ }
67
+ return found;
68
+ }
69
+ const TYPECHECK_CMD = /(?<![\w-])(?:tsc|typecheck|type-check|check-types|mypy|pyright)(?![\w-])/i;
70
+ const LINT_CMD = /(?<![\w-])(?:lint|eslint|biome|ruff|flake8|pylint|clippy|golangci-lint|stylelint|oxlint)(?![\w-])/i;
71
+ const TEST_CMD = /(?<![\w-])(?:tests?|vitest|jest|pytest|mocha|rspec)(?![\w-])/i;
72
+ const TYPECHECK_TEXT = /(?<![\p{L}\p{N}])type[ -]?check|tipos(?![\p{L}\p{N}])/iu;
73
+ const LINT_TEXT = /(?<![\p{L}\p{N}])lint(?:ing|er)?(?![\p{L}\p{N}])/iu;
74
+ const TEST_TEXT = /(?<![\p{L}\p{N}])(?:tests?|pruebas)(?![\p{L}\p{N}])/iu;
75
+ /** The kind of check a criterion stands for: its command when it has one, else its text. */
76
+ function kindOf(criterion) {
77
+ const verifier = criterion.verifier;
78
+ if (verifier && (verifier.kind === "command" || verifier.kind === "coverage")) {
79
+ if (TYPECHECK_CMD.test(verifier.command))
80
+ return "typecheck";
81
+ if (LINT_CMD.test(verifier.command))
82
+ return "lint";
83
+ if (TEST_CMD.test(verifier.command))
84
+ return "test";
85
+ return undefined;
86
+ }
87
+ if (TYPECHECK_TEXT.test(criterion.text))
88
+ return "typecheck";
89
+ if (LINT_TEXT.test(criterion.text))
90
+ return "lint";
91
+ if (TEST_TEXT.test(criterion.text))
92
+ return "test";
93
+ return undefined;
94
+ }
95
+ /**
96
+ * The claims of `finalText` that `ledger` does not back, in order of appearance. An empty ledger
97
+ * yields nothing (D7: without criteria Proof stays out of the way).
98
+ */
99
+ export function detectDivergence(finalText, ledger) {
100
+ if (ledger.criteria.length === 0)
101
+ return [];
102
+ const out = [];
103
+ for (const { claim, kind } of claimsIn(finalText)) {
104
+ const equivalent = ledger.criteria.filter((c) => c.status !== "not_applicable" && kindOf(c) === kind);
105
+ if (equivalent.length === 0) {
106
+ out.push({ claim, kind, expected: "verified", actualState: "not_checked" });
107
+ continue;
108
+ }
109
+ for (const c of equivalent)
110
+ if (c.status !== "verified")
111
+ out.push({
112
+ claim,
113
+ kind,
114
+ expected: "verified",
115
+ actualState: c.status,
116
+ criterionId: c.criterionId,
117
+ });
118
+ }
119
+ return out;
120
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The surfaces of Proof (specs/011 phase 5) read ONE ledger and share these pure helpers: the
3
+ * versioned `proof/v1` export, the Markdown report, the CI exit code and the parsing of criteria
4
+ * a user or a CLI flag supplies. Nothing here touches a database or a clock.
5
+ */
6
+ import type { ArtifactPublisher, ArtifactRef, ProofCriterion, ProofEvidence, ProofLedger } from "@alisio/sdk";
7
+ import type { NewCriterion } from "./types.ts";
8
+ /** The `schema` of the JSON export. A breaking change to the shape is a new version. */
9
+ export declare const PROOF_EXPORT_SCHEMA = "proof/v1";
10
+ export interface ProofSummary {
11
+ total: number;
12
+ required: number;
13
+ /** Required criteria that are `failed`, `not_checked` or `manual_review`. */
14
+ requiredUnmet: number;
15
+ verified: number;
16
+ failed: number;
17
+ notChecked: number;
18
+ manualReview: number;
19
+ notApplicable: number;
20
+ }
21
+ /** The JSON export (`proof.json`): one object, fixed key order, versioned. */
22
+ export interface ProofExportV1 {
23
+ schema: typeof PROOF_EXPORT_SCHEMA;
24
+ sessionId: string;
25
+ generatedAt: string;
26
+ summary: ProofSummary;
27
+ criteria: ProofCriterion[];
28
+ evidence: ProofEvidence[];
29
+ divergences: ProofLedger["divergences"];
30
+ }
31
+ export declare function proofSummary(ledger: ProofLedger): ProofSummary;
32
+ export declare function proofExportV1(ledger: ProofLedger): ProofExportV1;
33
+ /** `proof.json`: pretty JSON ending in a newline. */
34
+ export declare const renderProofJson: (ledger: ProofLedger) => string;
35
+ /** `proof.md`: the same statuses as the JSON, readable. The output excerpts are not repeated. */
36
+ export declare function renderProofMarkdown(ledger: ProofLedger): string;
37
+ /** The plain-text view of the ledger (terminal and headless report): one line per criterion. */
38
+ export declare function renderProofText(ledger: ProofLedger): string;
39
+ export interface ProofExitCodes {
40
+ /** At least one required criterion is `failed`. */
41
+ failed: number;
42
+ /** No failure, but a required criterion is `not_checked` or `manual_review`. */
43
+ unverified: number;
44
+ }
45
+ export declare const DEFAULT_PROOF_EXIT_CODES: ProofExitCodes;
46
+ /** The CI exit code of a ledger (specs/011 3.7): failures win over unchecked criteria. */
47
+ export declare function proofExitCode(ledger: ProofLedger, codes?: ProofExitCodes): number;
48
+ /** Publishes `proof.md` and `proof.json` as artifacts of the calling session. */
49
+ export declare function publishProofExport(publisher: Pick<ArtifactPublisher, "publishText">, ledger: ProofLedger): Promise<ArtifactRef[]>;
50
+ export interface CriteriaInputError {
51
+ index: number;
52
+ reason: string;
53
+ }
54
+ /**
55
+ * Turns user-supplied criteria (HTTP body, `--proof-file`, `--criterion/--verify`) into
56
+ * `NewCriterion` values without a session. `verify` is shorthand for a `command` verifier and the
57
+ * default for `required` is true: a criterion the user wrote is one the user wants met.
58
+ */
59
+ export declare function parseCriteriaInput(raw: unknown, options: {
60
+ origin: "user" | "cli";
61
+ defaultRequired?: boolean;
62
+ }): {
63
+ criteria: Array<Omit<NewCriterion, "sessionId">>;
64
+ errors: CriteriaInputError[];
65
+ };
@@ -0,0 +1,144 @@
1
+ import { validateCriteriaDrafts } from "../plan/criteria.js";
2
+ /** The `schema` of the JSON export. A breaking change to the shape is a new version. */
3
+ export const PROOF_EXPORT_SCHEMA = "proof/v1";
4
+ const UNMET = new Set(["failed", "not_checked", "manual_review"]);
5
+ export function proofSummary(ledger) {
6
+ const count = (status) => ledger.criteria.filter((c) => c.status === status).length;
7
+ return {
8
+ total: ledger.criteria.length,
9
+ required: ledger.criteria.filter((c) => c.required).length,
10
+ requiredUnmet: ledger.criteria.filter((c) => c.required && UNMET.has(c.status)).length,
11
+ verified: count("verified"),
12
+ failed: count("failed"),
13
+ notChecked: count("not_checked"),
14
+ manualReview: count("manual_review"),
15
+ notApplicable: count("not_applicable"),
16
+ };
17
+ }
18
+ export function proofExportV1(ledger) {
19
+ return {
20
+ schema: PROOF_EXPORT_SCHEMA,
21
+ sessionId: ledger.sessionId,
22
+ generatedAt: ledger.generatedAt,
23
+ summary: proofSummary(ledger),
24
+ criteria: ledger.criteria,
25
+ evidence: ledger.evidence,
26
+ divergences: ledger.divergences,
27
+ };
28
+ }
29
+ /** `proof.json`: pretty JSON ending in a newline. */
30
+ export const renderProofJson = (ledger) => `${JSON.stringify(proofExportV1(ledger), null, 2)}\n`;
31
+ const cell = (text) => text.replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
32
+ /** `proof.md`: the same statuses as the JSON, readable. The output excerpts are not repeated. */
33
+ export function renderProofMarkdown(ledger) {
34
+ const s = proofSummary(ledger);
35
+ const byId = new Map(ledger.evidence.map((e) => [e.evidenceId, e]));
36
+ const lines = [
37
+ "# Proof",
38
+ "",
39
+ `Session \`${ledger.sessionId}\` · generated ${ledger.generatedAt} · schema \`${PROOF_EXPORT_SCHEMA}\``,
40
+ "",
41
+ `${s.total} criteria · ${s.verified} verified · ${s.failed} failed · ${s.notChecked} not checked · ` +
42
+ `${s.manualReview} manual review · ${s.notApplicable} not applicable · ` +
43
+ `${s.requiredUnmet} required not verified`,
44
+ "",
45
+ ];
46
+ if (!ledger.criteria.length)
47
+ lines.push("_No criteria._", "");
48
+ else {
49
+ lines.push("| Status | Required | Criterion | Evidence |", "| --- | --- | --- | --- |");
50
+ for (const c of ledger.criteria) {
51
+ const latest = [...c.evidenceIds]
52
+ .map((id) => byId.get(id))
53
+ .filter((e) => !!e)
54
+ .at(-1);
55
+ const status = c.stale ? `${c.status} (stale)` : c.status;
56
+ const evidence = latest
57
+ ? `${latest.callId} · exit ${latest.exitCode ?? "-"} · ${latest.capturedAt}`
58
+ : c.notApplicableReason
59
+ ? `n/a: ${c.notApplicableReason}`
60
+ : "-";
61
+ lines.push(`| ${status} | ${c.required ? "yes" : "no"} | ${cell(c.text)} | ${cell(evidence)} |`);
62
+ }
63
+ lines.push("");
64
+ }
65
+ if (ledger.divergences.length) {
66
+ lines.push("## Claims without evidence", "");
67
+ for (const d of ledger.divergences)
68
+ lines.push(`- "${cell(d.claim)}" (${d.actual})`);
69
+ lines.push("");
70
+ }
71
+ lines.push("_Proof records operational evidence; it does not prove that the code is correct._", "");
72
+ return lines.join("\n");
73
+ }
74
+ /** The plain-text view of the ledger (terminal and headless report): one line per criterion. */
75
+ export function renderProofText(ledger) {
76
+ const s = proofSummary(ledger);
77
+ const head = !s.total
78
+ ? "Proof: no criteria"
79
+ : `Proof: ${s.verified} of ${s.total} criteria verified` +
80
+ (s.required ? ` · ${s.requiredUnmet} of ${s.required} required not verified` : "");
81
+ const lines = [head];
82
+ const width = Math.max(0, ...ledger.criteria.map((c) => statusLabel(c).length));
83
+ for (const c of ledger.criteria)
84
+ lines.push(` ${statusLabel(c).padEnd(width)} ${c.text.replace(/\s+/g, " ").trim()}${c.required ? " (required)" : ""}`);
85
+ for (const d of ledger.divergences)
86
+ lines.push(` ! claim without evidence: "${d.claim.replace(/\s+/g, " ").trim()}" (${d.actual})`);
87
+ return lines.join("\n");
88
+ }
89
+ const statusLabel = (c) => (c.stale ? `${c.status}(stale)` : c.status);
90
+ export const DEFAULT_PROOF_EXIT_CODES = { failed: 10, unverified: 11 };
91
+ /** The CI exit code of a ledger (specs/011 3.7): failures win over unchecked criteria. */
92
+ export function proofExitCode(ledger, codes = DEFAULT_PROOF_EXIT_CODES) {
93
+ const required = ledger.criteria.filter((c) => c.required);
94
+ if (required.some((c) => c.status === "failed"))
95
+ return codes.failed;
96
+ if (required.some((c) => c.status === "not_checked" || c.status === "manual_review"))
97
+ return codes.unverified;
98
+ return 0;
99
+ }
100
+ /** Publishes `proof.md` and `proof.json` as artifacts of the calling session. */
101
+ export async function publishProofExport(publisher, ledger) {
102
+ const md = await publisher.publishText({
103
+ fileName: "proof.md",
104
+ title: "Proof",
105
+ text: renderProofMarkdown(ledger),
106
+ });
107
+ const json = await publisher.publishText({
108
+ fileName: "proof.json",
109
+ title: "Proof (JSON)",
110
+ text: renderProofJson(ledger),
111
+ });
112
+ return [md, json];
113
+ }
114
+ /**
115
+ * Turns user-supplied criteria (HTTP body, `--proof-file`, `--criterion/--verify`) into
116
+ * `NewCriterion` values without a session. `verify` is shorthand for a `command` verifier and the
117
+ * default for `required` is true: a criterion the user wrote is one the user wants met.
118
+ */
119
+ export function parseCriteriaInput(raw, options) {
120
+ const list = Array.isArray(raw) ? raw : [];
121
+ const normalized = list.map((entry) => {
122
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
123
+ return entry;
124
+ const e = entry;
125
+ const { verify, ...rest } = e;
126
+ return {
127
+ ...rest,
128
+ required: e.required === undefined ? (options.defaultRequired ?? true) : e.required,
129
+ ...(typeof verify === "string" && e.verifier === undefined
130
+ ? { verifier: { kind: "command", command: verify } }
131
+ : {}),
132
+ };
133
+ });
134
+ if (!Array.isArray(raw))
135
+ return { criteria: [], errors: [{ index: 0, reason: "criteria must be an array" }] };
136
+ const checked = validateCriteriaDrafts(normalized);
137
+ return {
138
+ criteria: checked.accepted.map(({ origin: _inferred, ...draft }) => ({
139
+ ...draft,
140
+ origin: options.origin,
141
+ })),
142
+ errors: checked.dropped.map((d) => ({ index: d.index, reason: d.reason })),
143
+ };
144
+ }
@@ -0,0 +1,12 @@
1
+ export * from "./command.ts";
2
+ export * from "./divergence.ts";
3
+ export * from "./export.ts";
4
+ export * from "./ledger.ts";
5
+ export * from "./plugin-verifiers.ts";
6
+ export * from "./recorder.ts";
7
+ export * from "./scope.ts";
8
+ export * from "./store.ts";
9
+ export * from "./tree.ts";
10
+ export * from "./types.ts";
11
+ export * from "./verifiers.ts";
12
+ export * from "./verify.ts";
@@ -0,0 +1,12 @@
1
+ export * from "./command.js";
2
+ export * from "./divergence.js";
3
+ export * from "./export.js";
4
+ export * from "./ledger.js";
5
+ export * from "./plugin-verifiers.js";
6
+ export * from "./recorder.js";
7
+ export * from "./scope.js";
8
+ export * from "./store.js";
9
+ export * from "./tree.js";
10
+ export * from "./types.js";
11
+ export * from "./verifiers.js";
12
+ export * from "./verify.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure derivation of the Proof ledger (specs/011 section 3.2). Nothing here reads a clock, a
3
+ * database or the filesystem: the same inputs always give the same statuses (D4).
4
+ */
5
+ import type { ProofEvidence, ProofLedger, ProofStatus, ProofVerifierSpec } from "@alisio/sdk";
6
+ import type { CriterionRecord } from "./types.ts";
7
+ export interface DerivedStatus {
8
+ status: ProofStatus;
9
+ /** True when the criterion had successful evidence that a later edit in its scope invalidated. */
10
+ stale: boolean;
11
+ }
12
+ export interface DeriveOptions {
13
+ /** ISO time of the latest edit inside the criterion scope (D5), when there was one. */
14
+ lastEditAt?: string;
15
+ }
16
+ /**
17
+ * Derives the status of one criterion from its own evidence.
18
+ *
19
+ * - user `not_applicable` wins; no verifier is `not_checked` whatever evidence exists (D1);
20
+ * a `manual` verifier is `manual_review`.
21
+ * - the latest backed evidence decides (later `capturedAt` wins; ties go to the later entry); a
22
+ * latest evidence of kind `manual` means a verifier degraded the criterion to `manual_review`;
23
+ * - an edit at or after that evidence expires it (D5, fail safe on equal instants): an expired
24
+ * success is `not_checked` and stale, an expired failure is just `not_checked`.
25
+ */
26
+ export declare function deriveStatus(criterion: {
27
+ verifier?: ProofVerifierSpec;
28
+ notApplicableReason?: string;
29
+ }, evidence: readonly ProofEvidence[], options?: DeriveOptions): DerivedStatus;
30
+ export interface BuildLedgerOptions {
31
+ /** ISO time stamped on the ledger; the caller owns the clock. */
32
+ now: string;
33
+ /** Latest edit time inside the scope of a criterion (Fase 3 supplies it). */
34
+ lastEditAt?: (criterion: CriterionRecord) => string | undefined;
35
+ divergences?: ProofLedger["divergences"];
36
+ }
37
+ /** Assembles the ledger of one session: every criterion derived from its own evidence. */
38
+ export declare function buildLedger(sessionId: string, criteria: readonly CriterionRecord[], evidence: readonly ProofEvidence[], options: BuildLedgerOptions): ProofLedger;
@@ -0,0 +1,57 @@
1
+ const time = (iso) => Date.parse(iso);
2
+ /** D2: only evidence that names its run, its call and a real date can back a status. */
3
+ const backs = (e) => e.runId.length > 0 && e.callId.length > 0 && Number.isFinite(time(e.capturedAt));
4
+ /**
5
+ * Derives the status of one criterion from its own evidence.
6
+ *
7
+ * - user `not_applicable` wins; no verifier is `not_checked` whatever evidence exists (D1);
8
+ * a `manual` verifier is `manual_review`.
9
+ * - the latest backed evidence decides (later `capturedAt` wins; ties go to the later entry); a
10
+ * latest evidence of kind `manual` means a verifier degraded the criterion to `manual_review`;
11
+ * - an edit at or after that evidence expires it (D5, fail safe on equal instants): an expired
12
+ * success is `not_checked` and stale, an expired failure is just `not_checked`.
13
+ */
14
+ export function deriveStatus(criterion, evidence, options = {}) {
15
+ if (criterion.notApplicableReason)
16
+ return { status: "not_applicable", stale: false };
17
+ if (!criterion.verifier)
18
+ return { status: "not_checked", stale: false };
19
+ if (criterion.verifier.kind === "manual")
20
+ return { status: "manual_review", stale: false };
21
+ let latest;
22
+ for (const e of evidence) {
23
+ if (backs(e) && (!latest || time(e.capturedAt) >= time(latest.capturedAt)))
24
+ latest = e;
25
+ }
26
+ if (!latest)
27
+ return { status: "not_checked", stale: false };
28
+ const edited = options.lastEditAt !== undefined && time(options.lastEditAt) >= time(latest.capturedAt);
29
+ if (edited)
30
+ return { status: "not_checked", stale: latest.ok };
31
+ // A verifier handed the criterion to a person (a plugin result with no traceable evidence).
32
+ if (latest.verifierKind === "manual")
33
+ return { status: "manual_review", stale: false };
34
+ return { status: latest.ok ? "verified" : "failed", stale: false };
35
+ }
36
+ /** Assembles the ledger of one session: every criterion derived from its own evidence. */
37
+ export function buildLedger(sessionId, criteria, evidence, options) {
38
+ const derived = criteria.map((criterion) => {
39
+ const own = evidence.filter((e) => e.criterionId === criterion.criterionId);
40
+ const { status, stale } = deriveStatus(criterion, own, {
41
+ lastEditAt: options.lastEditAt?.(criterion),
42
+ });
43
+ return {
44
+ ...criterion,
45
+ status,
46
+ ...(stale ? { stale } : {}),
47
+ evidenceIds: own.map((e) => e.evidenceId),
48
+ };
49
+ });
50
+ return {
51
+ sessionId,
52
+ criteria: derived,
53
+ evidence: [...evidence],
54
+ divergences: options.divergences ?? [],
55
+ generatedAt: options.now,
56
+ };
57
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Registry of the Proof verifiers plugins contribute (`api.proof.registerVerifier`, specs/011
3
+ * 3.8). Criteria bind to a verifier as `<pluginId>:<id>`, exactly like plugin commands, so two
4
+ * plugins cannot shadow each other. The registry holds code only: it never creates criteria or
5
+ * evidence, and it grants no permission (a verifier's tool calls go through the core gate).
6
+ */
7
+ import type { ProofVerifierRegistration } from "@alisio/sdk";
8
+ export interface RegisteredVerifier {
9
+ plugin: string;
10
+ verifier: ProofVerifierRegistration;
11
+ }
12
+ export declare class ProofVerifierRegistry {
13
+ private readonly verifiers;
14
+ /** Registers `verifier` for `plugin`; returns the function that removes exactly this entry. */
15
+ register(plugin: string, verifier: ProofVerifierRegistration): () => void;
16
+ get(verifierId: string): RegisteredVerifier | undefined;
17
+ /** What is registered, sorted by id (no handlers). */
18
+ list(): Array<{
19
+ verifierId: string;
20
+ plugin: string;
21
+ describe: string;
22
+ }>;
23
+ }
@@ -0,0 +1,39 @@
1
+ const VERIFIER_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
2
+ const DESCRIBE_MAX = 500;
3
+ export class ProofVerifierRegistry {
4
+ verifiers = new Map();
5
+ /** Registers `verifier` for `plugin`; returns the function that removes exactly this entry. */
6
+ register(plugin, verifier) {
7
+ if (typeof verifier?.id !== "string" || !VERIFIER_ID.test(verifier.id))
8
+ throw new Error(`Invalid verifier id: ${String(verifier?.id)}`);
9
+ if (typeof verifier.describe !== "string" || !verifier.describe.trim())
10
+ throw new Error("A verifier needs a description");
11
+ if (typeof verifier.run !== "function")
12
+ throw new Error("A verifier needs a run function");
13
+ const key = `${plugin}:${verifier.id}`;
14
+ if (this.verifiers.has(key))
15
+ throw new Error(`Duplicate verifier ${key}`);
16
+ const entry = {
17
+ plugin,
18
+ verifier: { ...verifier, describe: verifier.describe.trim().slice(0, DESCRIBE_MAX) },
19
+ };
20
+ this.verifiers.set(key, entry);
21
+ return () => {
22
+ if (this.verifiers.get(key) === entry)
23
+ this.verifiers.delete(key);
24
+ };
25
+ }
26
+ get(verifierId) {
27
+ return this.verifiers.get(verifierId);
28
+ }
29
+ /** What is registered, sorted by id (no handlers). */
30
+ list() {
31
+ return [...this.verifiers.entries()]
32
+ .map(([verifierId, { plugin, verifier }]) => ({
33
+ verifierId,
34
+ plugin,
35
+ describe: verifier.describe,
36
+ }))
37
+ .sort((a, b) => (a.verifierId < b.verifierId ? -1 : a.verifierId > b.verifierId ? 1 : 0));
38
+ }
39
+ }
@@ -0,0 +1,69 @@
1
+ import type { RunEventDataMap, SqlDatabase, ToolResult } from "@alisio/sdk";
2
+ import { type Divergence } from "./divergence.ts";
3
+ export type ProofUpdate = RunEventDataMap["proof_updated"];
4
+ /** A tool call that already executed, as the runner reports it. */
5
+ export interface ExecutedCall {
6
+ sessionId: string;
7
+ runId: string;
8
+ callId: string;
9
+ name: string;
10
+ input: Record<string, unknown>;
11
+ /** The result exactly as the tool returned it. */
12
+ result: ToolResult;
13
+ workspace: string;
14
+ durationMs: number;
15
+ }
16
+ /** The last `EXCERPT_BYTES` bytes of the redacted output (the end holds the summary and errors). */
17
+ export declare function excerptOf(output: string): string;
18
+ export declare class ProofRecorder {
19
+ private readonly db;
20
+ private readonly options;
21
+ private readonly store;
22
+ /** `bg_run` calls whose exit code has not been seen yet, by session and task id. */
23
+ private readonly background;
24
+ constructor(db: SqlDatabase, options?: {
25
+ now?: () => Date;
26
+ /** `proof.coverage.enabled`, read on every call so a config reload applies at once. */
27
+ coverage?: () => boolean;
28
+ });
29
+ /** Records the evidence a just-executed call yields; returns the ledger changes to announce. */
30
+ capture(call: ExecutedCall): ProofUpdate[];
31
+ /**
32
+ * A `read_file` that threw because the file is not there (the runner reports it here: a throw
33
+ * never reaches `capture`). It refutes every `file` criterion on that path; any other failure
34
+ * proves nothing.
35
+ */
36
+ captureFailure(call: ExecutedCall): ProofUpdate[];
37
+ /** The criteria of the session the recorder can capture evidence for (not marked N/A). */
38
+ private tracked;
39
+ private finding;
40
+ private fileFindings;
41
+ private dataFindings;
42
+ private commandFindings;
43
+ /** Appends one evidence per finding, once the call is proven to be in the session journal. */
44
+ private persist;
45
+ private clock;
46
+ /**
47
+ * Notes a successful write/edit of a workspace file (D5) and announces the criteria whose
48
+ * derived status or staleness changed because of it. Edits outside the workspace cannot touch
49
+ * a workspace criterion and are ignored.
50
+ */
51
+ private captureEdit;
52
+ /**
53
+ * The claims of a final answer that the ledger does not back (D8: shown, never acted on). Empty
54
+ * without criteria, after one indexed read (D7).
55
+ */
56
+ divergences(sessionId: string, finalText: string): Divergence[];
57
+ /** Reads what a command call proved, or undefined when it proved nothing (yet). */
58
+ private outcome;
59
+ private processOutcome;
60
+ private startedBackground;
61
+ /** `bg_output` carries the exit code of a task this recorder saw start; once per task. */
62
+ private backgroundOutcome;
63
+ private key;
64
+ /**
65
+ * D2/D3: the call must exist in the session's tool-call journal under this run and name, which
66
+ * the runner writes before the tool executes. A forged or foreign `runId`/`callId` fails here.
67
+ */
68
+ private executed;
69
+ }