@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
@@ -1,6 +1,6 @@
1
- import type { GoalEvidence, GoalInfo, GoalWaiting } from "@alisio/sdk";
1
+ import type { GoalEvidence, GoalInfo, GoalWaiting, ProofLedger } from "@alisio/sdk";
2
2
  import { type GoalBlocker, type GoalHostState } from "./blockers.ts";
3
- import { type GoalLimits, type GoalLog, type GoalRecord, type GoalRunOutcome } from "./machine.ts";
3
+ import { type GoalLimits, type GoalLog, type GoalRecord, type GoalRunOutcome, type GoalUnmet } from "./machine.ts";
4
4
  import { type GoalPrompt } from "./prompts.ts";
5
5
  import { type GoalExpectation, type GoalStore } from "./store.ts";
6
6
  /** `config.goal`, as the service reads it (live: a settings change applies to the next call). */
@@ -58,6 +58,13 @@ export interface GoalServiceOptions {
58
58
  onChange?: (sessionId: string, goal: GoalRecord | undefined) => void;
59
59
  /** Breaker observations (counts only; never the objective or the reply). */
60
60
  onLog?: (sessionId: string, log: GoalLog) => void;
61
+ /**
62
+ * Where the Proof ledger comes from (`ProofStore` fits). Without it criteria do not exist for
63
+ * the goal: completion behaves as in 0.7.x and a declared `evidenceId` is dropped.
64
+ */
65
+ proof?: {
66
+ ledger(sessionId: string): ProofLedger;
67
+ };
61
68
  }
