specrails-core 6.0.0 → 6.1.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 (173) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-runtime/capabilities.js +3 -1
  3. package/dist/agent-runtime/change-scope.d.ts +63 -0
  4. package/dist/agent-runtime/change-scope.js +297 -0
  5. package/dist/agent-runtime/cli-executor.d.ts +3 -0
  6. package/dist/agent-runtime/cli-executor.js +50 -13
  7. package/dist/agent-runtime/cli-process.js +6 -2
  8. package/dist/agent-runtime/cli.d.ts +15 -0
  9. package/dist/agent-runtime/cli.js +43 -9
  10. package/dist/agent-runtime/compact/chat-client.js +2 -1
  11. package/dist/agent-runtime/compact/environment.d.ts +12 -0
  12. package/dist/agent-runtime/compact/environment.js +31 -1
  13. package/dist/agent-runtime/compact/test-reachability.js +6 -2
  14. package/dist/agent-runtime/config.d.ts +4 -1
  15. package/dist/agent-runtime/config.js +41 -3
  16. package/dist/agent-runtime/core-host.d.ts +10 -4
  17. package/dist/agent-runtime/core-host.js +22 -19
  18. package/dist/agent-runtime/efficiency-summary.d.ts +4 -1
  19. package/dist/agent-runtime/efficiency-summary.js +30 -27
  20. package/dist/agent-runtime/efficiency-types.d.ts +4 -4
  21. package/dist/agent-runtime/efficiency.d.ts +1 -1
  22. package/dist/agent-runtime/efficiency.js +7 -6
  23. package/dist/agent-runtime/engine/__fixtures__/implementation-component.json +90 -0
  24. package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.d.ts +11 -0
  25. package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.js +60 -0
  26. package/dist/agent-runtime/engine/__fixtures__/implementation.json +43 -0
  27. package/dist/agent-runtime/engine/budget.d.ts +9 -0
  28. package/dist/agent-runtime/engine/budget.js +33 -0
  29. package/dist/agent-runtime/engine/canonical-json.d.ts +9 -0
  30. package/dist/agent-runtime/engine/canonical-json.js +162 -0
  31. package/dist/agent-runtime/engine/checkpoint/database.d.ts +67 -0
  32. package/dist/agent-runtime/engine/checkpoint/database.js +304 -0
  33. package/dist/agent-runtime/engine/checkpoint/lease.d.ts +17 -0
  34. package/dist/agent-runtime/engine/checkpoint/lease.js +82 -0
  35. package/dist/agent-runtime/engine/checkpoint/ledger.d.ts +133 -0
  36. package/dist/agent-runtime/engine/checkpoint/ledger.js +510 -0
  37. package/dist/agent-runtime/engine/checkpoint/saver.d.ts +25 -0
  38. package/dist/agent-runtime/engine/checkpoint/saver.js +146 -0
  39. package/dist/agent-runtime/engine/cli.d.ts +10 -0
  40. package/dist/agent-runtime/engine/cli.js +172 -0
  41. package/dist/agent-runtime/engine/compiler.d.ts +23 -0
  42. package/dist/agent-runtime/engine/compiler.js +261 -0
  43. package/dist/agent-runtime/engine/composition.d.ts +16 -0
  44. package/dist/agent-runtime/engine/composition.js +28 -0
  45. package/dist/agent-runtime/engine/concurrency.d.ts +14 -0
  46. package/dist/agent-runtime/engine/concurrency.js +55 -0
  47. package/dist/agent-runtime/engine/contracts.d.ts +273 -0
  48. package/dist/agent-runtime/engine/contracts.js +11 -0
  49. package/dist/agent-runtime/engine/definition-schema.d.ts +250 -0
  50. package/dist/agent-runtime/engine/definition-schema.js +313 -0
  51. package/dist/agent-runtime/engine/definition-types.d.ts +84 -0
  52. package/dist/agent-runtime/engine/definition-types.js +3 -0
  53. package/dist/agent-runtime/engine/definition-validator.d.ts +7 -0
  54. package/dist/agent-runtime/engine/definition-validator.js +187 -0
  55. package/dist/agent-runtime/engine/efficiency-summary.d.ts +62 -0
  56. package/dist/agent-runtime/engine/efficiency-summary.js +31 -0
  57. package/dist/agent-runtime/engine/events.d.ts +19 -0
  58. package/dist/agent-runtime/engine/events.js +88 -0
  59. package/dist/agent-runtime/engine/execution.d.ts +29 -0
  60. package/dist/agent-runtime/engine/execution.js +121 -0
  61. package/dist/agent-runtime/engine/expressions.d.ts +11 -0
  62. package/dist/agent-runtime/engine/expressions.js +172 -0
  63. package/dist/agent-runtime/engine/fork.d.ts +28 -0
  64. package/dist/agent-runtime/engine/fork.js +211 -0
  65. package/dist/agent-runtime/engine/graph-description.d.ts +48 -0
  66. package/dist/agent-runtime/engine/graph-description.js +16 -0
  67. package/dist/agent-runtime/engine/implementation-compiler.d.ts +214 -0
  68. package/dist/agent-runtime/engine/implementation-compiler.js +91 -0
  69. package/dist/agent-runtime/engine/index.d.ts +14 -0
  70. package/dist/agent-runtime/engine/index.js +14 -0
  71. package/dist/agent-runtime/engine/invocation-context.d.ts +11 -0
  72. package/dist/agent-runtime/engine/invocation-context.js +128 -0
  73. package/dist/agent-runtime/engine/otel.d.ts +15 -0
  74. package/dist/agent-runtime/engine/otel.js +132 -0
  75. package/dist/agent-runtime/engine/output.d.ts +4 -0
  76. package/dist/agent-runtime/engine/output.js +73 -0
  77. package/dist/agent-runtime/engine/piece-dependencies.d.ts +50 -0
  78. package/dist/agent-runtime/engine/piece-dependencies.js +193 -0
  79. package/dist/agent-runtime/engine/piece-registry.d.ts +19 -0
  80. package/dist/agent-runtime/engine/piece-registry.js +62 -0
  81. package/dist/agent-runtime/engine/pieces/assign.d.ts +4 -0
  82. package/dist/agent-runtime/engine/pieces/assign.js +35 -0
  83. package/dist/agent-runtime/engine/pieces/control.d.ts +5 -0
  84. package/dist/agent-runtime/engine/pieces/control.js +49 -0
  85. package/dist/agent-runtime/engine/pieces/decider.d.ts +5 -0
  86. package/dist/agent-runtime/engine/pieces/decider.js +70 -0
  87. package/dist/agent-runtime/engine/pieces/implementation-binding.d.ts +12 -0
  88. package/dist/agent-runtime/engine/pieces/implementation-binding.js +40 -0
  89. package/dist/agent-runtime/engine/pieces/implementation-journal.d.ts +25 -0
  90. package/dist/agent-runtime/engine/pieces/implementation-journal.js +192 -0
  91. package/dist/agent-runtime/engine/pieces/implementation.d.ts +23 -0
  92. package/dist/agent-runtime/engine/pieces/implementation.js +176 -0
  93. package/dist/agent-runtime/engine/pieces/index.d.ts +7 -0
  94. package/dist/agent-runtime/engine/pieces/index.js +22 -0
  95. package/dist/agent-runtime/engine/pieces/openspec.d.ts +3 -0
  96. package/dist/agent-runtime/engine/pieces/openspec.js +69 -0
  97. package/dist/agent-runtime/engine/pieces/ports.d.ts +48 -0
  98. package/dist/agent-runtime/engine/pieces/ports.js +1 -0
  99. package/dist/agent-runtime/engine/pieces/project-memory.d.ts +3 -0
  100. package/dist/agent-runtime/engine/pieces/project-memory.js +13 -0
  101. package/dist/agent-runtime/engine/pieces/prompt.d.ts +5 -0
  102. package/dist/agent-runtime/engine/pieces/prompt.js +130 -0
  103. package/dist/agent-runtime/engine/pieces/role-turn.d.ts +7 -0
  104. package/dist/agent-runtime/engine/pieces/role-turn.js +73 -0
  105. package/dist/agent-runtime/engine/pieces/shared.d.ts +67 -0
  106. package/dist/agent-runtime/engine/pieces/shared.js +40 -0
  107. package/dist/agent-runtime/engine/pieces/shell.d.ts +6 -0
  108. package/dist/agent-runtime/engine/pieces/shell.js +47 -0
  109. package/dist/agent-runtime/engine/pieces/verify.d.ts +7 -0
  110. package/dist/agent-runtime/engine/pieces/verify.js +52 -0
  111. package/dist/agent-runtime/engine/preflight.d.ts +27 -0
  112. package/dist/agent-runtime/engine/preflight.js +54 -0
  113. package/dist/agent-runtime/engine/regex.d.ts +3 -0
  114. package/dist/agent-runtime/engine/regex.js +19 -0
  115. package/dist/agent-runtime/engine/run-status.d.ts +563 -0
  116. package/dist/agent-runtime/engine/run-status.js +61 -0
  117. package/dist/agent-runtime/engine/runs.d.ts +605 -0
  118. package/dist/agent-runtime/engine/runs.js +247 -0
  119. package/dist/agent-runtime/engine/state.d.ts +64 -0
  120. package/dist/agent-runtime/engine/state.js +98 -0
  121. package/dist/agent-runtime/engine/steering/inbox.d.ts +54 -0
  122. package/dist/agent-runtime/engine/steering/inbox.js +113 -0
  123. package/dist/agent-runtime/engine/storage/fork-allocation.d.ts +21 -0
  124. package/dist/agent-runtime/engine/storage/fork-allocation.js +143 -0
  125. package/dist/agent-runtime/engine/storage/private-path.d.ts +11 -0
  126. package/dist/agent-runtime/engine/storage/private-path.js +74 -0
  127. package/dist/agent-runtime/engine/store/sqlite-store.d.ts +26 -0
  128. package/dist/agent-runtime/engine/store/sqlite-store.js +227 -0
  129. package/dist/agent-runtime/evaluation-corpus.d.ts +125 -0
  130. package/dist/agent-runtime/evaluation-corpus.js +4 -0
  131. package/dist/agent-runtime/evaluation.d.ts +9 -3
  132. package/dist/agent-runtime/evaluation.js +50 -8
  133. package/dist/agent-runtime/executor-types.d.ts +53 -3
  134. package/dist/agent-runtime/executor-types.js +35 -2
  135. package/dist/agent-runtime/executors.js +2 -2
  136. package/dist/agent-runtime/graph/artifacts.d.ts +7 -0
  137. package/dist/agent-runtime/graph/artifacts.js +86 -10
  138. package/dist/agent-runtime/graph/convergence.d.ts +26 -0
  139. package/dist/agent-runtime/graph/convergence.js +62 -0
  140. package/dist/agent-runtime/graph/nodes.d.ts +2 -0
  141. package/dist/agent-runtime/graph/nodes.js +171 -24
  142. package/dist/agent-runtime/graph/roles.d.ts +6 -0
  143. package/dist/agent-runtime/graph/roles.js +23 -17
  144. package/dist/agent-runtime/graph/state.d.ts +5 -0
  145. package/dist/agent-runtime/graph/state.js +2 -0
  146. package/dist/agent-runtime/kimi-acp.js +6 -6
  147. package/dist/agent-runtime/openai-executor.js +17 -12
  148. package/dist/agent-runtime/openspec.d.ts +17 -2
  149. package/dist/agent-runtime/openspec.js +54 -20
  150. package/dist/agent-runtime/prompts.d.ts +11 -5
  151. package/dist/agent-runtime/prompts.js +71 -20
  152. package/dist/agent-runtime/recovery.d.ts +1 -1
  153. package/dist/agent-runtime/repository-context.js +20 -1
  154. package/dist/agent-runtime/role-routing.js +4 -2
  155. package/dist/agent-runtime/role-state.d.ts +5 -0
  156. package/dist/agent-runtime/verification-plan.d.ts +2 -0
  157. package/dist/agent-runtime/verification-plan.js +18 -3
  158. package/dist/agent-runtime/workflow-types.d.ts +2 -0
  159. package/dist/agent-runtime/workflow.d.ts +3 -1
  160. package/dist/agent-runtime/workflow.js +7 -3
  161. package/dist/agent-runtime/workspace-tools.d.ts +2 -2
  162. package/dist/agent-runtime/workspace-tools.js +6 -6
  163. package/dist/installer/cli.js +5 -0
  164. package/dist/installer/phases/scaffold.d.ts +2 -2
  165. package/dist/installer/phases/scaffold.js +3 -7
  166. package/dist/pipeline/pipeline-state.d.ts +49 -4
  167. package/dist/pipeline/pipeline-state.js +150 -23
  168. package/integration-contract.json +84 -12
  169. package/package.json +12 -5
  170. package/schemas/agent-runtime.schema.json +93 -1
  171. package/schemas/workflow-definition.schema.json +312 -0
  172. package/templates/kimi/specrails/run-skill.mjs +15 -1
  173. package/templates/commands/specrails/batch-implement.md +0 -32
