@hunterzhu/pulse-runtime 0.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 (93) hide show
  1. package/dist/context/builder.d.ts +68 -0
  2. package/dist/context/builder.js +127 -0
  3. package/dist/context/index.d.ts +2 -0
  4. package/dist/context/index.js +2 -0
  5. package/dist/context/merger.d.ts +25 -0
  6. package/dist/context/merger.js +125 -0
  7. package/dist/core/actions.d.ts +1 -0
  8. package/dist/core/actions.js +1 -0
  9. package/dist/core/errors.d.ts +8 -0
  10. package/dist/core/errors.js +36 -0
  11. package/dist/core/events.d.ts +10 -0
  12. package/dist/core/events.js +24 -0
  13. package/dist/core/factory.d.ts +35 -0
  14. package/dist/core/factory.js +27 -0
  15. package/dist/core/inbox.d.ts +119 -0
  16. package/dist/core/inbox.js +217 -0
  17. package/dist/core/mutations.d.ts +80 -0
  18. package/dist/core/mutations.js +127 -0
  19. package/dist/core/records.d.ts +1 -0
  20. package/dist/core/records.js +1 -0
  21. package/dist/core/types.d.ts +615 -0
  22. package/dist/core/types.js +109 -0
  23. package/dist/dependencies/graph.d.ts +25 -0
  24. package/dist/dependencies/graph.js +92 -0
  25. package/dist/dependencies/index.d.ts +1 -0
  26. package/dist/dependencies/index.js +1 -0
  27. package/dist/dsl/context-proxy.d.ts +20 -0
  28. package/dist/dsl/context-proxy.js +64 -0
  29. package/dist/dsl/index.d.ts +4 -0
  30. package/dist/dsl/index.js +4 -0
  31. package/dist/dsl/program.d.ts +314 -0
  32. package/dist/dsl/program.js +756 -0
  33. package/dist/dsl/session.d.ts +45 -0
  34. package/dist/dsl/session.js +93 -0
  35. package/dist/dsl/templates-index.d.ts +1 -0
  36. package/dist/dsl/templates-index.js +1 -0
  37. package/dist/dsl/templates.d.ts +85 -0
  38. package/dist/dsl/templates.js +110 -0
  39. package/dist/index.d.ts +15 -0
  40. package/dist/index.js +15 -0
  41. package/dist/lifecycle/index.d.ts +2 -0
  42. package/dist/lifecycle/index.js +2 -0
  43. package/dist/lifecycle/scopes.d.ts +38 -0
  44. package/dist/lifecycle/scopes.js +50 -0
  45. package/dist/lifecycle/watchdog.d.ts +16 -0
  46. package/dist/lifecycle/watchdog.js +66 -0
  47. package/dist/models/actions.d.ts +10 -0
  48. package/dist/models/actions.js +68 -0
  49. package/dist/models/index.d.ts +2 -0
  50. package/dist/models/index.js +2 -0
  51. package/dist/models/router.d.ts +187 -0
  52. package/dist/models/router.js +353 -0
  53. package/dist/scheduler/clock.d.ts +45 -0
  54. package/dist/scheduler/clock.js +92 -0
  55. package/dist/scheduler/decision.d.ts +72 -0
  56. package/dist/scheduler/decision.js +63 -0
  57. package/dist/scheduler/index.d.ts +6 -0
  58. package/dist/scheduler/index.js +6 -0
  59. package/dist/scheduler/locks.d.ts +18 -0
  60. package/dist/scheduler/locks.js +106 -0
  61. package/dist/scheduler/ready-queue.d.ts +32 -0
  62. package/dist/scheduler/ready-queue.js +40 -0
  63. package/dist/scheduler/runtime.d.ts +486 -0
  64. package/dist/scheduler/runtime.js +3445 -0
  65. package/dist/scheduler/telemetry.d.ts +111 -0
  66. package/dist/scheduler/telemetry.js +177 -0
  67. package/dist/scheduler/worker.d.ts +158 -0
  68. package/dist/scheduler/worker.js +744 -0
  69. package/dist/storage/artifacts.d.ts +17 -0
  70. package/dist/storage/artifacts.js +90 -0
  71. package/dist/storage/findings.d.ts +12 -0
  72. package/dist/storage/findings.js +70 -0
  73. package/dist/storage/index.d.ts +8 -0
  74. package/dist/storage/index.js +8 -0
  75. package/dist/storage/memory.d.ts +11 -0
  76. package/dist/storage/memory.js +21 -0
  77. package/dist/storage/mutation-log.d.ts +41 -0
  78. package/dist/storage/mutation-log.js +140 -0
  79. package/dist/storage/outbox.d.ts +30 -0
  80. package/dist/storage/outbox.js +59 -0
  81. package/dist/storage/persistence.d.ts +183 -0
  82. package/dist/storage/persistence.js +999 -0
  83. package/dist/storage/policy.d.ts +80 -0
  84. package/dist/storage/policy.js +268 -0
  85. package/dist/storage/session.d.ts +140 -0
  86. package/dist/storage/session.js +447 -0
  87. package/dist/tools/registry.d.ts +125 -0
  88. package/dist/tools/registry.js +308 -0
  89. package/dist/transitions/index.d.ts +2 -0
  90. package/dist/transitions/index.js +1 -0
  91. package/dist/transitions/validate.d.ts +4 -0
  92. package/dist/transitions/validate.js +1118 -0
  93. package/package.json +21 -0
