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
@@ -1,12 +1,12 @@
1
+ import { BUILTIN_ROLES } from './executor-types.js';
1
2
  import { CACHE_TOKEN_KEYS } from './efficiency-types.js';
2
3
  export * from './efficiency-types.js';
3
4
  function sum(values) {
4
5
  return values.some(value => value == null || !Number.isFinite(value) || value < 0) ? null : values.reduce((total, value) => total + (value ?? 0), 0);
5
6
  }
6
- const AGENT_PHASES = new Set(['architect', 'developer', 'reviewer']);
7
- function totals(attempts) {
7
+ function totals(attempts, agentRoles) {
8
8
  const invocations = attempts.flatMap(attempt => attempt.invocations ?? []);
9
- const measured = attempts.filter(attempt => !AGENT_PHASES.has(attempt.stepId) || attempt.invocations !== undefined);
9
+ const measured = attempts.filter(attempt => !agentRoles.has(attempt.stepId) || attempt.invocations !== undefined);
10
10
  const complete = measured.length === attempts.length && attempts.every(attempt => !attempt.pendingInvocations?.length);
11
11
  const duration = (attempt) => attempt.completedAt ? Math.max(0, Date.parse(attempt.completedAt) - Date.parse(attempt.startedAt)) : null;
12
12
  const cache = Object.fromEntries(CACHE_TOKEN_KEYS.map(key => [key, complete ? sum(invocations.map(call => call.usage[key])) : null]));
@@ -23,12 +23,13 @@ function totals(attempts) {
23
23
  };
24
24
  }
25
25
  /** Read-only projection: failed/superseded attempts remain spend, polling never changes it. */
26
- export function runtimeEfficiency(state) {
26
+ export function runtimeEfficiency(state, roleIds = BUILTIN_ROLES) {
27
+ const agentRoles = new Set(roleIds);
27
28
  const phases = [...new Set(state.history.map(attempt => attempt.stepId))].map(stepId => {
28
29
  const attempts = state.history.filter(attempt => attempt.stepId === stepId);
29
30
  const calls = attempts.flatMap(attempt => attempt.invocations ?? []);
30
- return { stepId, ...totals(attempts), providers: [...new Set(calls.map(call => call.provider))], models: [...new Set(calls.map(call => call.model).filter((model) => model !== undefined))] };
31
+ return { stepId, ...totals(attempts, agentRoles), providers: [...new Set(calls.map(call => call.provider))], models: [...new Set(calls.map(call => call.model).filter((model) => model !== undefined))] };
31
32
  });
32
33
  const pending = state.history.some(attempt => attempt.pendingInvocations?.length);
33
- return { schemaVersion: 1, total: { ...totals(state.history), durationMs: state.usage.durationMs, inputTokens: pending ? null : state.usage.inputTokens, outputTokens: pending ? null : state.usage.outputTokens, costUsd: pending ? null : state.usage.costUsd }, phases };
34
+ return { schemaVersion: 1, total: { ...totals(state.history, agentRoles), durationMs: state.usage.durationMs, inputTokens: pending ? null : state.usage.inputTokens, outputTokens: pending ? null : state.usage.outputTokens, costUsd: pending ? null : state.usage.costUsd }, phases };
34
35
  }
@@ -0,0 +1,90 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "implementation-component",
4
+ "title": "Implementation inside a component",
5
+ "journal": "implementation",
6
+ "change": "new",
7
+ "entry": "work",
8
+ "maxTransitions": 100,
9
+ "roles": [
10
+ "architect",
11
+ "developer",
12
+ "reviewer"
13
+ ],
14
+ "nodes": {
15
+ "work": {
16
+ "kind": "component",
17
+ "params": {
18
+ "ref": "work"
19
+ },
20
+ "ends": {
21
+ "next": "verify",
22
+ "failed": "failed"
23
+ }
24
+ },
25
+ "done": {
26
+ "kind": "end",
27
+ "params": {
28
+ "outcome": "success",
29
+ "requiresVerified": true
30
+ },
31
+ "ends": {}
32
+ },
33
+ "failed": {
34
+ "kind": "end",
35
+ "params": {
36
+ "outcome": "failure"
37
+ },
38
+ "ends": {}
39
+ },
40
+ "verify": {
41
+ "kind": "verify",
42
+ "params": {
43
+ "commands": "configured"
44
+ },
45
+ "ends": {
46
+ "pass": "done",
47
+ "fail": "failed",
48
+ "failed": "failed"
49
+ }
50
+ }
51
+ },
52
+ "components": {
53
+ "work": {
54
+ "entry": "implement",
55
+ "nodes": {
56
+ "implement": {
57
+ "kind": "implementation",
58
+ "params": {
59
+ "approvalBeforeArchive": false
60
+ },
61
+ "ends": {
62
+ "next": "done",
63
+ "rejected": "failed",
64
+ "failed": "failed"
65
+ }
66
+ },
67
+ "done": {
68
+ "kind": "end",
69
+ "params": {
70
+ "outcome": "success",
71
+ "exit": "next"
72
+ },
73
+ "ends": {}
74
+ },
75
+ "failed": {
76
+ "kind": "end",
77
+ "params": {
78
+ "outcome": "failure",
79
+ "exit": "failed"
80
+ },
81
+ "ends": {}
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "delivery": {
87
+ "requiresVerified": true
88
+ },
89
+ "version": "3ebd003720588211080fe8515e19def7fd7516ff165765162d360839ca015e53"
90
+ }
@@ -0,0 +1,11 @@
1
+ import type { AgentRequest, RuntimeConfig } from '../../executor-types.js';
2
+ import { ExecutorRegistry } from '../../executors.js';
3
+ export declare function implementationFixture(approval?: boolean): {
4
+ root: string;
5
+ repository: string;
6
+ context: import("../../../pipeline/pipeline-state.js").PipelineContext;
7
+ config: RuntimeConfig;
8
+ registry: ExecutorRegistry;
9
+ requests: AgentRequest[];
10
+ dispose: () => void;
11
+ };
@@ -0,0 +1,60 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { validatePipelineContext } from '../../../pipeline/pipeline-state.js';
6
+ import { ExecutorRegistry } from '../../executors.js';
7
+ import { OpenSpecTools } from '../../openspec.js';
8
+ const change = 'native-implementation';
9
+ const usage = { inputTokens: 20, outputTokens: 10, costUsd: 0.1 };
10
+ export function implementationFixture(approval = false) {
11
+ const root = mkdtempSync(path.join(tmpdir(), 'native implementation '));
12
+ const repository = path.join(root, 'repository'), backlog = path.join(root, 'backlog');
13
+ mkdirSync(repository);
14
+ mkdirSync(backlog);
15
+ execFileSync('git', ['init', '-q', repository]);
16
+ writeFileSync(path.join(repository, 'code.cjs'), 'module.exports = 1\n');
17
+ execFileSync('git', ['-C', repository, 'add', '.']);
18
+ execFileSync('git', ['-C', repository, '-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-qm', 'baseline']);
19
+ const context = validatePipelineContext({ schemaVersion: 1, runId: 'implementation', backlogRoot: backlog, artifactRoot: repository, artifactRepositoryId: 'repo', repositories: [{ id: 'repo', name: 'Repo', path: repository }],
20
+ ownership: { git: 'host', backlog: 'host', worktrees: 'host' }, specs: [{ id: 7, title: 'Feature', description: 'Return two', repositoryIds: ['repo'], acceptanceCriteria: ['Function returns 2'] }] });
21
+ const role = { provider: 'fixture' };
22
+ const config = { schemaVersion: 1, enabled: true, providers: [], agents: { architect: role, developer: role, reviewer: role }, limits: { maxAttempts: 2 }, approvalBeforeArchive: approval,
23
+ verification: [{ repositoryId: 'repo', command: process.execPath, args: ['-e', 'if(require("./code.cjs")!==2)process.exit(9);console.log("real check passed")'] }] };
24
+ const requests = [];
25
+ const registry = new ExecutorRegistry().register('fixture', { capabilities: () => ({ transport: 'fixture', continuation: 'supported', effortSupport: 'unsupported', supportedEfforts: [], observedModel: false, observedEffort: false }),
26
+ async execute(request) {
27
+ requests.push(request);
28
+ const tools = new OpenSpecTools(request.openspec);
29
+ const capability = request.openspec.change === change ? 'feature' : 'feature-' + request.openspec.change.slice(-6);
30
+ await tools.execute({ action: 'load_skill' });
31
+ if (request.role !== 'architect')
32
+ await tools.execute({ action: 'instructions', artifact: 'apply' });
33
+ if (request.role === 'architect') {
34
+ await tools.execute({ action: 'new' });
35
+ const files = [
36
+ ['proposal', 'proposal.md', '# Why\nReturn two.\n# What Changes\nUpdate the function.\n# Capabilities\n## New Capabilities\n- feature: Return the required value.\n# Impact\nOne function.'],
37
+ ['design', 'design.md', '# Design\nUpdate code.cjs and verify the return value.'],
38
+ ['specs', `specs/${capability}/spec.md`, '## ADDED Requirements\n### Requirement: Return the required value\nThe function SHALL return 2.\n#### Scenario: Calling the function\n- **WHEN** code.cjs is loaded\n- **THEN** it returns 2\n'],
39
+ ['tasks', 'tasks.md', '- [ ] 1. Implement the return value and verify behavior\n'],
40
+ ];
41
+ for (const [artifact, file, content] of files) {
42
+ await tools.execute({ action: 'instructions', artifact });
43
+ await tools.execute({ action: 'write_artifact', path: file, content });
44
+ }
45
+ return { text: '{"confidence":"high"}', usage };
46
+ }
47
+ if (request.role === 'developer') {
48
+ writeFileSync(path.join(request.openspec.root, 'code.cjs'), 'module.exports = 2\n');
49
+ const tasks = path.join(request.openspec.root, 'openspec/changes', request.openspec.change, 'tasks.md');
50
+ writeFileSync(tasks, readFileSync(tasks, 'utf8').replaceAll('- [ ]', '- [x]'));
51
+ return { text: 'Implemented and checked the required behavior.', usage };
52
+ }
53
+ const obligations = JSON.parse(request.prompt.split('Current frozen acceptance obligations (all remain required):\n')[1].split('\n')[0]);
54
+ return { text: JSON.stringify({ approved: true, summary: 'Inspected actual code and verification evidence', issues: [], score: 90,
55
+ aspects: { type_correctness: 90, pattern_adherence: 90, test_coverage: 90, security: 90, architectural_alignment: 90 },
56
+ acceptance: { criteria: obligations.map(item => ({ specId: item.specId, criterionIndex: item.criterionIndex, status: 'met', evidence: ['code.cjs returns 2 and host verification passed'] })), checks: [], findings: [] } }), usage };
57
+ },
58
+ });
59
+ return { root, repository, context, config, registry, requests, dispose: () => rmSync(root, { recursive: true, force: true }) };
60
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "implementation",
4
+ "title": "Verified implementation",
5
+ "journal": "implementation",
6
+ "change": "new",
7
+ "entry": "implement",
8
+ "maxTransitions": 100,
9
+ "roles": [
10
+ "architect",
11
+ "developer",
12
+ "reviewer"
13
+ ],
14
+ "nodes": {
15
+ "implement": {
16
+ "kind": "implementation",
17
+ "params": {
18
+ "approvalBeforeArchive": false
19
+ },
20
+ "ends": {
21
+ "next": "done",
22
+ "rejected": "failed",
23
+ "failed": "failed"
24
+ }
25
+ },
26
+ "done": {
27
+ "kind": "end",
28
+ "params": {
29
+ "outcome": "success",
30
+ "requiresVerified": true
31
+ },
32
+ "ends": {}
33
+ },
34
+ "failed": {
35
+ "kind": "end",
36
+ "params": {
37
+ "outcome": "failure"
38
+ },
39
+ "ends": {}
40
+ }
41
+ },
42
+ "version": "e1aea44e144c42fe39ab782e9ce2df6e2253163ee3a1e503a966c2d99aaf45c4"
43
+ }
@@ -0,0 +1,9 @@
1
+ import { type EngineUsage, type WorkflowBudget } from './contracts.js';
2
+ export declare function budgetError(usage: EngineUsage, budget: WorkflowBudget, elapsedMs: number, admission?: boolean): string | undefined;
3
+ /** Missing billing stays unknown; only reported lower bounds reduce headroom. */
4
+ export declare function remainingBudget(usage: EngineUsage, budget: WorkflowBudget, elapsedMs: number, reserved?: {
5
+ costUsd: number;
6
+ tokens: number;
7
+ }): WorkflowBudget;
8
+ /** Graph and host limits intersect: an imported definition cannot raise host caps. */
9
+ export declare function intersectBudgets(...values: Array<WorkflowBudget | undefined>): WorkflowBudget;
@@ -0,0 +1,33 @@
1
+ import { EngineError } from './contracts.js';
2
+ export function budgetError(usage, budget, elapsedMs, admission = true) {
3
+ const reached = (used, limit) => admission ? used >= limit : used > limit;
4
+ if (budget.maxCostUsd !== undefined && reached(usage.knownCostUsd, budget.maxCostUsd))
5
+ return 'cost_budget';
6
+ if (budget.maxTokens !== undefined && reached(usage.knownInputTokens + usage.knownOutputTokens, budget.maxTokens))
7
+ return 'token_budget';
8
+ if (budget.maxDurationMs !== undefined && reached(elapsedMs, budget.maxDurationMs))
9
+ return 'timeout';
10
+ return undefined;
11
+ }
12
+ /** Missing billing stays unknown; only reported lower bounds reduce headroom. */
13
+ export function remainingBudget(usage, budget, elapsedMs, reserved = { costUsd: 0, tokens: 0 }) {
14
+ return {
15
+ ...(budget.maxCostUsd === undefined ? {} : { maxCostUsd: Math.max(0, budget.maxCostUsd - usage.knownCostUsd - reserved.costUsd) }),
16
+ ...(budget.maxTokens === undefined ? {} : { maxTokens: Math.max(0, Math.floor(budget.maxTokens - usage.knownInputTokens - usage.knownOutputTokens - reserved.tokens)) }),
17
+ ...(budget.maxDurationMs === undefined ? {} : { maxDurationMs: Math.max(0, budget.maxDurationMs - elapsedMs) }),
18
+ };
19
+ }
20
+ /** Graph and host limits intersect: an imported definition cannot raise host caps. */
21
+ export function intersectBudgets(...values) {
22
+ const result = {};
23
+ for (const value of values)
24
+ for (const key of ['maxCostUsd', 'maxTokens', 'maxDurationMs']) {
25
+ const limit = value?.[key];
26
+ if (limit === undefined)
27
+ continue;
28
+ if (!Number.isFinite(limit) || limit < 0 || (key === 'maxTokens' && !Number.isSafeInteger(limit)))
29
+ throw new EngineError('invalid_arguments', `Invalid ${key}`);
30
+ result[key] = Math.min(result[key] ?? Infinity, limit);
31
+ }
32
+ return result;
33
+ }
@@ -0,0 +1,9 @@
1
+ import { type JsonValue } from './contracts.js';
2
+ export declare const MAX_DEFINITION_BYTES: number;
3
+ export declare const MAX_JSON_DEPTH = 64;
4
+ /** RFC 8785: emit sorted keys directly, avoiding JS integer-key enumeration order. */
5
+ export declare function canonicalJson(value: unknown): string;
6
+ /** Reject duplicate keys before JSON.parse could silently discard their evidence. */
7
+ export declare function parseDefinitionJson(input: string | Uint8Array): JsonValue;
8
+ export declare function definitionVersion(definition: unknown): string;
9
+ export declare function contentDigest(value: unknown): string;
@@ -0,0 +1,162 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { EngineError } from './contracts.js';
3
+ export const MAX_DEFINITION_BYTES = 2 * 1024 * 1024;
4
+ export const MAX_JSON_DEPTH = 64;
5
+ const invalidSurrogate = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u;
6
+ function fail(message) { throw new EngineError('invalid_definition', message); }
7
+ function validString(value) {
8
+ if (invalidSurrogate.test(value))
9
+ fail('JSON strings must contain valid Unicode scalar values');
10
+ }
11
+ /** RFC 8785: emit sorted keys directly, avoiding JS integer-key enumeration order. */
12
+ export function canonicalJson(value) {
13
+ const active = new Set();
14
+ function encode(item, depth) {
15
+ if (depth > MAX_JSON_DEPTH)
16
+ fail(`JSON exceeds depth ${MAX_JSON_DEPTH}`);
17
+ if (item === null || typeof item === 'boolean')
18
+ return JSON.stringify(item);
19
+ if (typeof item === 'number') {
20
+ if (!Number.isFinite(item))
21
+ fail('JSON numbers must be finite');
22
+ return JSON.stringify(item);
23
+ }
24
+ if (typeof item === 'string') {
25
+ validString(item);
26
+ return JSON.stringify(item);
27
+ }
28
+ if (typeof item !== 'object')
29
+ return fail('Only JSON values are permitted');
30
+ if (active.has(item))
31
+ fail('JSON must not contain cycles');
32
+ if (!Array.isArray(item) && ![Object.prototype, null].includes(Object.getPrototypeOf(item)))
33
+ fail('JSON objects must have a plain prototype');
34
+ if (Object.getOwnPropertySymbols(item).length)
35
+ fail('JSON objects must not contain symbol keys');
36
+ active.add(item);
37
+ let encoded;
38
+ if (Array.isArray(item)) {
39
+ encoded = '[' + Array.from({ length: item.length }, (_, index) => {
40
+ const descriptor = Object.getOwnPropertyDescriptor(item, String(index));
41
+ if (!descriptor || !('value' in descriptor))
42
+ fail('JSON arrays must not contain holes or accessors');
43
+ return encode(descriptor.value, depth + 1);
44
+ }).join(',') + ']';
45
+ }
46
+ else {
47
+ encoded = '{' + Object.keys(item).sort().map(key => {
48
+ validString(key);
49
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
50
+ if (!('value' in descriptor))
51
+ fail('JSON objects must not contain accessors');
52
+ return JSON.stringify(key) + ':' + encode(descriptor.value, depth + 1);
53
+ }).join(',') + '}';
54
+ }
55
+ active.delete(item);
56
+ return encoded;
57
+ }
58
+ return encode(value, 0);
59
+ }
60
+ /** Reject duplicate keys before JSON.parse could silently discard their evidence. */
61
+ export function parseDefinitionJson(input) {
62
+ if (Buffer.byteLength(input) > MAX_DEFINITION_BYTES)
63
+ fail(`Definition exceeds ${MAX_DEFINITION_BYTES} bytes`);
64
+ let source;
65
+ try {
66
+ source = typeof input === 'string' ? input : new TextDecoder('utf-8', { fatal: true }).decode(input);
67
+ }
68
+ catch {
69
+ return fail('Definition is not valid UTF-8');
70
+ }
71
+ let cursor = 0;
72
+ function whitespace() { while (/[\x20\x09\x0a\x0d]/.test(source[cursor] ?? '\0'))
73
+ cursor += 1; }
74
+ function string() {
75
+ const start = cursor++;
76
+ while (cursor < source.length) {
77
+ const token = source[cursor++];
78
+ if (token === '\\') {
79
+ cursor += 1;
80
+ continue;
81
+ }
82
+ if (token === '"') {
83
+ let value;
84
+ try {
85
+ value = JSON.parse(source.slice(start, cursor));
86
+ }
87
+ catch {
88
+ return fail('Invalid JSON string');
89
+ }
90
+ validString(value);
91
+ return value;
92
+ }
93
+ }
94
+ return fail('Unterminated JSON string');
95
+ }
96
+ function parse(depth) {
97
+ if (depth > MAX_JSON_DEPTH)
98
+ fail(`JSON exceeds depth ${MAX_JSON_DEPTH}`);
99
+ whitespace();
100
+ const token = source[cursor];
101
+ if (token === '"')
102
+ return string();
103
+ if (token === '{' || token === '[') {
104
+ cursor += 1;
105
+ whitespace();
106
+ const object = token === '{';
107
+ const value = object ? Object.create(null) : [];
108
+ const close = object ? '}' : ']';
109
+ if (source[cursor] === close) {
110
+ cursor += 1;
111
+ return value;
112
+ }
113
+ while (cursor < source.length) {
114
+ whitespace();
115
+ if (object) {
116
+ if (source[cursor] !== '"')
117
+ fail('JSON object key must be a string');
118
+ const key = string();
119
+ if (Object.hasOwn(value, key))
120
+ fail(`Duplicate JSON key: ${key}`);
121
+ whitespace();
122
+ if (source[cursor++] !== ':')
123
+ fail('Expected colon after JSON key');
124
+ value[key] = parse(depth + 1);
125
+ }
126
+ else
127
+ value.push(parse(depth + 1));
128
+ whitespace();
129
+ const next = source[cursor++];
130
+ if (next === close)
131
+ return value;
132
+ if (next !== ',')
133
+ fail('Expected JSON comma or closing delimiter');
134
+ }
135
+ return fail('Unterminated JSON container');
136
+ }
137
+ const literal = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(source.slice(cursor))?.[0];
138
+ if (!literal)
139
+ return fail('Invalid JSON value');
140
+ cursor += literal.length;
141
+ const value = JSON.parse(literal);
142
+ if (typeof value === 'number' && !Number.isFinite(value))
143
+ fail('JSON numbers must be finite');
144
+ return value;
145
+ }
146
+ const value = parse(0);
147
+ whitespace();
148
+ if (cursor !== source.length)
149
+ fail('Unexpected content after JSON value');
150
+ return value;
151
+ }
152
+ export function definitionVersion(definition) {
153
+ if (!definition || typeof definition !== 'object' || Array.isArray(definition))
154
+ fail('Definition must be an object');
155
+ // Encode before copying to reject accessor properties without invoking them.
156
+ canonicalJson(definition);
157
+ const content = Object.fromEntries(Object.entries(definition).filter(([key]) => key !== 'version'));
158
+ return createHash('sha256').update(canonicalJson(content), 'utf8').digest('hex');
159
+ }
160
+ export function contentDigest(value) {
161
+ return createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex');
162
+ }
@@ -0,0 +1,67 @@
1
+ import { DatabaseSync, type SQLInputValue, type SQLOutputValue } from 'node:sqlite';
2
+ import { type DurableEngineEvent, type JsonValue } from '../contracts.js';
3
+ export { PersistenceError, ensurePrivateDirectory as ensurePrivateRunDirectory } from '../storage/private-path.js';
4
+ export type DatabaseRow = Record<string, SQLOutputValue>;
5
+ export type WriteRow = Record<string, SQLInputValue>;
6
+ /** Every identifier interpolated into SQL comes from this closed adapter-owned map. */
7
+ declare const keys: {
8
+ readonly runs: readonly ["run_id"];
9
+ readonly visits: readonly ["visit_id"];
10
+ readonly steps: readonly ["run_id", "node_path", "scope_id"];
11
+ readonly attempts: readonly ["attempt_id"];
12
+ readonly invocations: readonly ["invocation_id"];
13
+ readonly receipts: readonly ["receipt_id"];
14
+ readonly budget: readonly ["run_id"];
15
+ readonly interrupts: readonly ["interrupt_id"];
16
+ readonly control_inbox: readonly ["request_id"];
17
+ readonly role_sessions: readonly ["session_key"];
18
+ readonly piece_state: readonly ["run_id", "scope_id", "node_path", "key"];
19
+ readonly reservations: readonly ["reservation_id"];
20
+ readonly events: readonly ["run_id", "sequence"];
21
+ readonly checkpoints: readonly ["thread_id", "checkpoint_ns", "checkpoint_id"];
22
+ readonly writes: readonly ["thread_id", "checkpoint_ns", "checkpoint_id", "task_id", "idx"];
23
+ };
24
+ export type RevisionTable = keyof typeof keys;
25
+ /** Short synchronous transactions own graph writes and ledger evidence together. */
26
+ export declare class RunDatabase {
27
+ readonly filename: string;
28
+ readonly sqlite: DatabaseSync;
29
+ private activeRevision;
30
+ private inTransaction;
31
+ private closed;
32
+ private constructor();
33
+ static open(filename: string, options?: {
34
+ create?: boolean;
35
+ readOnly?: boolean;
36
+ }): Promise<RunDatabase>;
37
+ get revision(): number;
38
+ get transactionRevision(): number;
39
+ /** Undefined reason is reserved for leases/read reservations, which do not change graph history. */
40
+ transaction<T>(reason: string | undefined, operation: () => T): T;
41
+ get(table: RevisionTable, key: WriteRow): DatabaseRow | undefined;
42
+ put(table: RevisionTable, row: WriteRow): void;
43
+ delete(table: RevisionTable, key: WriteRow): void;
44
+ /** Immutable cut includes overwritten pending writes and all child namespaces. Caller holds an inactive-source reservation. */
45
+ rowsAt(revision: number): Array<{
46
+ table: RevisionTable;
47
+ row: WriteRow;
48
+ }>;
49
+ /**
50
+ * Copies a complete historical cut into an empty, separately staged database.
51
+ * The source writer reservation prevents takeover/steering while rows are read;
52
+ * serialized graph blobs, opaque thread IDs and child namespaces stay unchanged.
53
+ */
54
+ forkAt(destination: RunDatabase, options: {
55
+ revision: number;
56
+ runId: string;
57
+ omitPieceStatePrefixes?: string[];
58
+ }): {
59
+ sourceRunId: string;
60
+ checkpointThreadId: string;
61
+ revision: number;
62
+ };
63
+ checkIntegrity(): void;
64
+ close(): void;
65
+ }
66
+ /** Called inside the business transaction. Delivery/replay happens only after commit. */
67
+ export declare function appendRunEvent(db: RunDatabase, runId: string, type: string, payload: JsonValue, context?: Partial<Pick<DurableEngineEvent, 'nodePath' | 'scopeId' | 'branchId' | 'visit' | 'attempt' | 'attemptId'>>, timestamp?: string): DurableEngineEvent;