62
69
  export declare class GoalService {
63
70
  private readonly options;
@@ -93,6 +100,10 @@ export declare class GoalService {
93
100
  next(sessionId: string, view: GoalHostView): GoalDecision;
94
101
  /** The claimed continuation could not start: give the claim back (the next call may retry). */
95
102
  abandon(sessionId: string, requestId: string, kind: GoalPrompt["kind"]): void;
103
+ /** Runs whose `complete` the ledger refused, per goal (memory only: a restart pauses goals). */
104
+ private readonly refusals;
105
+ /** Records a refused completion of `runId`; true once enough different runs were refused. */
106
+ private refusedRuns;
96
107
  /** `update_goal` (the model): the only transitions the model can cause. */
97
108
  report(sessionId: string, runId: string | undefined, report: {
98
109
  status: "complete" | "blocked";
@@ -107,7 +118,16 @@ export declare class GoalService {
107
118
  ok: false;
108
119
  code: GoalErrorCode;
109
120
  message: string;
121
+ unmet?: GoalUnmet[];
110
122
  };
123
+ /**
124
+ * The USER completes the goal over unmet required criteria (O6). A separate method on purpose:
125
+ * `report` is the model's only door and has no way to carry an override, so the model cannot
126
+ * reach this path whatever it sends. Hosts call it from a user action only (Phase 5 surfaces).
127
+ */
128
+ overrideComplete(sessionId: string, input: {
129
+ reason: string;
130
+ }, expect?: GoalExpectation): GoalOutcome;
111
131
  /** Startup: goals whose driver process is gone pause with reason `restart`. */
112
132
  recover(): GoalRecord[];
113
133
  }
@@ -18,7 +18,7 @@
18
18
  import { randomUUID } from "node:crypto";
19
19
  import { goalBlocker, waitingOf } from "./blockers.js";
20
20
  import { objectiveError } from "./command.js";
21
- import { checkCaps, DEFAULT_GOAL_LIMITS, modelBlocked, modelComplete, newGoal, settleRun, toGoalInfo, userBudget, userEdit, userPause, userResume, } from "./machine.js";
21
+ import { COMPLETION_REFUSAL_LIMIT, checkCaps, criteriaForGoal, DEFAULT_GOAL_LIMITS, modelBlocked, modelComplete, newGoal, pauseForRefusedCompletions, settleRun, toGoalInfo, userBudget, userEdit, userOverrideComplete, userPause, userResume, } from "./machine.js";
22
22
  import { buildGoalPrompt } from "./prompts.js";
23
23
  export const settingsToLimits = (settings) => ({
24
24
  maxTurns: settings.maxTurns,
@@ -27,6 +27,24 @@ export const settingsToLimits = (settings) => ({
27
27
  noToolTurnsLimit: settings.noToolTurnsLimit,
28
28
  blockedRepeats: settings.blockedRepeats,
29
29
  });
30
+ /**
31
+ * The declared evidence with every `evidenceId` that does not resolve to captured evidence of the
32
+ * session removed (such a declaration is free text and backs nothing), and the ids that resolved.
33
+ */
34
+ function linkEvidence(evidence, ledger) {
35
+ const known = new Set(ledger?.evidence.map((e) => e.evidenceId));
36
+ const linked = [];
37
+ const out = evidence.map((item) => {
38
+ const { evidenceId, ...rest } = item;
39
+ if (evidenceId === undefined)
40
+ return item;
41
+ if (!known.has(evidenceId))
42
+ return rest;
43
+ linked.push(evidenceId);
44
+ return item;
45
+ });
46
+ return { evidence: out, linked };
47
+ }
30
48
  export class GoalService {
31
49
  options;
32
50
  store;
@@ -109,6 +127,9 @@ export class GoalService {
109
127
  if (!this.enabled)
110
128
  return { ok: false, code: "disabled", message: "Goals are turned off (goal.enabled)." };
111
129
  const limits = this.limits();
130
+ const current = this.store.get(sessionId);
131
+ if (current)
132
+ this.refusals.delete(current.goalId);
112
133
  return this.act(sessionId, expect, (goal) => userResume(goal, this.clock(), limits, this.pid));
113
134
  }
114
135
  edit(sessionId, objective, expect) {
@@ -232,17 +253,49 @@ export class GoalService {
232
253
  if (result.ok)
233
254
  this.emit(sessionId, result.goal);
234
255
  }
256
+ /** Runs whose `complete` the ledger refused, per goal (memory only: a restart pauses goals). */
257
+ refusals = new Map();
258
+ /** Records a refused completion of `runId`; true once enough different runs were refused. */
259
+ refusedRuns(goalId, runId) {
260
+ const runs = this.refusals.get(goalId) ?? new Set();
261
+ runs.add(runId);
262
+ this.refusals.set(goalId, runs);
263
+ if (runs.size < COMPLETION_REFUSAL_LIMIT)
264
+ return false;
265
+ this.refusals.delete(goalId);
266
+ return true;
267
+ }
235
268
  /** `update_goal` (the model): the only transitions the model can cause. */
236
269
  report(sessionId, runId, report) {
237
270
  const limits = this.limits();
238
271
  const outcome = this.store.update(sessionId, (goal) => {
272
+ // The ledger is read inside this transaction, so the verdict and the write see one state.
273
+ // Only a completion, or a declaration that links evidence, needs it (D7: no extra reads).
274
+ const needsLedger = report.status === "complete" || report.evidence.some((e) => e.evidenceId !== undefined);
275
+ const ledger = needsLedger ? this.options.proof?.ledger(sessionId) : undefined;
276
+ const linked = linkEvidence(report.evidence, ledger);
239
277
  if (report.status === "complete") {
240
- const result = modelComplete(goal, this.clock(), report);
241
- return result.ok
242
- ? { goal: result.goal, value: { ok: true, stopped: true, needed: undefined } }
243
- : { value: { ok: false, message: result.message } };
278
+ const proof = ledger
279
+ ? { criteria: criteriaForGoal(ledger, goal.goalId), linkedEvidence: linked.linked }
280
+ : undefined;
281
+ const result = modelComplete(goal, this.clock(), { summary: report.summary, evidence: linked.evidence }, proof);
282
+ if (result.ok)
283
+ return { goal: result.goal, value: { ok: true, stopped: true, needed: undefined } };
284
+ // A refusal over the ledger counts per run; the third run that tries pauses the goal.
285
+ if (result.unmet?.length && runId && this.refusedRuns(goal.goalId, runId)) {
286
+ return {
287
+ goal: pauseForRefusedCompletions(goal, this.clock()),
288
+ value: {
289
+ ok: false,
290
+ message: `${result.message} The goal is paused: completion was refused in ${COMPLETION_REFUSAL_LIMIT} different runs. The user decides: resume, or complete it with an override.`,
291
+ unmet: result.unmet,
292
+ paused: true,
293
+ },
294
+ };
295
+ }
296
+ return { value: { ok: false, message: result.message, unmet: result.unmet } };
244
297
  }
245
- const result = modelBlocked(goal, this.clock(), { summary: report.summary, evidence: report.evidence, runId }, limits);
298
+ const result = modelBlocked(goal, this.clock(), { summary: report.summary, evidence: linked.evidence, runId }, limits);
246
299
  return result.ok
247
300
  ? {
248
301
  goal: result.goal.goal,
@@ -253,8 +306,15 @@ export class GoalService {
253
306
  if (!outcome.ok)
254
307
  return { ok: false, code: outcome.code, message: outcome.message };
255
308
  const value = outcome.value;
309
+ if (!value.ok && value.paused)
310
+ this.emit(sessionId, outcome.goal);
256
311
  if (!value.ok)
257
- return { ok: false, code: "not_allowed", message: value.message };
312
+ return {
313
+ ok: false,
314
+ code: "not_allowed",
315
+ message: value.message,
316
+ ...(value.unmet ? { unmet: value.unmet } : {}),
317
+ };
258
318
  this.emit(sessionId, outcome.goal);
259
319
  return {
260
320
  ok: true,
@@ -263,6 +323,33 @@ export class GoalService {
263
323
  ...(value.needed ? { needed: value.needed } : {}),
264
324
  };
265
325
  }
326
+ /**
327
+ * The USER completes the goal over unmet required criteria (O6). A separate method on purpose:
328
+ * `report` is the model's only door and has no way to carry an override, so the model cannot
329
+ * reach this path whatever it sends. Hosts call it from a user action only (Phase 5 surfaces).
330
+ */
331
+ overrideComplete(sessionId, input, expect) {
332
+ const reason = input.reason.trim();
333
+ if (!reason)
334
+ return { ok: false, code: "invalid", message: "An override needs a reason." };
335
+ const outcome = this.store.update(sessionId, (goal) => {
336
+ const ledger = this.options.proof?.ledger(sessionId);
337
+ const result = userOverrideComplete(goal, this.clock(), {
338
+ reason,
339
+ proof: {
340
+ criteria: ledger ? criteriaForGoal(ledger, goal.goalId) : [],
341
+ linkedEvidence: goal.proof?.linkedEvidence ?? [],
342
+ },
343
+ });
344
+ return result.ok ? { goal: result.goal, value: result } : { value: result };
345
+ }, expect);
346
+ if (!outcome.ok)
347
+ return { ok: false, code: outcome.code, message: outcome.message };
348
+ const result = outcome.value;
349
+ if (!result.ok)
350
+ return { ok: false, code: "not_allowed", message: result.message };
351
+ return this.done(sessionId, { ok: true, goal: result.goal });
352
+ }
266
353
  /** Startup: goals whose driver process is gone pause with reason `restart`. */
267
354
  recover() {
268
355
  const paused = this.store.pauseOrphans(this.clock());
@@ -13,6 +13,24 @@ function parseEvidence(raw) {
13
13
  return undefined;
14
14
  }
15
15
  }
16
+ function parseProof(raw) {
17
+ if (typeof raw !== "string" || !raw)
18
+ return undefined;
19
+ try {
20
+ const value = JSON.parse(raw);
21
+ return value && Array.isArray(value.criteria) && Array.isArray(value.unmet)
22
+ ? {
23
+ criteria: value.criteria,
24
+ unmet: value.unmet,
25
+ linkedEvidence: Array.isArray(value.linkedEvidence) ? value.linkedEvidence : [],
26
+ ...(value.override ? { override: value.override } : {}),
27
+ }
28
+ : undefined;
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
16
34
  export function goalFromRow(row) {
17
35
  const status = String(row.status);
18
36
  return {
@@ -26,6 +44,7 @@ export function goalFromRow(row) {
26
44
  ...(parseEvidence(row.evidence)
27
45
  ? { evidence: parseEvidence(row.evidence) }
28
46
  : {}),
47
+ ...(parseProof(row.proof_json) ? { proof: parseProof(row.proof_json) } : {}),
29
48
  epoch: Number(row.epoch ?? 1),
30
49
  ...(num(row.token_budget) !== undefined
31
50
  ? { tokenBudget: num(row.token_budget) }
@@ -55,7 +74,7 @@ export function goalFromRow(row) {
55
74
  const COLUMNS = `session, goal_id, objective, status, reason, detail, summary, evidence, epoch,
56
75
  token_budget, tokens_used, turns_used, max_turns, max_wall_ms, active_ms, no_tool_streak,
57
76
  repeat_streak, last_reply_hash, blocked_streak, blocked_run, continuations, inflight,
58
- last_run_id, kickoff_sent, owner_pid, created_at, updated_at, completed_at`;
77
+ last_run_id, kickoff_sent, owner_pid, created_at, updated_at, completed_at, proof_json`;
59
78
  export class GoalStore {
60
79
  db;
61
80
  constructor(db) {
@@ -89,8 +108,8 @@ export class GoalStore {
89
108
  continuations=excluded.continuations, inflight=excluded.inflight,
90
109
  last_run_id=excluded.last_run_id, kickoff_sent=excluded.kickoff_sent,
91
110
  owner_pid=excluded.owner_pid, updated_at=excluded.updated_at,
92
- completed_at=excluded.completed_at`)
93
- .run(goal.sessionId, goal.goalId, goal.objective, goal.status, goal.reason ?? null, goal.detail ?? null, goal.summary ?? null, goal.evidence?.length ? JSON.stringify(goal.evidence) : null, goal.epoch, goal.tokenBudget ?? null, goal.tokensUsed, goal.turnsUsed, goal.maxTurns, goal.maxWallMs, goal.activeMs, goal.noToolStreak, goal.repeatStreak, goal.lastReplyHash ?? null, goal.blockedStreak, goal.blockedRun ?? null, goal.continuations, goal.inflight ?? null, goal.lastRunId ?? null, goal.kickoffSent ? 1 : 0, goal.ownerPid ?? null, goal.createdAt, goal.updatedAt, goal.completedAt ?? null);
111
+ completed_at=excluded.completed_at, proof_json=excluded.proof_json`)
112
+ .run(goal.sessionId, goal.goalId, goal.objective, goal.status, goal.reason ?? null, goal.detail ?? null, goal.summary ?? null, goal.evidence?.length ? JSON.stringify(goal.evidence) : null, goal.epoch, goal.tokenBudget ?? null, goal.tokensUsed, goal.turnsUsed, goal.maxTurns, goal.maxWallMs, goal.activeMs, goal.noToolStreak, goal.repeatStreak, goal.lastReplyHash ?? null, goal.blockedStreak, goal.blockedRun ?? null, goal.continuations, goal.inflight ?? null, goal.lastRunId ?? null, goal.kickoffSent ? 1 : 0, goal.ownerPid ?? null, goal.createdAt, goal.updatedAt, goal.completedAt ?? null, goal.proof ? JSON.stringify(goal.proof) : null);
94
113
  }
95
114
  /**
96
115
  * Replaces (or creates) the goal of a session. `build` gets the previous goal, if any, so the
package/dist/index.d.ts CHANGED
@@ -41,6 +41,21 @@ export { ToolRegistry } from "./core/registry.ts";
41
41
  export { RunClock } from "./core/run-clock.ts";
42
42
  export { AgentRunner, type CompactionResult, defaultTokenBudget, type RunnerOptions, type RunnerSettingsPatch, type RunOptions, } from "./core/runner.ts";
43
43
  export { CircuitBreaker, type CircuitBreakerOptions, type CircuitState, DECISION_LIMITS, DEFAULT_DECISIONS_CONFIG, type DecisionAttempt, DecisionError, type DecisionMetricEntry, DecisionMetrics, type DecisionMetricsSnapshot, type DecisionOutcome, DecisionRegistry, DecisionService, type DecisionServiceDeps, type DecisionStatus, type DecisionsConfig, formatDecisionStatsSection, formatDecisionsReport, HEALTH_TIMEOUT_MS, type RegisteredDecisionProvider, splitSupported, validateAnswers, validateRequest, } from "./decisions/index.ts";
44
+ export { canonicalJson, hashCanonical } from "./eval/canonical.ts";
45
+ export { CASSETTE_SUFFIX, type Cassette, type CassetteCall, CassetteFormatError, type CassetteHeader, type CassetteMeta, CassetteRecorder, CassetteWriteError, MAX_CASSETTE_BYTES, MAX_CASSETTE_DIR_BYTES, parseCassette, RecordingProvider, ReplayDivergenceError, ReplayExhausted, ReplayProvider, ReplayUnconsumedError, readCassette, serializeCassette, type WriteOptions, writeCassette, } from "./eval/cassette.ts";
46
+ export { type Conditions, compareReports, compareVariants, EvalCompareError, type EvalComparison, type Interval, type MetricDelta, parseReport, renderComparisonMarkdown, renderVariantComparisonMarkdown, type TaskComparison, type TokenComparison, type VariantComparison, type Verdict, } from "./eval/compare.ts";
47
+ export { CONFIG_HASH_COVERAGE, type ConfigHashExtras, checkTaskPolicy, computeConfigHash, type EvalApplication, EvalBudgetExceeded, type EvalRunMode, EvalRunRefusedError, type InvokerPermissions, type RunTaskOptions, runTask, } from "./eval/engine.ts";
48
+ export { buildExportBundle, type EvalExportBundle, EvalExportRefusedError, type ExportContext, } from "./eval/export.ts";
49
+ export { computeFingerprint, type FingerprintDiff, type FingerprintDiffEntry, } from "./eval/fingerprint.ts";
50
+ export { EvalPathError, resolveInside } from "./eval/paths.ts";
51
+ export { interleaveVariants, mulberry32, type PlannedRun, planRuns, type VariantStep, } from "./eval/plan.ts";
52
+ export { type BuildReportOptions, buildReport, EvalReportError, estimateRunCost, lowPowerTasks, MIN_POWER_K, renderReportMarkdown, } from "./eval/report.ts";
53
+ export { type RunEvaluationOptions, type RunEvaluationResult, type RunVariantsOptions, runEvaluation, runVariantEvaluations, } from "./eval/run-suite.ts";
54
+ export { findSecrets, redactSecrets } from "./eval/secrets.ts";
55
+ export { median, passAtK, passPowK, sampleVariance, wilsonInterval, } from "./eval/stats.ts";
56
+ export { EvalSuiteError, type LoadedSuite, loadSuite, MAX_VERIFIER_TIMEOUT_MS, } from "./eval/suite.ts";
57
+ export { applyConfigOverrides, EvalVariantError, loadVariantsFile, resolveVariants, type VariantsFile, } from "./eval/variants.ts";
58
+ export { runVerifier, runVerifiers, snapshotTree, type ToolEvent, type TreeSnapshot, type VerifierContext, type VerifierResult, type VerifierStatus, } from "./eval/verifiers.ts";
44
59
  export { type ExtensionConflict, ExtensionRegistry } from "./extensions/registry.ts";
45
60
  export { ForgeError } from "./forge/errors.ts";
46
61
  export { downloadSize as pyodideDownloadSize, inspectPyodide, installPyodide, PyodideIntegrityError, pyodideDir, } from "./forge/python/pyodide-install.ts";
@@ -55,9 +70,10 @@ export { McpConnector, type McpServerInfo, type McpStatus, type McpToolInfo, } f
55
70
  export { describePermissionStatus, type EffectState, effectState, FULL_ACCESS_WARNING, type ModeFlags, type ModePolicy, modeFromFlags, modeToPreset, PERMISSION_MODE_TABLE, PERMISSION_MODES, PERMISSION_MODES_LOCKED, PERMISSION_USAGE, type PermissionCommand, type PermissionModeLabel, type PermissionModeSpec, parsePermissionCommand, parsePermissionMode, presetToMode, READ_ONLY_POLICY, } from "./permissions/modes.ts";
56
71
  export { DIAGRAM_MAX_BYTES, DIAGRAM_MAX_NODES, DIAGRAM_SYNTAXES, DIAGRAMS_MAX_ALLOWED, mermaidProblem, type PlanDiagram, validateDiagrams, } from "./plan/diagrams.ts";
57
72
  export { buildPlanManifest, parsePlanHeadings, slugifyHeading, } from "./plan/manifest.ts";
58
- export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, type PlanFollowUp, type PlanState, type PlanStatus, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.ts";
73
+ export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_CRITERIA_KEY, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, type PlanFollowUp, type PlanState, type PlanStatus, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.ts";
59
74
  export { discoverPlugins, type PluginEnvironment, PluginHost, type PluginHostOptions, pluginPrefix, type ViewInfo, ViewRunError, } from "./plugins/host.ts";
60
75
  export { type CliInstallOptions, cliInstall, cliUpdateAll, INSTALL_TIMEOUT_MS, type InstallPluginInput, type InstallPluginResult, type InstallRunner, installedGlobalPluginPackages, installedNpmPlugins, installPlugin, isPeerConflict, npmInstallTarget, type PluginSpec, parsePluginSpec, pluginTrustRemark, printInstallResult, registerPluginInstallTool, sanitizeNpmError, updateAllPlugins, } from "./plugins/install.ts";
76
+ export * from "./proof/index.ts";
61
77
  export { ActiveProvider, ProviderRegistry, UnconfiguredProvider } from "./providers/registry.ts";
62
78
  export { availableProviderModels, type ProviderModelCatalog, resolveProviderModel, } from "./providers/routing.ts";
63
79
  export { maskSecret, type ProviderProfile, type ProviderSettings, ProviderSettingsStore, } from "./providers/settings.ts";
@@ -69,7 +85,7 @@ export { type ExternalDirectoryDecision, type ExternalDirectoryHandler, type Ext
69
85
  export { BlobStore, type BlobStoreOptions, BlobTooLarge } from "./runtime/blobs.ts";
70
86
  export { clipLines, unifiedPatch } from "./runtime/diff.ts";
71
87
  export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.ts";
72
- export { newId, ulid } from "./runtime/ids.ts";
88
+ export { type IdSource, newId, newUuid, seededIdSource, setIdSource, ulid, } from "./runtime/ids.ts";
73
89
  export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.ts";
74
90
  export { findWorkspace, inside, outsideRootsMessage, safePath, workspaceKey, } from "./runtime/paths.ts";
75
91
  export { isMissingCommand, type ProcessResult, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.ts";
package/dist/index.js CHANGED
@@ -40,6 +40,21 @@ export { ToolRegistry } from "./core/registry.js";
40
40
  export { RunClock } from "./core/run-clock.js";
41
41
  export { AgentRunner, defaultTokenBudget, } from "./core/runner.js";
42
42
  export { CircuitBreaker, DECISION_LIMITS, DEFAULT_DECISIONS_CONFIG, DecisionError, DecisionMetrics, DecisionRegistry, DecisionService, formatDecisionStatsSection, formatDecisionsReport, HEALTH_TIMEOUT_MS, splitSupported, validateAnswers, validateRequest, } from "./decisions/index.js";
43
+ export { canonicalJson, hashCanonical } from "./eval/canonical.js";
44
+ export { CASSETTE_SUFFIX, CassetteFormatError, CassetteRecorder, CassetteWriteError, MAX_CASSETTE_BYTES, MAX_CASSETTE_DIR_BYTES, parseCassette, RecordingProvider, ReplayDivergenceError, ReplayExhausted, ReplayProvider, ReplayUnconsumedError, readCassette, serializeCassette, writeCassette, } from "./eval/cassette.js";
45
+ export { compareReports, compareVariants, EvalCompareError, parseReport, renderComparisonMarkdown, renderVariantComparisonMarkdown, } from "./eval/compare.js";
46
+ export { CONFIG_HASH_COVERAGE, checkTaskPolicy, computeConfigHash, EvalBudgetExceeded, EvalRunRefusedError, runTask, } from "./eval/engine.js";
47
+ export { buildExportBundle, EvalExportRefusedError, } from "./eval/export.js";
48
+ export { computeFingerprint, } from "./eval/fingerprint.js";
49
+ export { EvalPathError, resolveInside } from "./eval/paths.js";
50
+ export { interleaveVariants, mulberry32, planRuns, } from "./eval/plan.js";
51
+ export { buildReport, EvalReportError, estimateRunCost, lowPowerTasks, MIN_POWER_K, renderReportMarkdown, } from "./eval/report.js";
52
+ export { runEvaluation, runVariantEvaluations, } from "./eval/run-suite.js";
53
+ export { findSecrets, redactSecrets } from "./eval/secrets.js";
54
+ export { median, passAtK, passPowK, sampleVariance, wilsonInterval, } from "./eval/stats.js";
55
+ export { EvalSuiteError, loadSuite, MAX_VERIFIER_TIMEOUT_MS, } from "./eval/suite.js";
56
+ export { applyConfigOverrides, EvalVariantError, loadVariantsFile, resolveVariants, } from "./eval/variants.js";
57
+ export { runVerifier, runVerifiers, snapshotTree, } from "./eval/verifiers.js";
43
58
  export { ExtensionRegistry } from "./extensions/registry.js";
44
59
  export { ForgeError } from "./forge/errors.js";
45
60
  export { downloadSize as pyodideDownloadSize, inspectPyodide, installPyodide, PyodideIntegrityError, pyodideDir, } from "./forge/python/pyodide-install.js";
@@ -54,9 +69,10 @@ export { McpConnector, } from "./mcp/connector.js";
54
69
  export { describePermissionStatus, effectState, FULL_ACCESS_WARNING, modeFromFlags, modeToPreset, PERMISSION_MODE_TABLE, PERMISSION_MODES, PERMISSION_MODES_LOCKED, PERMISSION_USAGE, parsePermissionCommand, parsePermissionMode, presetToMode, READ_ONLY_POLICY, } from "./permissions/modes.js";
55
70
  export { DIAGRAM_MAX_BYTES, DIAGRAM_MAX_NODES, DIAGRAM_SYNTAXES, DIAGRAMS_MAX_ALLOWED, mermaidProblem, validateDiagrams, } from "./plan/diagrams.js";
56
71
  export { buildPlanManifest, parsePlanHeadings, slugifyHeading, } from "./plan/manifest.js";
57
- export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.js";
72
+ export { claimApprovedPlan, currentPlan, discardApprovedPlan, IMPLEMENTATION_AGENT_ID, implementationPrompt, PLAN_CONTEXT_MAX_CHARS, PLAN_CRITERIA_KEY, PLAN_OPTIONS, PLAN_QUESTION_ID, PLAN_REVIEW_TITLE, PLAN_TEXT_KEY, planHash, readPlanState, settlePlanRun, withdrawPendingPlan, } from "./plan/state.js";
58
73
  export { discoverPlugins, PluginHost, pluginPrefix, ViewRunError, } from "./plugins/host.js";
59
74
  export { cliInstall, cliUpdateAll, INSTALL_TIMEOUT_MS, installedGlobalPluginPackages, installedNpmPlugins, installPlugin, isPeerConflict, npmInstallTarget, parsePluginSpec, pluginTrustRemark, printInstallResult, registerPluginInstallTool, sanitizeNpmError, updateAllPlugins, } from "./plugins/install.js";
75
+ export * from "./proof/index.js";
60
76
  export { ActiveProvider, ProviderRegistry, UnconfiguredProvider } from "./providers/registry.js";
61
77
  export { availableProviderModels, resolveProviderModel, } from "./providers/routing.js";
62
78
  export { maskSecret, ProviderSettingsStore, } from "./providers/settings.js";
@@ -68,7 +84,7 @@ export { PathAccess, } from "./runtime/access.js";
68
84
  export { BlobStore, BlobTooLarge } from "./runtime/blobs.js";
69
85
  export { clipLines, unifiedPatch } from "./runtime/diff.js";
70
86
  export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.js";
71
- export { newId, ulid } from "./runtime/ids.js";
87
+ export { newId, newUuid, seededIdSource, setIdSource, ulid, } from "./runtime/ids.js";
72
88
  export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.js";
73
89
  export { findWorkspace, inside, outsideRootsMessage, safePath, workspaceKey, } from "./runtime/paths.js";
74
90
  export { isMissingCommand, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Validation of the acceptance criteria a model proposes in `exit_plan` (specs/011 3.6, O3).
3
+ * Pure: no I/O. The result is a DRAFT: nothing here creates a criterion; the plan decision of
4
+ * the user does (`exit-plan.ts`). The model may only propose verifier kinds Alisio can run today
5
+ * (`command`, `file`, `data-check`, `coverage`, `plugin`, `manual`) and plain workspace-relative scopes.
6
+ */
7
+ import type { PlanCriterionDraft } from "@alisio/sdk";
8
+ export declare const CRITERIA_MAX = 20;
9
+ export declare const CRITERION_TEXT_MAX = 500;
10
+ export interface DroppedCriterion {
11
+ /** Zero-based position in the proposed list. */
12
+ index: number;
13
+ reason: string;
14
+ }
15
+ export interface ValidatedCriteria {
16
+ accepted: PlanCriterionDraft[];
17
+ dropped: DroppedCriterion[];
18
+ }
19
+ /** Turns the model's `acceptanceCriteria` input into drafts, reporting what it could not keep. */
20
+ export declare function validateCriteriaDrafts(raw: unknown): ValidatedCriteria;
21
+ /**
22
+ * Applies the user's per-draft decisions (`plan:criteria`) to the drafts of the plan they approved.
23
+ * `undefined` selection keeps every draft (the plain "approve"). Anything malformed, any index
24
+ * outside the list or an empty edit returns `undefined` for "nothing is activated": a selection
25
+ * the user did not make cleanly never activates a criterion (fail closed).
26
+ */
27
+ export declare function applyCriteriaSelection(drafts: readonly PlanCriterionDraft[], raw: unknown): {
28
+ kept: PlanCriterionDraft[];
29
+ rejected: number;
30
+ } | undefined;
@@ -0,0 +1,256 @@
1
+ export const CRITERIA_MAX = 20;
2
+ export const CRITERION_TEXT_MAX = 500;
3
+ const COMMAND_MAX = 1000;
4
+ const PATH_MAX = 500;
5
+ const PATTERN_MAX = 200;
6
+ const SCOPE_ENTRIES_MAX = 20;
7
+ const SCOPE_ENTRY_MAX = 256;
8
+ const EXIT_CODES_MAX = 10;
9
+ const DATASET_ID_MAX = 64;
10
+ const IDENTIFIER_MAX = 200;
11
+ const EXPECTED_MAX = 500;
12
+ const PLUGIN_INPUT_MAX = 4096;
13
+ const PLUGIN_VERIFIER_ID = /^[a-z0-9][a-z0-9._-]{0,63}:[a-z0-9][a-z0-9._-]{0,63}$/;
14
+ const DATA_CHECKS = new Set(["row_count", "sum", "equals"]);
15
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16
+ const text = (value, max) => typeof value === "string" && value.trim() && value.length <= max ? value : undefined;
17
+ function verifierOf(raw) {
18
+ if (!isRecord(raw))
19
+ return "verifier must be an object";
20
+ if (raw.kind === "manual")
21
+ return { kind: "manual" };
22
+ if (raw.kind === "command") {
23
+ const command = text(raw.command, COMMAND_MAX)?.trim();
24
+ if (!command)
25
+ return "a command verifier needs a command";
26
+ const cwd = raw.cwd === undefined ? undefined : text(raw.cwd, PATH_MAX);
27
+ if (raw.cwd !== undefined && !cwd)
28
+ return "cwd must be a short string";
29
+ if (cwd && (isAbsoluteLike(cwd) || hasParentSegment(cwd)))
30
+ return "cwd must stay in the workspace";
31
+ const codes = raw.successExitCodes;
32
+ if (codes !== undefined &&
33
+ !(Array.isArray(codes) &&
34
+ codes.length > 0 &&
35
+ codes.length <= EXIT_CODES_MAX &&
36
+ codes.every((c) => Number.isInteger(c) && c >= 0 && c <= 255)))
37
+ return "successExitCodes must be a short list of exit codes";
38
+ const pattern = raw.outputPattern === undefined ? undefined : text(raw.outputPattern, PATTERN_MAX);
39
+ if (raw.outputPattern !== undefined && !pattern)
40
+ return "outputPattern must be a short string";
41
+ return {
42
+ kind: "command",
43
+ command,
44
+ ...(cwd ? { cwd } : {}),
45
+ ...(codes ? { successExitCodes: codes } : {}),
46
+ ...(pattern ? { outputPattern: pattern } : {}),
47
+ };
48
+ }
49
+ if (raw.kind === "file") {
50
+ const path = text(raw.path, PATH_MAX)?.trim();
51
+ if (!path || isAbsoluteLike(path) || hasParentSegment(path))
52
+ return "a file verifier needs a workspace-relative path";
53
+ const sha = typeof raw.sha256 === "string" && /^[0-9a-f]{64}$/.test(raw.sha256);
54
+ const pattern = raw.pattern === undefined ? undefined : text(raw.pattern, PATTERN_MAX);
55
+ if (raw.pattern !== undefined && !pattern)
56
+ return "pattern must be a short string";
57
+ if (raw.sha256 !== undefined && !sha)
58
+ return "sha256 must be 64 hex characters";
59
+ return {
60
+ kind: "file",
61
+ path,
62
+ ...(sha ? { sha256: raw.sha256 } : {}),
63
+ ...(pattern ? { pattern } : {}),
64
+ };
65
+ }
66
+ if (raw.kind === "data-check")
67
+ return dataCheckOf(raw);
68
+ if (raw.kind === "coverage") {
69
+ const command = text(raw.command, COMMAND_MAX)?.trim();
70
+ if (!command)
71
+ return "a coverage verifier needs a command";
72
+ const min = raw.minPercent;
73
+ if (typeof min !== "number" || !Number.isFinite(min) || min < 0 || min > 100)
74
+ return "a coverage verifier needs minPercent between 0 and 100";
75
+ return { kind: "coverage", command, minPercent: min };
76
+ }
77
+ if (raw.kind === "plugin") {
78
+ const verifierId = typeof raw.verifierId === "string" ? raw.verifierId : "";
79
+ if (!PLUGIN_VERIFIER_ID.test(verifierId))
80
+ return "a plugin verifier needs a verifierId like pluginId:verifier";
81
+ if (raw.input === undefined)
82
+ return { kind: "plugin", verifierId };
83
+ let size = Number.POSITIVE_INFINITY;
84
+ try {
85
+ size = JSON.stringify(raw.input)?.length ?? Number.POSITIVE_INFINITY;
86
+ }
87
+ catch {
88
+ /* Not serializable: refused below. */
89
+ }
90
+ if (size > PLUGIN_INPUT_MAX)
91
+ return "a plugin verifier input must be short JSON";
92
+ return { kind: "plugin", verifierId, input: raw.input };
93
+ }
94
+ return "that verifier kind cannot be proposed (use command, file, data-check, coverage, plugin or manual)";
95
+ }
96
+ function dataCheckOf(raw) {
97
+ const datasetId = text(raw.datasetId, DATASET_ID_MAX)?.trim();
98
+ if (!datasetId)
99
+ return "a data-check verifier needs a datasetId";
100
+ const check = typeof raw.check === "string" ? raw.check : "";
101
+ if (!DATA_CHECKS.has(check))
102
+ return "check must be row_count, sum or equals";
103
+ const expected = raw.expected;
104
+ const expectedOk = (typeof expected === "number" && Number.isFinite(expected)) ||
105
+ (typeof expected === "string" && expected.length > 0 && expected.length <= EXPECTED_MAX);
106
+ if (!expectedOk)
107
+ return "a data-check verifier needs an expected number or short string";
108
+ const column = raw.column === undefined ? undefined : text(raw.column, IDENTIFIER_MAX);
109
+ if (raw.column !== undefined && !column)
110
+ return "column must be a short string";
111
+ if (check !== "row_count" && !column)
112
+ return `a ${check} check needs a column`;
113
+ const table = raw.table === undefined ? undefined : text(raw.table, IDENTIFIER_MAX);
114
+ if (raw.table !== undefined && !table)
115
+ return "table must be a short string";
116
+ return {
117
+ kind: "data-check",
118
+ datasetId,
119
+ check: check,
120
+ ...(column ? { column } : {}),
121
+ expected: expected,
122
+ ...(table ? { table } : {}),
123
+ };
124
+ }
125
+ const isAbsoluteLike = (path) => path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:/.test(path);
126
+ const hasParentSegment = (path) => path.split(/[\\/]/).includes("..");
127
+ function scopeOf(raw) {
128
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > SCOPE_ENTRIES_MAX)
129
+ return "scope must be a short list of globs";
130
+ const out = [];
131
+ for (const entry of raw) {
132
+ const glob = text(entry, SCOPE_ENTRY_MAX)?.trim();
133
+ if (!glob)
134
+ return "every scope entry must be a short string";
135
+ if (isAbsoluteLike(glob) || hasParentSegment(glob))
136
+ return "scope entries must stay inside the workspace";
137
+ out.push(glob);
138
+ }
139
+ return out;
140
+ }
141
+ /** Turns the model's `acceptanceCriteria` input into drafts, reporting what it could not keep. */
142
+ export function validateCriteriaDrafts(raw) {
143
+ const accepted = [];
144
+ const dropped = [];
145
+ if (!Array.isArray(raw)) {
146
+ return { accepted, dropped: [{ index: 0, reason: "acceptanceCriteria must be an array" }] };
147
+ }
148
+ raw.forEach((entry, index) => {
149
+ if (accepted.length >= CRITERIA_MAX) {
150
+ dropped.push({ index, reason: `more than ${CRITERIA_MAX} criteria were proposed` });
151
+ return;
152
+ }
153
+ if (!isRecord(entry)) {
154
+ dropped.push({ index, reason: "a criterion must be an object" });
155
+ return;
156
+ }
157
+ const statement = typeof entry.text === "string" ? entry.text.replace(/\s+/g, " ").trim() : "";
158
+ if (!statement) {
159
+ dropped.push({ index, reason: "a criterion needs text" });
160
+ return;
161
+ }
162
+ let verifier;
163
+ if (entry.verifier !== undefined) {
164
+ const checked = verifierOf(entry.verifier);
165
+ if (typeof checked === "string") {
166
+ dropped.push({ index, reason: checked });
167
+ return;
168
+ }
169
+ verifier = checked;
170
+ }
171
+ let scope;
172
+ if (entry.scope !== undefined) {
173
+ const checked = scopeOf(entry.scope);
174
+ if (typeof checked === "string") {
175
+ dropped.push({ index, reason: checked });
176
+ return;
177
+ }
178
+ scope = checked;
179
+ }
180
+ accepted.push({
181
+ text: statement.length > CRITERION_TEXT_MAX ? statement.slice(0, CRITERION_TEXT_MAX) : statement,
182
+ required: entry.required === true,
183
+ ...(verifier ? { verifier } : {}),
184
+ ...(scope ? { scope } : {}),
185
+ origin: "inferred",
186
+ });
187
+ });
188
+ return { accepted, dropped };
189
+ }
190
+ /**
191
+ * Applies the user's per-draft decisions (`plan:criteria`) to the drafts of the plan they approved.
192
+ * `undefined` selection keeps every draft (the plain "approve"). Anything malformed, any index
193
+ * outside the list or an empty edit returns `undefined` for "nothing is activated": a selection
194
+ * the user did not make cleanly never activates a criterion (fail closed).
195
+ */
196
+ export function applyCriteriaSelection(drafts, raw) {
197
+ if (raw === undefined || raw === "")
198
+ return { kept: [...drafts], rejected: 0 };
199
+ let selection;
200
+ try {
201
+ selection = typeof raw === "string" ? JSON.parse(raw) : undefined;
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ if (!isRecord(selection))
207
+ return undefined;
208
+ const index = (key) => {
209
+ const n = typeof key === "string" ? Number(key) : key;
210
+ return Number.isInteger(n) && n >= 0 && n < drafts.length ? n : undefined;
211
+ };
212
+ const rejected = new Set();
213
+ if (selection.reject !== undefined) {
214
+ if (!Array.isArray(selection.reject))
215
+ return undefined;
216
+ for (const entry of selection.reject) {
217
+ const i = index(entry);
218
+ if (i === undefined)
219
+ return undefined;
220
+ rejected.add(i);
221
+ }
222
+ }
223
+ const edits = new Map();
224
+ if (selection.edit !== undefined) {
225
+ if (!isRecord(selection.edit))
226
+ return undefined;
227
+ for (const [key, value] of Object.entries(selection.edit)) {
228
+ const i = index(key);
229
+ const statement = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
230
+ if (i === undefined || !statement)
231
+ return undefined;
232
+ edits.set(i, statement.slice(0, CRITERION_TEXT_MAX));
233
+ }
234
+ }
235
+ const required = new Map();
236
+ if (selection.required !== undefined) {
237
+ if (!isRecord(selection.required))
238
+ return undefined;
239
+ for (const [key, value] of Object.entries(selection.required)) {
240
+ const i = index(key);
241
+ if (i === undefined || typeof value !== "boolean")
242
+ return undefined;
243
+ required.set(i, value);
244
+ }
245
+ }
246
+ const kept = drafts.flatMap((draft, i) => rejected.has(i)
247
+ ? []
248
+ : [
249
+ {
250
+ ...draft,
251
+ text: edits.get(i) ?? draft.text,
252
+ required: required.get(i) ?? draft.required,
253
+ },
254
+ ]);
255
+ return { kept, rejected: rejected.size };
256
+ }
@@ -1,5 +1,6 @@
1
1
  import type { AskQuestionsRequest, AskQuestionsResult } from "@alisio/sdk";
2
2
  import type { ToolRegistry } from "../core/registry.ts";
3
+ import type { NewCriterion } from "../proof/types.ts";
3
4
  import { type PlanStore } from "./state.ts";
4
5
  export interface ExitPlanDeps {
5
6
  store: PlanStore;
@@ -14,5 +15,12 @@ export interface ExitPlanDeps {
14
15
  enabled: boolean;
15
16
  max: number;
16
17
  };
18
+ /**
19
+ * Where approved acceptance criteria go (`ProofStore` fits). Without it the `acceptanceCriteria`
20
+ * field is not offered and is ignored: a core without Proof plans exactly as before.
21
+ */
22
+ proof?: {
23
+ addCriterion(input: NewCriterion): unknown;
24
+ };
17
25
  }
18
26
  export declare function registerExitPlan(registry: ToolRegistry, deps: ExitPlanDeps): void;