@@ -0,0 +1,308 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { normalize } from 'node:path';
3
+ import { validateJsonSchema } from '../models/router.js';
4
+ function normalizeWorkspaceRoot(root) { if (root === '*')
5
+ return root; const value = normalize(root); return value.length > 1 ? value.replace(/\/$/, '') : value; }
6
+ function normalizeNetworkHost(host) { return host.toLocaleLowerCase().replace(/\.$/, ''); }
7
+ function isJsonSchema(value) {
8
+ const seen = new Set();
9
+ const visit = (candidate) => {
10
+ if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate))
11
+ return false;
12
+ const schema = candidate;
13
+ if (seen.has(schema))
14
+ return false;
15
+ seen.add(schema);
16
+ try {
17
+ if (schema.type !== undefined && (typeof schema.type !== 'string' || !['null', 'boolean', 'number', 'integer', 'string', 'array', 'object'].includes(schema.type)))
18
+ return false;
19
+ for (const key of ['anyOf', 'oneOf', 'allOf'])
20
+ if (schema[key] !== undefined && (!Array.isArray(schema[key]) || schema[key].length === 0 || !schema[key].every(visit)))
21
+ return false;
22
+ if (schema.not !== undefined && !visit(schema.not))
23
+ return false;
24
+ if (schema.items !== undefined && !visit(schema.items))
25
+ return false;
26
+ if (schema.properties !== undefined && (schema.properties === null || typeof schema.properties !== 'object' || Array.isArray(schema.properties) || !Object.values(schema.properties).every(visit)))
27
+ return false;
28
+ if (schema.additionalProperties !== undefined && typeof schema.additionalProperties !== 'boolean' && !visit(schema.additionalProperties))
29
+ return false;
30
+ if (schema.required !== undefined && (!Array.isArray(schema.required) || new Set(schema.required).size !== schema.required.length || schema.required.some((key) => typeof key !== 'string')))
31
+ return false;
32
+ if (schema.enum !== undefined && !Array.isArray(schema.enum))
33
+ return false;
34
+ if (schema.pattern !== undefined) {
35
+ if (typeof schema.pattern !== 'string')
36
+ return false;
37
+ try {
38
+ new RegExp(schema.pattern);
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ for (const key of ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf'])
45
+ if (schema[key] !== undefined && (typeof schema[key] !== 'number' || !Number.isFinite(schema[key])))
46
+ return false;
47
+ for (const key of ['minLength', 'maxLength', 'minItems', 'maxItems'])
48
+ if (schema[key] !== undefined && (!Number.isInteger(schema[key]) || schema[key] < 0))
49
+ return false;
50
+ if (schema.uniqueItems !== undefined && typeof schema.uniqueItems !== 'boolean')
51
+ return false;
52
+ return true;
53
+ }
54
+ finally {
55
+ seen.delete(schema);
56
+ }
57
+ };
58
+ return visit(value);
59
+ }
60
+ function isResourceLocks(value) {
61
+ if (!Array.isArray(value))
62
+ return false;
63
+ return value.every((lock) => {
64
+ if (lock === null || typeof lock !== 'object' || Array.isArray(lock))
65
+ return false;
66
+ const record = lock;
67
+ return typeof record.resource === 'string' && record.resource.length > 0 && (record.mode === 'shared' || record.mode === 'exclusive');
68
+ });
69
+ }
70
+ function validateResolvedLocks(value, name) {
71
+ if (!isResourceLocks(value))
72
+ throw Object.assign(new Error(`Resolved resources are invalid for tool ${name}.`), { code: 'INVALID_TOOL_RESOURCE_LOCKS', retryable: false });
73
+ return value;
74
+ }
75
+ function isPermissions(value) {
76
+ if (value === undefined)
77
+ return true;
78
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
79
+ return false;
80
+ const permissions = value;
81
+ return (permissions.workspaceRoots === undefined || (Array.isArray(permissions.workspaceRoots) && permissions.workspaceRoots.every((root) => typeof root === 'string' && root.length > 0))) && (permissions.networkHosts === undefined || (Array.isArray(permissions.networkHosts) && permissions.networkHosts.every((host) => typeof host === 'string' && host.length > 0)));
82
+ }
83
+ function isManifestContract(manifest) {
84
+ return typeof manifest.description === 'string' && isJsonSchema(manifest.inputSchema) && isJsonSchema(manifest.outputSchema) && ['llm', 'tool', 'agent', 'none'].includes(String(manifest.concurrencyClass)) && ['none', 'read', 'write', 'external'].includes(String(manifest.sideEffectPolicy)) && ['read_only', 'idempotent', 'unsafe'].includes(String(manifest.retrySafety)) && isResourceLocks(manifest.locks) && (manifest.resources === undefined || isResourceLocks(manifest.resources)) && (manifest.tags === undefined || (Array.isArray(manifest.tags) && manifest.tags.every((tag) => typeof tag === 'string' && tag.length > 0))) && (manifest.maxResultSummaryBytes === undefined || (Number.isInteger(manifest.maxResultSummaryBytes) && manifest.maxResultSummaryBytes >= 0)) && isPermissions(manifest.permissions);
85
+ }
86
+ function summaryWithinBudget(value, maxBytes) {
87
+ try {
88
+ return Buffer.byteLength(JSON.stringify(value), 'utf8') <= maxBytes;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ function isStrictJsonValue(value, seen = new Set()) {
95
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
96
+ return true;
97
+ if (typeof value === 'number')
98
+ return Number.isFinite(value);
99
+ if (typeof value !== 'object')
100
+ return false;
101
+ if (seen.has(value))
102
+ return false;
103
+ if (Array.isArray(value)) {
104
+ seen.add(value);
105
+ try {
106
+ return value.every((item) => isStrictJsonValue(item, seen));
107
+ }
108
+ finally {
109
+ seen.delete(value);
110
+ }
111
+ }
112
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
113
+ return false;
114
+ seen.add(value);
115
+ try {
116
+ return Object.values(value).every((item) => isStrictJsonValue(item, seen));
117
+ }
118
+ finally {
119
+ seen.delete(value);
120
+ }
121
+ }
122
+ function validateStrictJsonValue(value, code, message) {
123
+ if (!isStrictJsonValue(value))
124
+ throw Object.assign(new Error(message), { code, retryable: false });
125
+ return value;
126
+ }
127
+ function validateDiscoveryQuery(query) {
128
+ if (query === null || typeof query !== 'object' || Array.isArray(query))
129
+ throw Object.assign(new Error('Invalid tool discovery query.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
130
+ const value = query;
131
+ if (value.text !== undefined && typeof value.text !== 'string')
132
+ throw Object.assign(new Error('Invalid tool discovery query text.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
133
+ if (value.tags !== undefined && (!Array.isArray(value.tags) || value.tags.some((tag) => typeof tag !== 'string')))
134
+ throw Object.assign(new Error('Invalid tool discovery query tags.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
135
+ if (value.sideEffectPolicy !== undefined && !['none', 'read', 'write', 'external'].includes(String(value.sideEffectPolicy)))
136
+ throw Object.assign(new Error('Invalid tool discovery side-effect policy.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
137
+ if (value.concurrencyClass !== undefined && !['llm', 'tool', 'agent', 'none'].includes(String(value.concurrencyClass)))
138
+ throw Object.assign(new Error('Invalid tool discovery concurrency class.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
139
+ if (value.limit !== undefined && (!Number.isInteger(value.limit) || value.limit < 0))
140
+ throw Object.assign(new Error('Invalid tool discovery limit.'), { code: 'INVALID_TOOL_DISCOVERY_QUERY', retryable: false });
141
+ }
142
+ /**
143
+ * Core tool catalog used by PulseRuntime. Tool SDK definitions are structurally
144
+ * compatible, so an application may register them directly and still choose a
145
+ * separate executor or adapter.
146
+ */
147
+ export class RuntimeToolRegistry {
148
+ definitions = new Map();
149
+ policy;
150
+ constructor(policy = {}) {
151
+ this.policy = { ...(policy.allow === undefined ? {} : { allow: new Set(policy.allow) }), deny: new Set(policy.deny ?? []), ...(policy.workspaceRoots === undefined ? {} : { workspaceRoots: new Set(policy.workspaceRoots.map(normalizeWorkspaceRoot)) }), ...(policy.networkHosts === undefined ? {} : { networkHosts: new Set(policy.networkHosts.map(normalizeNetworkHost)) }), allowNetwork: policy.allowNetwork ?? true };
152
+ }
153
+ register(definition) {
154
+ const candidate = definition;
155
+ const manifest = candidate?.manifest;
156
+ const name = typeof manifest?.name === 'string' ? manifest.name : '';
157
+ if (!manifest || typeof manifest.name !== 'string' || !name || this.definitions.has(name))
158
+ throw new Error(`TOOL_ALREADY_REGISTERED:${name}`);
159
+ if (typeof manifest.version !== 'string' || !manifest.version || typeof manifest.defaultTimeoutMs !== 'number' || !Number.isFinite(manifest.defaultTimeoutMs) || manifest.defaultTimeoutMs < 0 || !isManifestContract(manifest))
160
+ throw new Error(`INVALID_TOOL_MANIFEST:${name}`);
161
+ if (manifest.supportsAbortSignal !== true)
162
+ throw new Error(`TOOL_ABORT_SIGNAL_REQUIRED:${manifest.name}`);
163
+ if (typeof candidate.execute !== 'function')
164
+ throw new Error(`INVALID_TOOL_DEFINITION:${manifest.name}`);
165
+ this.definitions.set(manifest.name, candidate);
166
+ }
167
+ get(name) { return this.isAllowed(name) ? this.definitions.get(name) : undefined; }
168
+ isAllowed(name) {
169
+ const definition = this.definitions.get(name);
170
+ return this.policy.deny.has(name) === false && (this.policy.allow === undefined || this.policy.allow.has(name)) && (definition === undefined || this.permissionsAllowed(definition.manifest));
171
+ }
172
+ permissionReasons(name) {
173
+ const definition = this.definitions.get(name);
174
+ if (!definition)
175
+ return ['UNKNOWN_TOOL'];
176
+ const reasons = [];
177
+ const permissions = definition.manifest.permissions;
178
+ if (!permissions)
179
+ return reasons;
180
+ if (!this.policy.allowNetwork && (permissions.networkHosts?.length ?? 0) > 0)
181
+ reasons.push('NETWORK_DISABLED');
182
+ if (this.policy.networkHosts !== undefined)
183
+ for (const rawHost of permissions.networkHosts ?? []) {
184
+ const host = normalizeNetworkHost(rawHost);
185
+ if (!this.policy.networkHosts.has('*') && !this.policy.networkHosts.has(host))
186
+ reasons.push(`NETWORK_HOST_NOT_ALLOWED:${rawHost}`);
187
+ }
188
+ if (this.policy.workspaceRoots !== undefined)
189
+ for (const rawRoot of permissions.workspaceRoots ?? []) {
190
+ const root = normalizeWorkspaceRoot(rawRoot);
191
+ if (![...this.policy.workspaceRoots].some((allowed) => allowed === '*' || root === allowed || root.startsWith(`${allowed}/`)))
192
+ reasons.push(`WORKSPACE_ROOT_NOT_ALLOWED:${rawRoot}`);
193
+ }
194
+ return reasons;
195
+ }
196
+ list() { return [...this.definitions.values()].filter((definition) => this.isAllowed(definition.manifest.name)).map((definition) => structuredClone(definition.manifest)); }
197
+ validateInput(name, input) {
198
+ const definition = this.require(name);
199
+ try {
200
+ const parsed = definition.validateInput ? definition.validateInput(input) : input;
201
+ if (!definition.validateInput && !validateJsonSchema(parsed, definition.manifest.inputSchema))
202
+ throw new Error('schema mismatch');
203
+ return parsed;
204
+ }
205
+ catch {
206
+ throw Object.assign(new Error(`Input does not match the manifest for tool ${name}.`), { code: 'INVALID_TOOL_INPUT', retryable: false });
207
+ }
208
+ }
209
+ discover(query = {}) {
210
+ validateDiscoveryQuery(query);
211
+ const terms = (query.text ?? '').toLocaleLowerCase().split(/[^a-z0-9_:-]+/).filter(Boolean);
212
+ const requestedTags = new Set((query.tags ?? []).map((tag) => tag.toLocaleLowerCase()));
213
+ const results = this.list().flatMap((manifest) => {
214
+ if (query.sideEffectPolicy !== undefined && manifest.sideEffectPolicy !== query.sideEffectPolicy)
215
+ return [];
216
+ if (query.concurrencyClass !== undefined && manifest.concurrencyClass !== query.concurrencyClass)
217
+ return [];
218
+ const tags = (manifest.tags ?? []).map((tag) => tag.toLocaleLowerCase());
219
+ if ([...requestedTags].some((tag) => !tags.includes(tag)))
220
+ return [];
221
+ const haystack = [manifest.name, manifest.description, ...tags].join(' ').toLocaleLowerCase();
222
+ const score = terms.length === 0 ? 1 + requestedTags.size * 2 : terms.reduce((total, term) => total + (manifest.name.toLocaleLowerCase() === term ? 10 : manifest.name.toLocaleLowerCase().includes(term) ? 5 : haystack.includes(term) ? 1 : 0), requestedTags.size * 2);
223
+ return score > 0 ? [{ manifest, score }] : [];
224
+ });
225
+ results.sort((left, right) => right.score - left.score || left.manifest.name.localeCompare(right.manifest.name) || left.manifest.version.localeCompare(right.manifest.version));
226
+ return query.limit === undefined ? results : results.slice(0, Math.max(0, query.limit));
227
+ }
228
+ compileToolSet(id, query = {}, version) {
229
+ if (!id)
230
+ throw new Error('INVALID_TOOL_SET_ID');
231
+ const tools = this.discover(query).map((result) => result.manifest);
232
+ const derivedVersion = createHash('sha256').update(JSON.stringify(tools)).digest('hex').slice(0, 16);
233
+ return { id, version: version ?? derivedVersion, tools: structuredClone(tools) };
234
+ }
235
+ async execute(name, input, context) {
236
+ const definition = this.require(name);
237
+ const parsed = this.validateInput(name, input);
238
+ const toolContext = 'aborted' in context ? { toolCallId: '', effectId: '', attemptId: '', agentId: '', laneId: '', signal: context, emit: () => { } } : context;
239
+ const output = await definition.execute(parsed, toolContext);
240
+ if (!validateJsonSchema(output, definition.manifest.outputSchema))
241
+ throw Object.assign(new Error(`Output does not match the manifest for tool ${name}.`), { code: 'TOOL_OUTPUT_SCHEMA_VIOLATION', retryable: false });
242
+ return output;
243
+ }
244
+ async executeDetailed(name, input, context) {
245
+ const definition = this.require(name);
246
+ const parsed = this.validateInput(name, input);
247
+ const toolContext = 'aborted' in context ? { toolCallId: '', effectId: '', attemptId: '', agentId: '', laneId: '', signal: context, emit: () => { } } : context;
248
+ const output = await definition.execute(parsed, toolContext);
249
+ if (!validateJsonSchema(output, definition.manifest.outputSchema))
250
+ throw Object.assign(new Error(`Output does not match the manifest for tool ${name}.`), { code: 'TOOL_OUTPUT_SCHEMA_VIOLATION', retryable: false });
251
+ const summary = definition.summarize?.(output);
252
+ const summaryAllowed = summary === undefined || summaryWithinBudget(summary, definition.manifest.maxResultSummaryBytes ?? 4096);
253
+ const normalized = definition.normalize?.(output);
254
+ if (normalized !== undefined)
255
+ validateStrictJsonValue(normalized, 'TOOL_NORMALIZED_OUTPUT_INVALID', `Normalized output is not JSON-serializable for tool ${name}.`);
256
+ return { output, ...(normalized === undefined ? {} : { normalized }), ...(summaryAllowed && summary !== undefined ? { summary } : {}), manifest: structuredClone(definition.manifest) };
257
+ }
258
+ async reconcileDetailed(name, executionRef, context) {
259
+ const definition = this.require(name);
260
+ if (!definition.reconcile)
261
+ throw new Error(`TOOL_NOT_RECOVERABLE:${name}`);
262
+ const result = await definition.reconcile(executionRef, context);
263
+ if (!result || typeof result !== 'object' || !['succeeded', 'failed', 'cancelled', 'unknown'].includes(result.status))
264
+ throw Object.assign(new Error(`Reconcile returned an invalid result for tool ${name}.`), { code: 'TOOL_RECONCILE_RESULT_INVALID', retryable: false });
265
+ if (result.status === 'succeeded' && !validateJsonSchema(result.output, definition.manifest.outputSchema))
266
+ throw Object.assign(new Error(`Reconcile output does not match the manifest for tool ${name}.`), { code: 'TOOL_RECONCILE_OUTPUT_SCHEMA_VIOLATION', retryable: false });
267
+ if (result.status === 'succeeded' && result.output !== undefined)
268
+ validateStrictJsonValue(result.output, 'TOOL_RECONCILE_OUTPUT_INVALID', `Reconcile output is not JSON-serializable for tool ${name}.`);
269
+ if (result.error !== undefined && (typeof result.error !== 'object' || result.error === null || typeof result.error.code !== 'string' || typeof result.error.message !== 'string' || (result.error.details !== undefined && !isStrictJsonValue(result.error.details))))
270
+ throw Object.assign(new Error(`Reconcile returned an invalid error for tool ${name}.`), { code: 'TOOL_RECONCILE_ERROR_INVALID', retryable: false });
271
+ return result;
272
+ }
273
+ executionRef(name, input, context) {
274
+ const definition = this.require(name);
275
+ const executionRef = definition.executionRef?.(this.validateInput(name, input), context);
276
+ return executionRef === undefined ? undefined : validateStrictJsonValue(executionRef, 'TOOL_EXECUTION_REF_INVALID', `Execution reference is not JSON-serializable for tool ${name}.`);
277
+ }
278
+ resolveResources(name, input) {
279
+ const definition = this.require(name);
280
+ if (definition.resolveResources)
281
+ return validateResolvedLocks(definition.resolveResources(this.validateInput(name, input)), name);
282
+ if (definition.manifest.resources !== undefined)
283
+ return definition.manifest.resources;
284
+ if (definition.manifest.locks.length > 0 || definition.resourceAdmissionMode === 'explicit')
285
+ return definition.manifest.locks;
286
+ if (definition.manifest.sideEffectPolicy === 'write')
287
+ return [{ resource: 'workspace', mode: 'exclusive' }];
288
+ if (definition.manifest.sideEffectPolicy === 'read')
289
+ return [{ resource: 'workspace', mode: 'shared' }];
290
+ if (definition.manifest.sideEffectPolicy === 'external')
291
+ return [{ resource: `external:${name}`, mode: 'exclusive' }];
292
+ return [];
293
+ }
294
+ admission(name, input) {
295
+ const definition = this.require(name);
296
+ const parsed = this.validateInput(name, input);
297
+ return { locks: structuredClone(this.resolveResources(name, parsed)), sideEffectPolicy: definition.manifest.sideEffectPolicy, defaultTimeoutMs: definition.manifest.defaultTimeoutMs, retrySafety: definition.manifest.retrySafety, version: definition.manifest.version };
298
+ }
299
+ require(name) {
300
+ if (!this.isAllowed(name))
301
+ throw new Error(`TOOL_NOT_ALLOWED:${name}`);
302
+ const definition = this.definitions.get(name);
303
+ if (!definition)
304
+ throw new Error(`UNKNOWN_TOOL:${name}`);
305
+ return definition;
306
+ }
307
+ permissionsAllowed(manifest) { return this.permissionReasons(manifest.name).length === 0; }
308
+ }
@@ -0,0 +1,2 @@
1
+ export { validateStep, commitStep } from './validate.js';
2
+ export type { Mutation, ValidationResult, ValidationSuccess, ValidationFailure } from '../core/mutations.js';
@@ -0,0 +1 @@
1
+ export { validateStep, commitStep } from './validate.js';
@@ -0,0 +1,4 @@
1
+ import type { ValidationResult } from '../core/mutations.js';
2
+ import type { RuntimeState, LaneStepOutput } from '../core/types.js';
3
+ export declare function validateStep(state: RuntimeState, laneId: string, output: LaneStepOutput): ValidationResult;
4
+ export declare function commitStep(state: RuntimeState, laneId: string, output: LaneStepOutput): ValidationResult;