@@ -0,0 +1,304 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { EngineError } from '../contracts.js';
4
+ import { PersistenceError, privateSqlitePath } from '../storage/private-path.js';
5
+ export { PersistenceError, ensurePrivateDirectory as ensurePrivateRunDirectory } from '../storage/private-path.js';
6
+ /** Every identifier interpolated into SQL comes from this closed adapter-owned map. */
7
+ const keys = {
8
+ runs: ['run_id'], visits: ['visit_id'], steps: ['run_id', 'node_path', 'scope_id'], attempts: ['attempt_id'],
9
+ invocations: ['invocation_id'], receipts: ['receipt_id'], budget: ['run_id'], interrupts: ['interrupt_id'],
10
+ control_inbox: ['request_id'], role_sessions: ['session_key'], piece_state: ['run_id', 'scope_id', 'node_path', 'key'], reservations: ['reservation_id'],
11
+ events: ['run_id', 'sequence'], checkpoints: ['thread_id', 'checkpoint_ns', 'checkpoint_id'],
12
+ writes: ['thread_id', 'checkpoint_ns', 'checkpoint_id', 'task_id', 'idx'],
13
+ };
14
+ const schema = `
15
+ CREATE TABLE runs (
16
+ run_id TEXT PRIMARY KEY, singleton INTEGER NOT NULL DEFAULT 1 UNIQUE CHECK(singleton=1),
17
+ workflow_id TEXT NOT NULL, definition_hash TEXT NOT NULL, definition_json TEXT NOT NULL, request_json TEXT NOT NULL,
18
+ source TEXT NOT NULL CHECK(source IN ('definition','builtin')), engine_version INTEGER NOT NULL,
19
+ runtime_identity_json TEXT NOT NULL, status TEXT NOT NULL, fork_of TEXT, completion_json TEXT,
20
+ checkpoint_thread_id TEXT NOT NULL, head_checkpoint_json TEXT, current_revision INTEGER NOT NULL DEFAULT 0,
21
+ transitions INTEGER NOT NULL DEFAULT 0, candidate_revision INTEGER NOT NULL DEFAULT 0,
22
+ candidate_json TEXT, verified_json TEXT, lease_epoch INTEGER NOT NULL DEFAULT 0,
23
+ next_event_sequence INTEGER NOT NULL DEFAULT 1, consecutive_failures INTEGER NOT NULL DEFAULT 0,
24
+ active_duration_ms REAL NOT NULL DEFAULT 0, active_started_at TEXT,
25
+ created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
26
+ CREATE TABLE visits (
27
+ visit_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), node_path TEXT NOT NULL,
28
+ scope_id TEXT NOT NULL, branch_id TEXT, kind TEXT NOT NULL, effect TEXT NOT NULL CHECK(effect IN ('read','write')),
29
+ requires_ai INTEGER NOT NULL, local_visit INTEGER NOT NULL, global_transition INTEGER NOT NULL,
30
+ thread_id TEXT NOT NULL, checkpoint_ns TEXT NOT NULL, checkpoint_id TEXT NOT NULL, task_id TEXT NOT NULL, saver_checkpoint_ns TEXT,
31
+ before_revision INTEGER NOT NULL, created_at TEXT NOT NULL,
32
+ UNIQUE(thread_id,checkpoint_ns,checkpoint_id,task_id), UNIQUE(run_id,node_path,scope_id,local_visit));
33
+ CREATE TABLE steps (
34
+ run_id TEXT NOT NULL REFERENCES runs(run_id), node_path TEXT NOT NULL, scope_id TEXT NOT NULL, branch_id TEXT,
35
+ kind TEXT NOT NULL, visits INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL, last_attempt_id TEXT, updated_at TEXT NOT NULL,
36
+ PRIMARY KEY(run_id,node_path,scope_id));
37
+ CREATE TABLE attempts (
38
+ attempt_id TEXT PRIMARY KEY, visit_id TEXT NOT NULL REFERENCES visits(visit_id), run_id TEXT NOT NULL REFERENCES runs(run_id),
39
+ node_path TEXT NOT NULL, scope_id TEXT NOT NULL, visit INTEGER NOT NULL, attempt INTEGER NOT NULL, branch TEXT,
40
+ status TEXT NOT NULL, lease_epoch INTEGER NOT NULL, frame_json TEXT NOT NULL, started_at TEXT NOT NULL, ended_at TEXT,
41
+ error_code TEXT, error_message TEXT, outcome TEXT, output_json TEXT, usage_json TEXT, terminal_digest TEXT,
42
+ recovery_authorized INTEGER NOT NULL DEFAULT 0, interruption_json TEXT, inherited INTEGER NOT NULL DEFAULT 0,
43
+ UNIQUE(visit_id,attempt));
44
+ CREATE TABLE invocations (
45
+ invocation_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), attempt_id TEXT NOT NULL REFERENCES attempts(attempt_id),
46
+ node_path TEXT NOT NULL, role TEXT, provider TEXT NOT NULL, model TEXT, kind TEXT, status TEXT NOT NULL,
47
+ started_at TEXT NOT NULL, ended_at TEXT, duration_ms REAL, tool_calls INTEGER, usage_json TEXT NOT NULL,
48
+ prompt_bytes INTEGER, context_bytes INTEGER, result_json TEXT, inherited INTEGER NOT NULL DEFAULT 0, ordinal INTEGER NOT NULL,
49
+ UNIQUE(attempt_id,ordinal));
50
+ CREATE TABLE receipts (
51
+ receipt_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), node_path TEXT NOT NULL,
52
+ attempt_id TEXT NOT NULL REFERENCES attempts(attempt_id), kind TEXT NOT NULL, valid INTEGER NOT NULL,
53
+ candidate_hash TEXT NOT NULL, candidate_revision INTEGER NOT NULL, receipt_json TEXT NOT NULL, created_at TEXT NOT NULL);
54
+ CREATE TABLE budget (
55
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id), max_cost_usd REAL, max_tokens INTEGER, max_duration_ms REAL,
56
+ known_cost_usd REAL NOT NULL DEFAULT 0, known_tokens INTEGER NOT NULL DEFAULT 0, duration_ms REAL NOT NULL DEFAULT 0,
57
+ input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0,
58
+ cost_unknown INTEGER NOT NULL DEFAULT 0, input_unknown INTEGER NOT NULL DEFAULT 0, output_unknown INTEGER NOT NULL DEFAULT 0);
59
+ CREATE TABLE interrupts (
60
+ interrupt_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), visit_id TEXT NOT NULL REFERENCES visits(visit_id),
61
+ attempt_id TEXT NOT NULL REFERENCES attempts(attempt_id), node_path TEXT NOT NULL, scope_id TEXT NOT NULL, branch_id TEXT,
62
+ kind TEXT NOT NULL, requested_at TEXT NOT NULL, payload_json TEXT NOT NULL, answered_at TEXT, answer_json TEXT);
63
+ CREATE TABLE control_inbox (
64
+ request_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), kind TEXT NOT NULL CHECK(kind IN ('cancel','steer')),
65
+ payload_json TEXT NOT NULL, created_at TEXT NOT NULL, received_sequence INTEGER NOT NULL DEFAULT 0, consumed_by_attempt_id TEXT, consumed_at TEXT);
66
+ CREATE TABLE role_sessions (
67
+ session_key TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), scope_id TEXT NOT NULL, node_path TEXT NOT NULL,
68
+ role TEXT NOT NULL, identity TEXT NOT NULL, state_json TEXT NOT NULL, updated_at TEXT NOT NULL);
69
+ CREATE TABLE reservations (
70
+ reservation_id TEXT PRIMARY KEY, run_id TEXT NOT NULL REFERENCES runs(run_id), attempt_id TEXT NOT NULL REFERENCES attempts(attempt_id),
71
+ max_tokens INTEGER, max_cost_usd REAL, created_at TEXT NOT NULL);
72
+ CREATE TABLE piece_state (
73
+ run_id TEXT NOT NULL REFERENCES runs(run_id), scope_id TEXT NOT NULL, node_path TEXT NOT NULL, key TEXT NOT NULL,
74
+ visit_id TEXT REFERENCES visits(visit_id), value_json TEXT NOT NULL, updated_at TEXT NOT NULL,
75
+ PRIMARY KEY(run_id,scope_id,node_path,key));
76
+ CREATE TABLE lease (
77
+ run_id TEXT PRIMARY KEY REFERENCES runs(run_id), owner TEXT NOT NULL, epoch INTEGER NOT NULL,
78
+ acquired_at TEXT NOT NULL, heartbeat_at TEXT NOT NULL, expires_at TEXT NOT NULL);
79
+ CREATE TABLE events (
80
+ run_id TEXT NOT NULL REFERENCES runs(run_id), sequence INTEGER NOT NULL, event_id TEXT NOT NULL UNIQUE,
81
+ type TEXT NOT NULL, at TEXT NOT NULL, payload_json TEXT NOT NULL, inherited INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(run_id,sequence));
82
+ CREATE TABLE checkpoints (
83
+ thread_id TEXT NOT NULL, checkpoint_ns TEXT NOT NULL, checkpoint_id TEXT NOT NULL, parent_checkpoint_id TEXT,
84
+ type TEXT NOT NULL, checkpoint BLOB NOT NULL, metadata BLOB NOT NULL, metadata_type TEXT NOT NULL,
85
+ PRIMARY KEY(thread_id,checkpoint_ns,checkpoint_id));
86
+ CREATE TABLE writes (
87
+ thread_id TEXT NOT NULL, checkpoint_ns TEXT NOT NULL, checkpoint_id TEXT NOT NULL, task_id TEXT NOT NULL,
88
+ idx INTEGER NOT NULL, channel TEXT NOT NULL, type TEXT NOT NULL, value BLOB NOT NULL,
89
+ PRIMARY KEY(thread_id,checkpoint_ns,checkpoint_id,task_id,idx));
90
+ CREATE TABLE durable_revisions (revision INTEGER PRIMARY KEY AUTOINCREMENT, reason TEXT NOT NULL, at TEXT NOT NULL);
91
+ CREATE TABLE row_versions (
92
+ table_name TEXT NOT NULL, row_key TEXT NOT NULL, revision INTEGER NOT NULL REFERENCES durable_revisions(revision),
93
+ row_json TEXT, PRIMARY KEY(table_name,row_key,revision));
94
+ CREATE INDEX visits_run ON visits(run_id,node_path,scope_id,local_visit);
95
+ CREATE INDEX attempts_visit ON attempts(visit_id,attempt);
96
+ CREATE INDEX events_cursor ON events(run_id,sequence);
97
+ CREATE INDEX revisions_cut ON row_versions(revision,table_name);
98
+ PRAGMA user_version=1;
99
+ `;
100
+ function encodeRow(row) {
101
+ return JSON.stringify(Object.fromEntries(Object.entries(row).map(([key, value]) => [key,
102
+ ArrayBuffer.isView(value) ? { $sqliteBlob: Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('base64') } :
103
+ typeof value === 'bigint' ? { $sqliteInteger: value.toString() } : value])));
104
+ }
105
+ function decodeRow(json) {
106
+ const row = JSON.parse(json);
107
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, value && typeof value === 'object' && '$sqliteBlob' in value
108
+ ? Buffer.from(value.$sqliteBlob, 'base64') : value && typeof value === 'object' && '$sqliteInteger' in value ? BigInt(value.$sqliteInteger) : value]));
109
+ }
110
+ /** Short synchronous transactions own graph writes and ledger evidence together. */
111
+ export class RunDatabase {
112
+ filename;
113
+ sqlite;
114
+ activeRevision;
115
+ inTransaction = false;
116
+ closed = false;
117
+ constructor(filename, sqlite) {
118
+ this.filename = filename;
119
+ this.sqlite = sqlite;
120
+ }
121
+ static async open(filename, options = {}) {
122
+ const target = await privateSqlitePath(filename, options);
123
+ const sqlite = new DatabaseSync(target, { readOnly: options.readOnly ?? false });
124
+ try {
125
+ sqlite.exec('PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;');
126
+ if (!options.readOnly)
127
+ sqlite.exec('PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;');
128
+ const version = Number(sqlite.prepare('PRAGMA user_version').get()?.user_version);
129
+ if (version === 0 && options.create) {
130
+ sqlite.exec('BEGIN IMMEDIATE');
131
+ try {
132
+ sqlite.exec(schema);
133
+ sqlite.exec('COMMIT');
134
+ }
135
+ catch (error) {
136
+ sqlite.exec('ROLLBACK');
137
+ throw error;
138
+ }
139
+ }
140
+ else if (version !== 1)
141
+ throw new PersistenceError('resume_incompatible', `Unsupported run database schema ${version}`);
142
+ return new RunDatabase(target, sqlite);
143
+ }
144
+ catch (error) {
145
+ sqlite.close();
146
+ throw error;
147
+ }
148
+ }
149
+ get revision() { return Number(this.sqlite.prepare('SELECT COALESCE(MAX(revision),0) AS value FROM durable_revisions').get().value); }
150
+ get transactionRevision() {
151
+ if (this.activeRevision === undefined)
152
+ throw new PersistenceError('internal', 'A revision transaction is required');
153
+ return this.activeRevision;
154
+ }
155
+ /** Undefined reason is reserved for leases/read reservations, which do not change graph history. */
156
+ transaction(reason, operation) {
157
+ if (this.inTransaction)
158
+ throw new PersistenceError('internal', 'Nested database transactions are not supported');
159
+ this.sqlite.exec('BEGIN IMMEDIATE');
160
+ this.inTransaction = true;
161
+ try {
162
+ if (reason !== undefined)
163
+ this.activeRevision = Number(this.sqlite.prepare('INSERT INTO durable_revisions(reason,at) VALUES (?,?)').run(reason, new Date().toISOString()).lastInsertRowid);
164
+ const result = operation();
165
+ if (result && typeof result.then === 'function')
166
+ throw new PersistenceError('internal', 'Database transactions cannot await effects');
167
+ this.sqlite.exec('COMMIT');
168
+ return result;
169
+ }
170
+ catch (error) {
171
+ this.sqlite.exec('ROLLBACK');
172
+ throw error;
173
+ }
174
+ finally {
175
+ this.activeRevision = undefined;
176
+ this.inTransaction = false;
177
+ }
178
+ }
179
+ get(table, key) {
180
+ const primary = keys[table];
181
+ return this.sqlite.prepare(`SELECT * FROM ${table} WHERE ${primary.map(name => `${name}=?`).join(' AND ')}`).get(...primary.map(name => key[name]));
182
+ }
183
+ put(table, row) {
184
+ const revision = this.transactionRevision, primary = keys[table], columns = Object.keys(row);
185
+ const allowed = new Set(this.sqlite.prepare(`PRAGMA table_info(${table})`).all().map(info => String(info.name)));
186
+ if (columns.some(column => !allowed.has(column)) || primary.some(column => row[column] === undefined))
187
+ throw new PersistenceError('internal', `Invalid ${table} row`);
188
+ const update = columns.filter(column => !primary.includes(column));
189
+ this.sqlite.prepare(`INSERT INTO ${table} (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')}) ON CONFLICT(${primary.join(',')}) DO ${update.length ? `UPDATE SET ${update.map(column => `${column}=excluded.${column}`).join(',')}` : 'NOTHING'}`).run(...columns.map(column => row[column]));
190
+ const stored = this.get(table, row);
191
+ this.sqlite.prepare('INSERT OR REPLACE INTO row_versions(table_name,row_key,revision,row_json) VALUES (?,?,?,?)').run(table, JSON.stringify(primary.map(key => stored[key])), revision, encodeRow(stored));
192
+ }
193
+ delete(table, key) {
194
+ const revision = this.transactionRevision, primary = keys[table];
195
+ this.sqlite.prepare(`DELETE FROM ${table} WHERE ${primary.map(name => `${name}=?`).join(' AND ')}`).run(...primary.map(name => key[name]));
196
+ this.sqlite.prepare('INSERT OR REPLACE INTO row_versions(table_name,row_key,revision,row_json) VALUES (?,?,?,NULL)').run(table, JSON.stringify(primary.map(name => key[name])), revision);
197
+ }
198
+ /** Immutable cut includes overwritten pending writes and all child namespaces. Caller holds an inactive-source reservation. */
199
+ rowsAt(revision) {
200
+ if (!this.inTransaction)
201
+ throw new PersistenceError('internal', 'Historical reads require a source transaction');
202
+ if (!Number.isSafeInteger(revision) || revision < 1 || revision > this.revision)
203
+ throw new PersistenceError('invalid_arguments', 'Invalid historical revision');
204
+ const rows = this.sqlite.prepare(`SELECT v.table_name,v.row_json FROM row_versions v JOIN (
205
+ SELECT table_name,row_key,MAX(revision) revision FROM row_versions WHERE revision<=? GROUP BY table_name,row_key
206
+ ) cut ON v.table_name=cut.table_name AND v.row_key=cut.row_key AND v.revision=cut.revision WHERE v.row_json IS NOT NULL`).all(revision);
207
+ const order = Object.keys(keys);
208
+ return rows.map(row => ({ table: String(row.table_name), row: decodeRow(String(row.row_json)) }))
209
+ .sort((a, b) => order.indexOf(a.table) - order.indexOf(b.table));
210
+ }
211
+ /**
212
+ * Copies a complete historical cut into an empty, separately staged database.
213
+ * The source writer reservation prevents takeover/steering while rows are read;
214
+ * serialized graph blobs, opaque thread IDs and child namespaces stay unchanged.
215
+ */
216
+ forkAt(destination, options) {
217
+ if (destination.filename === this.filename || destination.sqlite.prepare('SELECT run_id FROM runs').get())
218
+ throw new EngineError('run_exists', 'Fork destination must be an empty separate database');
219
+ if (!options.runId)
220
+ throw new EngineError('invalid_arguments', 'A fork needs a new run ID');
221
+ return this.transaction(undefined, () => {
222
+ const source = this.sqlite.prepare('SELECT * FROM runs').get();
223
+ if (!source)
224
+ throw new EngineError('run_not_found', 'Source run does not exist');
225
+ if (source.run_id === options.runId)
226
+ throw new EngineError('invalid_arguments', 'Fork must have a new run ID');
227
+ const lease = this.sqlite.prepare('SELECT expires_at FROM lease WHERE run_id=?').get(source.run_id);
228
+ if (lease && Date.parse(String(lease.expires_at)) > Date.now())
229
+ throw new EngineError('lease_held', 'Cannot fork an actively leased run');
230
+ const activeRows = this.rowsAt(options.revision);
231
+ const revisions = this.sqlite.prepare('SELECT * FROM durable_revisions WHERE revision<=? ORDER BY revision').all(options.revision);
232
+ const versions = this.sqlite.prepare('SELECT * FROM row_versions WHERE revision<=? ORDER BY revision').all(options.revision);
233
+ const cutAt = Date.parse(String(revisions.at(-1)?.at)), omitted = options.omitPieceStatePrefixes ?? ['session:'];
234
+ const keep = (table, row) => table !== 'control_inbox' && table !== 'role_sessions' &&
235
+ !(table === 'piece_state' && row && omitted.some(prefix => String(row.key).startsWith(prefix)));
236
+ const revisionTimes = new Map(revisions.map(row => [Number(row.revision), Date.parse(String(row.at))]));
237
+ const rewrite = (table, original, at = cutAt) => {
238
+ const row = { ...original };
239
+ if ('run_id' in row)
240
+ row.run_id = options.runId;
241
+ if (table === 'runs') {
242
+ row.fork_of = source.run_id;
243
+ row.lease_epoch = 0;
244
+ row.active_duration_ms = Number(row.active_duration_ms) + (row.active_started_at ? Math.max(0, at - Date.parse(String(row.active_started_at))) : 0);
245
+ row.active_started_at = null;
246
+ }
247
+ if (table === 'events') {
248
+ row.inherited = 1;
249
+ row.payload_json = JSON.stringify({ ...JSON.parse(String(row.payload_json)), runId: options.runId });
250
+ }
251
+ if (table === 'invocations' || table === 'attempts')
252
+ row.inherited = 1;
253
+ return row;
254
+ };
255
+ destination.transaction(undefined, () => {
256
+ for (const row of revisions)
257
+ destination.sqlite.prepare('INSERT INTO durable_revisions(revision,reason,at) VALUES (?,?,?)').run(row.revision, row.reason, row.at);
258
+ for (const version of versions) {
259
+ const table = String(version.table_name);
260
+ if (!(table in keys))
261
+ throw new EngineError('storage_corrupt', 'Unknown revision table');
262
+ const original = version.row_json ? decodeRow(String(version.row_json)) : undefined;
263
+ if (!keep(table, original))
264
+ continue;
265
+ const row = original ? rewrite(table, original, revisionTimes.get(Number(version.revision))) : undefined;
266
+ const key = row ? keys[table].map(name => row[name]) : JSON.parse(String(version.row_key)).map((value, index) => keys[table][index] === 'run_id' ? options.runId : value);
267
+ destination.sqlite.prepare('INSERT INTO row_versions(table_name,row_key,revision,row_json) VALUES (?,?,?,?)')
268
+ .run(table, JSON.stringify(key), version.revision, row ? encodeRow(row) : null);
269
+ }
270
+ for (const { table, row: original } of activeRows) {
271
+ if (!keep(table, original))
272
+ continue;
273
+ const row = rewrite(table, original), columns = Object.keys(row);
274
+ const allowed = new Set(destination.sqlite.prepare(`PRAGMA table_info(${table})`).all().map(info => String(info.name)));
275
+ if (columns.some(column => !allowed.has(column)))
276
+ throw new EngineError('storage_corrupt', 'Historical row has unknown fields');
277
+ destination.sqlite.prepare(`INSERT INTO ${table} (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')})`).run(...columns.map(column => row[column]));
278
+ }
279
+ destination.checkIntegrity();
280
+ });
281
+ destination.transaction('workflow-forked', () => { appendRunEvent(destination, options.runId, 'workflow_forked', { forkOf: String(source.run_id), beforeRevision: options.revision }); });
282
+ return { sourceRunId: String(source.run_id), checkpointThreadId: String(source.checkpoint_thread_id), revision: options.revision };
283
+ });
284
+ }
285
+ checkIntegrity() {
286
+ if (this.sqlite.prepare('PRAGMA integrity_check').get()?.integrity_check !== 'ok' || this.sqlite.prepare('PRAGMA foreign_key_check').all().length)
287
+ throw new PersistenceError('storage_corrupt', 'Run database integrity check failed');
288
+ }
289
+ close() { if (!this.closed) {
290
+ this.sqlite.close();
291
+ this.closed = true;
292
+ } }
293
+ }
294
+ /** Called inside the business transaction. Delivery/replay happens only after commit. */
295
+ export function appendRunEvent(db, runId, type, payload, context = {}, timestamp = new Date().toISOString()) {
296
+ const run = db.get('runs', { run_id: runId });
297
+ if (!run)
298
+ throw new EngineError('run_not_found', 'Run does not exist');
299
+ const sequence = Number(run.next_event_sequence);
300
+ const event = { sequence, runId, type, timestamp, ...context, payload };
301
+ db.put('events', { run_id: runId, sequence, event_id: randomUUID(), type, at: timestamp, payload_json: JSON.stringify(event) });
302
+ db.put('runs', { ...run, next_event_sequence: sequence + 1, current_revision: db.transactionRevision, updated_at: timestamp });
303
+ return event;
304
+ }
@@ -0,0 +1,17 @@
1
+ import { EngineError, type ExecutionLease } from '../contracts.js';
2
+ import { RunDatabase } from './database.js';
3
+ export declare const LEASE_HEARTBEAT_MS = 15000;
4
+ /** Fencing is checked in the same SQLite transaction as every execution mutation. */
5
+ export declare class RunLease {
6
+ private readonly db;
7
+ readonly runId: string;
8
+ private readonly now;
9
+ constructor(db: RunDatabase, runId: string, now?: () => number);
10
+ current(): ExecutionLease | undefined;
11
+ acquire(owner: string, ttlMs?: number): ExecutionLease;
12
+ assert(token: ExecutionLease): void;
13
+ renew(token: ExecutionLease, ttlMs?: number): ExecutionLease;
14
+ release(token: ExecutionLease): boolean;
15
+ /** An observer aborts all effects on loss. It must never leave an unhandled timer exception. */
16
+ heartbeat(token: ExecutionLease, onLost: (error: EngineError) => void): () => void;
17
+ }
@@ -0,0 +1,82 @@
1
+ import { EngineError } from '../contracts.js';
2
+ import { appendRunEvent } from './database.js';
3
+ const LEASE_MS = 60_000;
4
+ export const LEASE_HEARTBEAT_MS = 15_000;
5
+ /** Fencing is checked in the same SQLite transaction as every execution mutation. */
6
+ export class RunLease {
7
+ db;
8
+ runId;
9
+ now;
10
+ constructor(db, runId, now = Date.now) {
11
+ this.db = db;
12
+ this.runId = runId;
13
+ this.now = now;
14
+ }
15
+ current() {
16
+ const row = this.db.sqlite.prepare('SELECT * FROM lease WHERE run_id=?').get(this.runId);
17
+ return row ? { runId: this.runId, owner: String(row.owner), epoch: Number(row.epoch), expiresAt: Date.parse(String(row.expires_at)) } : undefined;
18
+ }
19
+ acquire(owner, ttlMs = LEASE_MS) {
20
+ if (!owner || !Number.isFinite(ttlMs) || ttlMs <= 0)
21
+ throw new EngineError('invalid_arguments', 'Lease owner and positive TTL are required');
22
+ return this.db.transaction('lease-acquired', () => {
23
+ const now = this.now(), prior = this.current(), run = this.db.get('runs', { run_id: this.runId });
24
+ if (!run)
25
+ throw new EngineError('run_not_found', 'Run does not exist');
26
+ if (prior && prior.expiresAt > now)
27
+ throw new EngineError('lease_held', 'Another executor holds this run lease', { expiresAt: prior.expiresAt });
28
+ const epoch = Number(run.lease_epoch) + 1, at = new Date(now).toISOString(), expiresAt = now + ttlMs;
29
+ this.db.sqlite.prepare('INSERT OR REPLACE INTO lease(run_id,owner,epoch,acquired_at,heartbeat_at,expires_at) VALUES (?,?,?,?,?,?)')
30
+ .run(this.runId, owner, epoch, at, at, new Date(expiresAt).toISOString());
31
+ const previousActive = run.active_started_at ? Math.max(0, Math.min(now, prior?.expiresAt ?? now) - Date.parse(String(run.active_started_at))) : 0;
32
+ this.db.put('runs', { ...run, lease_epoch: epoch, current_revision: this.db.transactionRevision, updated_at: at,
33
+ active_duration_ms: Number(run.active_duration_ms) + previousActive, active_started_at: at });
34
+ if (prior)
35
+ appendRunEvent(this.db, this.runId, 'lease_recovered', { previousEpoch: prior.epoch, epoch }, {}, at);
36
+ return { runId: this.runId, owner, epoch, expiresAt };
37
+ });
38
+ }
39
+ assert(token) {
40
+ const current = this.current();
41
+ if (token.runId !== this.runId || !current || current.owner !== token.owner || current.epoch !== token.epoch || current.expiresAt <= this.now()) {
42
+ throw new EngineError('lease_lost', 'Execution lease expired or was replaced; late effects cannot commit');
43
+ }
44
+ }
45
+ renew(token, ttlMs = LEASE_MS) {
46
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0)
47
+ throw new EngineError('invalid_arguments', 'Lease TTL must be positive');
48
+ return this.db.transaction(undefined, () => {
49
+ this.assert(token);
50
+ const now = this.now(), expiresAt = now + ttlMs;
51
+ this.db.sqlite.prepare('UPDATE lease SET heartbeat_at=?,expires_at=? WHERE run_id=? AND owner=? AND epoch=?')
52
+ .run(new Date(now).toISOString(), new Date(expiresAt).toISOString(), this.runId, token.owner, token.epoch);
53
+ return { ...token, expiresAt };
54
+ });
55
+ }
56
+ release(token) {
57
+ return this.db.transaction('lease-released', () => {
58
+ const current = this.current();
59
+ if (!current || current.owner !== token.owner || current.epoch !== token.epoch)
60
+ return false;
61
+ const run = this.db.get('runs', { run_id: this.runId });
62
+ const active = run.active_started_at ? Math.max(0, Math.min(this.now(), current.expiresAt) - Date.parse(String(run.active_started_at))) : 0;
63
+ this.db.put('runs', { ...run, active_duration_ms: Number(run.active_duration_ms) + active, active_started_at: null });
64
+ this.db.sqlite.prepare('DELETE FROM lease WHERE run_id=? AND owner=? AND epoch=?').run(this.runId, token.owner, token.epoch);
65
+ return true;
66
+ });
67
+ }
68
+ /** An observer aborts all effects on loss. It must never leave an unhandled timer exception. */
69
+ heartbeat(token, onLost) {
70
+ const timer = setInterval(() => {
71
+ try {
72
+ this.renew(token);
73
+ }
74
+ catch (error) {
75
+ clearInterval(timer);
76
+ onLost(error instanceof EngineError ? error : new EngineError('lease_lost', String(error)));
77
+ }
78
+ }, LEASE_HEARTBEAT_MS);
79
+ timer.unref();
80
+ return () => clearInterval(timer);
81
+ }
82
+ }
@@ -0,0 +1,133 @@
1
+ import type { ProviderInvocation, PendingProviderInvocation } from '../../efficiency-types.js';
2
+ import { type AttemptFrame, type DurableEngineEvent, type EngineUsage, type ExecutionLease, type JsonValue, type NodeAdmission, type PieceResult, type TaskIdentity, type TerminalCommit, type WorkflowBudget } from '../contracts.js';
3
+ import { RunDatabase, type DatabaseRow } from './database.js';
4
+ import { RunLease } from './lease.js';
5
+ export interface FrozenLedgerInput {
6
+ runId: string;
7
+ workflowId: string;
8
+ definitionHash: string;
9
+ definition: unknown;
10
+ request: unknown;
11
+ runtimeIdentity: unknown;
12
+ source: 'definition' | 'builtin';
13
+ budget?: WorkflowBudget;
14
+ checkpointThreadId?: string;
15
+ forkOf?: string;
16
+ }
17
+ /** The ledger is authoritative for visits, attempts, usage, receipts and durable event cursors. */
18
+ export declare class RunLedger {
19
+ readonly db: RunDatabase;
20
+ readonly token: ExecutionLease;
21
+ private readonly options;
22
+ readonly lease: RunLease;
23
+ private readonly now;
24
+ constructor(db: RunDatabase, token: ExecutionLease, options: {
25
+ maxTransitions: number;
26
+ now?: () => number;
27
+ failFast?: number;
28
+ });
29
+ static initialize(db: RunDatabase, input: FrozenLedgerInput): void;
30
+ get runId(): string;
31
+ run(): DatabaseRow;
32
+ private assertFrame;
33
+ private event;
34
+ /** Public node namespace differs from the parent saver namespace; never parse opaque namespace strings. */
35
+ private bindPendingTask;
36
+ private checkAdmission;
37
+ enter(input: NodeAdmission): AttemptFrame;
38
+ resultFor(frame: AttemptFrame): PieceResult | undefined;
39
+ terminal(frame: AttemptFrame, result: PieceResult): TerminalCommit;
40
+ /** Saver-only synchronous settlement; it shares the already-open pending-write transaction. */
41
+ commitTerminal(marker: TerminalCommit, task: TaskIdentity): DurableEngineEvent[];
42
+ fail(frame: AttemptFrame, error: {
43
+ code: string;
44
+ message: string;
45
+ }, retryable: boolean): void;
46
+ /** Stages evidence before an error is rethrown; saver owns its terminal __error__ transaction. */
47
+ prepareInterruption(frame: AttemptFrame, error: {
48
+ code: string;
49
+ message: string;
50
+ }): void;
51
+ commitError(task: TaskIdentity, error: unknown): DurableEngineEvent[];
52
+ /** Called by saver when LangGraph records the actual public interrupt IDs. */
53
+ commitInterrupts(task: TaskIdentity, values: Array<{
54
+ id: string;
55
+ value: JsonValue;
56
+ }>): DurableEngineEvent[];
57
+ /** Actual LangGraph interrupt identities, with answers retained across crashes before resume. */
58
+ interrupts(): Array<{
59
+ id: string;
60
+ nodePath: string;
61
+ scopeId: string;
62
+ branchId?: string;
63
+ attemptId: string;
64
+ kind: string;
65
+ requestedAt: string;
66
+ value: JsonValue;
67
+ answer?: JsonValue;
68
+ }>;
69
+ pendingInterrupts(): ReturnType<RunLedger['interrupts']>;
70
+ /** Persist validated answers before invoking Command; a retry must reuse exactly those answers. */
71
+ answerInterrupts(answers: Record<string, JsonValue>): void;
72
+ /** The run owner calls this only after graph.invoke has reached an idle interrupt barrier. */
73
+ settlePause(): void;
74
+ authorizeRecovery(attemptId: string): void;
75
+ startInvocation(frame: AttemptFrame, input: Omit<PendingProviderInvocation, 'ordinal'> & {
76
+ role?: string;
77
+ }, reservation?: {
78
+ maxTokens?: number;
79
+ maxCostUsd?: number;
80
+ }): number;
81
+ settleInvocation(frame: AttemptFrame, rawInvocation: ProviderInvocation & {
82
+ invocationId: string;
83
+ }, memo?: {
84
+ key: string;
85
+ value: JsonValue;
86
+ }): void;
87
+ /** Only a new fenced owner can close calls abandoned by an earlier executor.
88
+ * Keep unknown usage reservations: a lost response does not prove zero spend.
89
+ * This records accounting, never authorizes replay of an uncertain write. */
90
+ settleAbandonedInvocations(): number;
91
+ /** Caller owns the transaction and has checked either the current frame or
92
+ * exclusive recovery ownership of the earlier epoch. */
93
+ private commitInvocation;
94
+ readPieceState(frame: AttemptFrame, key: string): JsonValue | undefined;
95
+ private storePieceState;
96
+ writePieceState(frame: AttemptFrame, key: string, value: JsonValue): void;
97
+ private sessionOwner;
98
+ readScopedPieceState(frame: AttemptFrame, ownerNodePath: string, key: string): JsonValue | undefined;
99
+ writeScopedPieceState(frame: AttemptFrame, ownerNodePath: string, key: string, value: JsonValue): void;
100
+ usage(): EngineUsage;
101
+ /** Unreported spend keeps its reservation through recovery; bounds remain distinct from known usage. */
102
+ reservationStatus(): {
103
+ pendingInvocations: number;
104
+ knownTokens: number;
105
+ knownCostUsd: number;
106
+ tokensUnknown: boolean;
107
+ costUnknown: boolean;
108
+ };
109
+ activeDurationMs(): number;
110
+ scopeSnapshot(scopeId?: string): {
111
+ runId: string;
112
+ scopeId: string;
113
+ status: string;
114
+ transitions: number;
115
+ usage: EngineUsage;
116
+ candidate: import('../contracts.js').CandidateState | null;
117
+ verified: import('../contracts.js').VerifiedState | null;
118
+ consecutiveFailures: {
119
+ revision: number;
120
+ count: number;
121
+ };
122
+ attempts: Array<{
123
+ frame: AttemptFrame;
124
+ status: string;
125
+ result?: PieceResult;
126
+ error?: {
127
+ code: string;
128
+ message: string;
129
+ };
130
+ }>;
131
+ };
132
+ events(after?: number): DurableEngineEvent[];
133
+ }