@veil-runtime/core 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -21,7 +21,7 @@ Requires Node.js 24+.
21
21
 
22
22
  npm install @veil-runtime/core
23
23
 
24
- This installs the published package. To verify v0.2.0 before publication, see [candidate installation](docs/getting-started/installation.md).
24
+ This installs the published package. To verify the v0.3.0 candidate before publication, see [candidate installation](docs/getting-started/installation.md).
25
25
 
26
26
  ## Smallest working example
27
27
 
@@ -47,7 +47,7 @@ The default authorizer permits reads and denies write/destructive capabilities.
47
47
 
48
48
  ## Status and roadmap
49
49
 
50
- Release candidate: **v0.2.0**. See [release notes](docs/getting-started/v0.2.0.md). The latest published package remains **v0.1.3** until publication. ExecutionPlan v1 is linear. DAGs, parallel execution, conditionals, cancellation, and retry policies are roadmap work, not current behavior.
50
+ Release candidate: **v0.3.0**. See the [v0.3.0 release notes](docs/getting-started/v0.3.0.md). The latest published package remains **v0.2.0** until publication. ExecutionPlan V1 remains the default; opt-in [ExecutionPlan V2](docs/reference/execution-plan-v2.md) adds governed receiving-value ownership. Both are linear. DAGs, parallel execution, conditionals, cancellation, and retry policies are roadmap work, not current behavior.
51
51
 
52
52
  ## Contributing
53
53
 
package/dist/index.d.ts CHANGED
@@ -14,3 +14,5 @@ export type { JobEvent } from './runtime/jobs/job-event.js';
14
14
  export type { RuntimeEvent } from './runtime/events/runtime-event.js';
15
15
  export { McpAdapter } from './integrations/mcp/inbound/mcp-adapter.js';
16
16
  export * from './sdk/index.js';
17
+ export { isPlanAdmissionError } from './runtime/execution/plan-admission-error.js';
18
+ export type { PlanAdmissionError, PlanAdmissionIssue, PlanAdmissionIssueCode } from './runtime/execution/plan-admission-error.js';
package/dist/index.js CHANGED
@@ -14,10 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.McpAdapter = exports.operatorRuntime = exports.OperatorRuntime = void 0;
17
+ exports.isPlanAdmissionError = exports.McpAdapter = exports.operatorRuntime = exports.OperatorRuntime = void 0;
18
18
  var operator_runtime_js_1 = require("./runtime/operator-runtime.js");
19
19
  Object.defineProperty(exports, "OperatorRuntime", { enumerable: true, get: function () { return operator_runtime_js_1.OperatorRuntime; } });
20
20
  Object.defineProperty(exports, "operatorRuntime", { enumerable: true, get: function () { return operator_runtime_js_1.operatorRuntime; } });
21
21
  var mcp_adapter_js_1 = require("./integrations/mcp/inbound/mcp-adapter.js");
22
22
  Object.defineProperty(exports, "McpAdapter", { enumerable: true, get: function () { return mcp_adapter_js_1.McpAdapter; } });
23
23
  __exportStar(require("./sdk/index.js"), exports);
24
+ var plan_admission_error_js_1 = require("./runtime/execution/plan-admission-error.js");
25
+ Object.defineProperty(exports, "isPlanAdmissionError", { enumerable: true, get: function () { return plan_admission_error_js_1.isPlanAdmissionError; } });
@@ -0,0 +1,5 @@
1
+ export declare class GovernedValueError extends Error {
2
+ constructor();
3
+ }
4
+ export declare function captureGovernedValue(value: unknown): unknown;
5
+ export declare function copyGovernedValue(value: unknown, frozen?: boolean): unknown;
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GovernedValueError = void 0;
4
+ exports.captureGovernedValue = captureGovernedValue;
5
+ exports.copyGovernedValue = copyGovernedValue;
6
+ const node_util_1 = require("node:util");
7
+ const MAX_DEPTH = 128;
8
+ const MAX_CONTAINERS = 10_000;
9
+ const MAX_EDGES = 100_000;
10
+ const MAX_STRING_UNITS = 1_048_576;
11
+ class GovernedValueError extends Error {
12
+ constructor() {
13
+ super('Value is not a supported governed representation');
14
+ this.name = 'GovernedValueError';
15
+ }
16
+ }
17
+ exports.GovernedValueError = GovernedValueError;
18
+ function reject() {
19
+ throw new GovernedValueError();
20
+ }
21
+ function primitive(value) {
22
+ if (value === null || value === undefined)
23
+ return true;
24
+ switch (typeof value) {
25
+ case 'boolean':
26
+ case 'string':
27
+ case 'number':
28
+ case 'bigint':
29
+ return true;
30
+ default:
31
+ return false;
32
+ }
33
+ }
34
+ function excludedBrand(value) {
35
+ if (node_util_1.types.isProxy(value))
36
+ return true;
37
+ if (node_util_1.types.isDate(value) ||
38
+ node_util_1.types.isMap(value) ||
39
+ node_util_1.types.isSet(value) ||
40
+ node_util_1.types.isRegExp(value) ||
41
+ node_util_1.types.isBoxedPrimitive(value) ||
42
+ node_util_1.types.isAnyArrayBuffer(value) ||
43
+ node_util_1.types.isArrayBufferView(value) ||
44
+ node_util_1.types.isDataView(value) ||
45
+ node_util_1.types.isWeakMap(value) ||
46
+ node_util_1.types.isWeakSet(value) ||
47
+ node_util_1.types.isPromise(value))
48
+ return true;
49
+ const iteratorTypes = [
50
+ node_util_1.types.isMapIterator,
51
+ node_util_1.types.isSetIterator,
52
+ node_util_1.types.isGeneratorObject,
53
+ ];
54
+ return iteratorTypes.some((check) => check?.(value) === true);
55
+ }
56
+ function inspect(value, counters) {
57
+ if (excludedBrand(value))
58
+ reject();
59
+ const prototype = Object.getPrototypeOf(value);
60
+ const isArray = Array.isArray(value);
61
+ if (isArray && prototype !== Array.prototype)
62
+ reject();
63
+ if (!isArray && prototype !== Object.prototype && prototype !== null)
64
+ reject();
65
+ const keys = Reflect.ownKeys(value);
66
+ const entries = [];
67
+ let length = 0;
68
+ if (isArray) {
69
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
70
+ if (!lengthDescriptor ||
71
+ lengthDescriptor.enumerable ||
72
+ !('value' in lengthDescriptor) ||
73
+ typeof lengthDescriptor.value !== 'number' ||
74
+ !Number.isSafeInteger(lengthDescriptor.value) ||
75
+ lengthDescriptor.value < 0)
76
+ reject();
77
+ length = lengthDescriptor.value;
78
+ }
79
+ for (const key of keys) {
80
+ if (typeof key !== 'string')
81
+ reject();
82
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
83
+ if (!descriptor || !('value' in descriptor))
84
+ reject();
85
+ if (isArray && key === 'length') {
86
+ if (descriptor.enumerable || typeof descriptor.value !== 'number' || descriptor.value !== length)
87
+ reject();
88
+ continue;
89
+ }
90
+ if (!descriptor.enumerable)
91
+ reject();
92
+ if (typeof descriptor.value === 'string')
93
+ counters.stringUnits += descriptor.value.length;
94
+ counters.stringUnits += key.length;
95
+ if (counters.stringUnits > MAX_STRING_UNITS)
96
+ reject();
97
+ if (isArray) {
98
+ const index = Number(key);
99
+ if (!Number.isInteger(index) || index < 0 || String(index) !== key || index >= length)
100
+ reject();
101
+ }
102
+ entries.push({ key, value: descriptor.value });
103
+ }
104
+ if (isArray) {
105
+ if (!Number.isSafeInteger(length) || length < 0)
106
+ reject();
107
+ for (let index = 0; index < length; index += 1) {
108
+ if (!entries.some((entry) => entry.key === String(index)))
109
+ reject();
110
+ }
111
+ counters.edges += entries.length;
112
+ if (counters.edges > MAX_EDGES)
113
+ reject();
114
+ return { target: new Array(length), entries };
115
+ }
116
+ counters.edges += entries.length;
117
+ if (counters.edges > MAX_EDGES)
118
+ reject();
119
+ return {
120
+ target: Object.create(prototype === null ? null : Object.prototype),
121
+ entries,
122
+ };
123
+ }
124
+ function clone(value, freeze) {
125
+ const counters = { containers: 0, edges: 0, stringUnits: 0 };
126
+ const memo = new Map();
127
+ const active = new Set();
128
+ const stack = [];
129
+ const make = (candidate, depth) => {
130
+ if (primitive(candidate)) {
131
+ if (typeof candidate === 'string') {
132
+ counters.stringUnits += candidate.length;
133
+ if (counters.stringUnits > MAX_STRING_UNITS)
134
+ reject();
135
+ }
136
+ return candidate;
137
+ }
138
+ if (typeof candidate !== 'object' || candidate === null)
139
+ reject();
140
+ if (depth > MAX_DEPTH)
141
+ reject();
142
+ const source = candidate;
143
+ const existing = memo.get(source);
144
+ if (existing) {
145
+ if (active.has(source))
146
+ reject();
147
+ return existing;
148
+ }
149
+ counters.containers += 1;
150
+ if (counters.containers > MAX_CONTAINERS)
151
+ reject();
152
+ const inspected = inspect(source, counters);
153
+ memo.set(source, inspected.target);
154
+ active.add(source);
155
+ stack.push({ source, target: inspected.target, entries: inspected.entries, index: 0 });
156
+ return inspected.target;
157
+ };
158
+ const root = make(value, 0);
159
+ while (stack.length > 0) {
160
+ const frame = stack[stack.length - 1];
161
+ if (frame.index >= frame.entries.length) {
162
+ active.delete(frame.source);
163
+ stack.pop();
164
+ continue;
165
+ }
166
+ const entry = frame.entries[frame.index++];
167
+ const child = make(entry.value, stack.length);
168
+ Object.defineProperty(frame.target, entry.key, {
169
+ value: child,
170
+ enumerable: true,
171
+ configurable: true,
172
+ writable: true,
173
+ });
174
+ }
175
+ if (freeze && typeof root === 'object' && root !== null) {
176
+ const pending = [root];
177
+ const seen = new Set();
178
+ while (pending.length > 0) {
179
+ const current = pending.pop();
180
+ if (seen.has(current))
181
+ continue;
182
+ seen.add(current);
183
+ for (const key of Object.keys(current)) {
184
+ const child = current[key];
185
+ if (typeof child === 'object' && child !== null)
186
+ pending.push(child);
187
+ }
188
+ Object.freeze(current);
189
+ }
190
+ }
191
+ return root;
192
+ }
193
+ function captureGovernedValue(value) {
194
+ return clone(value, false);
195
+ }
196
+ function copyGovernedValue(value, frozen = false) {
197
+ return clone(value, frozen);
198
+ }
@@ -0,0 +1,22 @@
1
+ /** Structured admission evidence. Legacy Error.message is not safe wire data. */
2
+ export type PlanAdmissionIssueCode = 'UNSUPPORTED_PLAN_VERSION' | 'EMPTY_PLAN' | 'DUPLICATE_STEP_ID' | 'UNKNOWN_CAPABILITY' | 'CAPABILITY_VERSION_MISMATCH' | 'REQUIRED_INPUT_MISSING' | 'INPUT_TYPE_MISMATCH' | 'UNSUPPORTED_INPUT_SCHEMA' | 'INVALID_RESULT_REFERENCE' | 'RESULT_REFERENCE_NOT_EARLIER';
3
+ export interface PlanAdmissionIssue {
4
+ readonly code: PlanAdmissionIssueCode;
5
+ readonly message: string;
6
+ readonly stepIndex?: number;
7
+ readonly field?: string;
8
+ }
9
+ export interface PlanAdmissionError extends Error {
10
+ readonly code: 'PLAN_ADMISSION_REJECTED';
11
+ readonly issues: readonly PlanAdmissionIssue[];
12
+ }
13
+ export type AdmissionOwner = object;
14
+ /** Local issuance only; current-call certainty requires the direct executePlan boundary. */
15
+ export declare function isPlanAdmissionError(value: unknown): value is PlanAdmissionError;
16
+ export declare function issuePlanAdmissionError(owner: AdmissionOwner, message: string, issues: readonly {
17
+ code: PlanAdmissionIssueCode;
18
+ stepIndex?: number;
19
+ field?: string;
20
+ }[]): PlanAdmissionError;
21
+ /** Prevent a genuine diagnostic from another submission becoming this call's evidence. */
22
+ export declare function containForeignAdmissionError(owner: AdmissionOwner, error: unknown): unknown;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPlanAdmissionError = isPlanAdmissionError;
4
+ exports.issuePlanAdmissionError = issuePlanAdmissionError;
5
+ exports.containForeignAdmissionError = containForeignAdmissionError;
6
+ const messages = {
7
+ UNSUPPORTED_PLAN_VERSION: 'The plan version is not supported.',
8
+ EMPTY_PLAN: 'The plan contains no steps.',
9
+ DUPLICATE_STEP_ID: 'A step identifier is repeated.',
10
+ UNKNOWN_CAPABILITY: 'The requested capability is not registered.',
11
+ CAPABILITY_VERSION_MISMATCH: 'The requested capability version does not match.',
12
+ REQUIRED_INPUT_MISSING: 'A required input field is missing.',
13
+ INPUT_TYPE_MISMATCH: 'An input field has an incompatible type.',
14
+ UNSUPPORTED_INPUT_SCHEMA: 'An input field cannot be validated with the declared schema.',
15
+ INVALID_RESULT_REFERENCE: 'A result reference was not accepted by the admission parser.',
16
+ RESULT_REFERENCE_NOT_EARLIER: 'A result reference must target an earlier declared step.',
17
+ };
18
+ const issued = new WeakMap();
19
+ /** Local issuance only; current-call certainty requires the direct executePlan boundary. */
20
+ function isPlanAdmissionError(value) {
21
+ return typeof value === 'object' && value !== null && issued.has(value);
22
+ }
23
+ function issuePlanAdmissionError(owner, message, issues) {
24
+ const details = Object.freeze(issues.map(issue => Object.freeze({
25
+ code: issue.code,
26
+ message: messages[issue.code],
27
+ ...(issue.stepIndex === undefined ? {} : { stepIndex: issue.stepIndex }),
28
+ ...(issue.field === undefined ? {} : { field: issue.field }),
29
+ })));
30
+ const error = new Error(message);
31
+ Object.defineProperties(error, {
32
+ code: { value: 'PLAN_ADMISSION_REJECTED', writable: false, configurable: false, enumerable: false },
33
+ issues: { value: details, writable: false, configurable: false, enumerable: false },
34
+ });
35
+ issued.set(error, { owner, message });
36
+ return error;
37
+ }
38
+ /** Prevent a genuine diagnostic from another submission becoming this call's evidence. */
39
+ function containForeignAdmissionError(owner, error) {
40
+ if (!isPlanAdmissionError(error))
41
+ return error;
42
+ const record = issued.get(error);
43
+ return record.owner === owner ? error : new Error(record.message, { cause: error });
44
+ }
@@ -1,5 +1,8 @@
1
+ import type { PlanAdmissionIssueCode } from './plan-admission-error.js';
1
2
  import { ExecutionStep } from '../planner/planner.js';
2
3
  export interface PlanValidationError {
4
+ code: PlanAdmissionIssueCode;
5
+ stepIndex?: number;
3
6
  stepId: string;
4
7
  capability: string;
5
8
  field?: string;
@@ -23,6 +23,7 @@ function validateInput(step, capability, allowReferences) {
23
23
  stepId: step.id,
24
24
  capability: step.capability,
25
25
  field,
26
+ code: 'REQUIRED_INPUT_MISSING',
26
27
  message: `Required input '${field}' is missing for capability '${step.capability}'`,
27
28
  });
28
29
  continue;
@@ -36,6 +37,7 @@ function validateInput(step, capability, allowReferences) {
36
37
  stepId: step.id,
37
38
  capability: step.capability,
38
39
  field,
40
+ code: 'UNSUPPORTED_INPUT_SCHEMA',
39
41
  message: `Unsupported schema type '${definition.type}' for capability '${step.capability}'`,
40
42
  });
41
43
  }
@@ -44,6 +46,7 @@ function validateInput(step, capability, allowReferences) {
44
46
  stepId: step.id,
45
47
  capability: step.capability,
46
48
  field,
49
+ code: 'INPUT_TYPE_MISMATCH',
47
50
  message: `Input '${field}' must be of type '${definition.type}' for capability '${step.capability}'`,
48
51
  });
49
52
  }
@@ -68,6 +71,7 @@ function validateStepInput(step, input) {
68
71
  errors: [{
69
72
  stepId: step.id,
70
73
  capability: step.capability,
74
+ code: 'UNKNOWN_CAPABILITY',
71
75
  message: `Unknown capability: ${step.capability}`,
72
76
  }],
73
77
  };
@@ -78,11 +82,13 @@ function validateStepInput(step, input) {
78
82
  function validatePlan(steps) {
79
83
  const errors = [];
80
84
  const seenStepIds = new Set();
81
- for (const step of steps) {
85
+ for (const [stepIndex, step] of steps.entries()) {
86
+ const firstIssue = errors.length;
82
87
  if (seenStepIds.has(step.id)) {
83
88
  errors.push({
84
89
  stepId: step.id,
85
90
  capability: step.capability,
91
+ code: 'DUPLICATE_STEP_ID',
86
92
  message: `Duplicate step ID: ${step.id}`,
87
93
  });
88
94
  }
@@ -91,8 +97,11 @@ function validatePlan(steps) {
91
97
  errors.push({
92
98
  stepId: step.id,
93
99
  capability: step.capability,
100
+ code: 'UNKNOWN_CAPABILITY',
94
101
  message: `Unknown capability: ${step.capability}`,
95
102
  });
103
+ for (let i = firstIssue; i < errors.length; i++)
104
+ errors[i].stepIndex = stepIndex;
96
105
  seenStepIds.add(step.id);
97
106
  continue;
98
107
  }
@@ -100,6 +109,7 @@ function validatePlan(steps) {
100
109
  errors.push({
101
110
  stepId: step.id,
102
111
  capability: step.capability,
112
+ code: 'CAPABILITY_VERSION_MISMATCH',
103
113
  message: `Capability version mismatch for '${step.capability}': requested ${step.capabilityVersion}, registered ${capability.version}`,
104
114
  });
105
115
  }
@@ -111,6 +121,7 @@ function validatePlan(steps) {
111
121
  errors.push({
112
122
  stepId: step.id,
113
123
  capability: step.capability,
124
+ code: 'RESULT_REFERENCE_NOT_EARLIER',
114
125
  message: `Result reference must target an earlier step: ${reference}`,
115
126
  });
116
127
  }
@@ -119,10 +130,13 @@ function validatePlan(steps) {
119
130
  errors.push({
120
131
  stepId: step.id,
121
132
  capability: step.capability,
133
+ code: 'INVALID_RESULT_REFERENCE',
122
134
  message: error instanceof Error ? error.message : 'Invalid result reference',
123
135
  });
124
136
  }
125
137
  }
138
+ for (let i = firstIssue; i < errors.length; i++)
139
+ errors[i].stepIndex = stepIndex;
126
140
  seenStepIds.add(step.id);
127
141
  }
128
142
  return { valid: errors.length === 0, errors };
@@ -9,8 +9,15 @@ function isResultReference(value) {
9
9
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
10
10
  return false;
11
11
  }
12
- const record = value;
13
- return Object.keys(record).length === 1 && typeof record.$ref === 'string';
12
+ const keys = Reflect.ownKeys(value);
13
+ if (keys.length !== 1 || keys[0] !== '$ref') {
14
+ return false;
15
+ }
16
+ const descriptor = Object.getOwnPropertyDescriptor(value, '$ref');
17
+ return descriptor !== undefined
18
+ && descriptor.enumerable === true
19
+ && Object.hasOwn(descriptor, 'value')
20
+ && typeof descriptor.value === 'string';
14
21
  }
15
22
  function parseResultReference(reference) {
16
23
  if (!reference.startsWith(REFERENCE_PREFIX)) {
@@ -1,3 +1,4 @@
1
+ import { AdmissionOwner } from '../execution/plan-admission-error.js';
1
2
  import { ExecutionAuthorizer } from '../permissions/execution-authorizer.js';
2
3
  import { ExecutionPlan } from '../planner/planner.js';
3
4
  import { ExecutionCaller } from '../execution/execution-context.js';
@@ -5,12 +6,12 @@ import { Job, JobOutcome } from './job.js';
5
6
  import { JobStatus } from './job-status.js';
6
7
  import { JobListFilter } from './job-store.js';
7
8
  declare class JobManager {
8
- executePlan(plan: ExecutionPlan, caller?: ExecutionCaller, authorizer?: ExecutionAuthorizer): Promise<Job>;
9
+ executePlan(plan: ExecutionPlan, caller?: ExecutionCaller, authorizer?: ExecutionAuthorizer, admissionOwner?: AdmissionOwner, enabledVersions?: ReadonlySet<string>): Promise<Job>;
9
10
  create(goal: string, planner?: string): Promise<Job>;
10
11
  get(id: string): Promise<Job | undefined>;
11
12
  list(filter?: JobListFilter): Promise<Job[]>;
12
13
  setStatus(id: string, status: JobStatus): Promise<Job>;
13
- execute(id: string, caller?: ExecutionCaller, authorizer?: ExecutionAuthorizer): Promise<Job>;
14
+ execute(id: string, caller?: ExecutionCaller, authorizer?: ExecutionAuthorizer, semanticVersion?: '1.0' | '2.0'): Promise<Job>;
14
15
  review(id: string, outcome: JobOutcome, notes?: string): Promise<Job>;
15
16
  private addEvent;
16
17
  }
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jobManager = void 0;
4
+ const plan_admission_error_js_1 = require("../execution/plan-admission-error.js");
4
5
  const node_crypto_1 = require("node:crypto");
5
6
  const registry_js_1 = require("../registry/registry.js");
6
7
  const execution_authorizer_js_1 = require("../permissions/execution-authorizer.js");
7
8
  const plan_validator_js_1 = require("../execution/plan-validator.js");
8
9
  const result_reference_js_1 = require("../execution/result-reference.js");
10
+ const governed_value_js_1 = require("../execution/governed-value.js");
9
11
  const memory_event_bus_js_1 = require("../events/memory-event-bus.js");
10
12
  const job_store_js_1 = require("./job-store.js");
11
13
  const console_execution_logger_js_1 = require("../execution/console-execution-logger.js");
@@ -13,7 +15,11 @@ const console_log_sink_js_1 = require("../logging/console-log-sink.js");
13
15
  const composite_log_sink_js_1 = require("../logging/composite-log-sink.js");
14
16
  const sqlite_log_sink_js_1 = require("../logging/sqlite-log-sink.js");
15
17
  class JobManager {
16
- async executePlan(plan, caller, authorizer = execution_authorizer_js_1.defaultExecutionAuthorizer) {
18
+ async executePlan(plan, caller, authorizer = execution_authorizer_js_1.defaultExecutionAuthorizer, admissionOwner = {}, enabledVersions = new Set(['1.0'])) {
19
+ const version = plan.version;
20
+ if ((version !== '1.0' && version !== '2.0') || !enabledVersions.has(version)) {
21
+ throw (0, plan_admission_error_js_1.issuePlanAdmissionError)(admissionOwner, "Execution plan version is not supported; expected '1.0'.", [{ code: 'UNSUPPORTED_PLAN_VERSION' }]);
22
+ }
17
23
  // Own the structural envelope before admission; nested input remains shared.
18
24
  const capturedGoal = plan.goal;
19
25
  const idempotencyKey = plan.idempotencyKey;
@@ -33,13 +39,13 @@ class JobManager {
33
39
  };
34
40
  }
35
41
  if (!steps.length) {
36
- throw new Error('Execution plan contains no steps');
42
+ throw (0, plan_admission_error_js_1.issuePlanAdmissionError)(admissionOwner, 'Execution plan contains no steps', [{ code: 'EMPTY_PLAN' }]);
37
43
  }
38
44
  const validation = (0, plan_validator_js_1.validatePlan)(steps);
39
45
  if (!validation.valid) {
40
- throw new Error(`Execution plan failed validation: ${validation.errors
46
+ throw (0, plan_admission_error_js_1.issuePlanAdmissionError)(admissionOwner, `Execution plan failed validation: ${validation.errors
41
47
  .map((error) => error.message)
42
- .join('; ')}`);
48
+ .join('; ')}`, validation.errors);
43
49
  }
44
50
  const goal = capturedGoal?.trim() ||
45
51
  'External execution plan';
@@ -53,7 +59,7 @@ class JobManager {
53
59
  job.updatedAt =
54
60
  new Date().toISOString();
55
61
  await job_store_js_1.jobStore.update(job);
56
- return this.execute(job.id, caller, authorizer);
62
+ return this.execute(job.id, caller, authorizer, version);
57
63
  }
58
64
  async create(goal, planner) {
59
65
  if (!goal?.trim()) {
@@ -92,7 +98,7 @@ class JobManager {
92
98
  await job_store_js_1.jobStore.update(job);
93
99
  return job;
94
100
  }
95
- async execute(id, caller, authorizer = execution_authorizer_js_1.defaultExecutionAuthorizer) {
101
+ async execute(id, caller, authorizer = execution_authorizer_js_1.defaultExecutionAuthorizer, semanticVersion = '1.0') {
96
102
  const job = await job_store_js_1.jobStore.get(id);
97
103
  if (!job) {
98
104
  throw new Error(`Job not found: ${id}`);
@@ -112,24 +118,32 @@ class JobManager {
112
118
  }
113
119
  try {
114
120
  const completedSteps = job.steps.slice(0, job.steps.indexOf(step));
121
+ // ADR-0011 begins after the existing resolver. Reference traversal
122
+ // may retain its legacy getter/Proxy behavior; capture governs only
123
+ // the representation returned by that resolver.
115
124
  const resolvedInput = (0, result_reference_js_1.resolveResultReferences)(step.input, completedSteps);
116
- const inputValidation = (0, plan_validator_js_1.validateStepInput)(step, resolvedInput);
125
+ const governedInput = semanticVersion === '2.0'
126
+ ? (0, governed_value_js_1.captureGovernedValue)(resolvedInput)
127
+ : resolvedInput;
128
+ const inputValidation = (0, plan_validator_js_1.validateStepInput)(step, governedInput);
117
129
  if (!inputValidation.valid) {
118
130
  throw new Error(inputValidation.errors
119
131
  .map((validationError) => validationError.message)
120
132
  .join('; '));
121
133
  }
122
- const authorization = await authorizer.authorize({
123
- jobId: job.id,
124
- stepId: step.id,
125
- capability: {
126
- name: capability.name,
127
- version: capability.version,
128
- risk: capability.risk,
129
- },
130
- input: resolvedInput,
131
- caller,
132
- });
134
+ const authorization = semanticVersion === '2.0'
135
+ ? await authorizer.authorize(createGovernedAuthorizationContext(job.id, step.id, capability, (0, governed_value_js_1.copyGovernedValue)(governedInput, true), caller))
136
+ : await authorizer.authorize({
137
+ jobId: job.id,
138
+ stepId: step.id,
139
+ capability: {
140
+ name: capability.name,
141
+ version: capability.version,
142
+ risk: capability.risk,
143
+ },
144
+ input: resolvedInput,
145
+ caller,
146
+ });
133
147
  if (typeof authorization !== 'object' ||
134
148
  authorization === null ||
135
149
  Array.isArray(authorization) ||
@@ -158,6 +172,11 @@ class JobManager {
158
172
  });
159
173
  throw new AuthorizationDeniedError(message);
160
174
  }
175
+ // ADR-0011 requires the detached v2 entry value to exist before the
176
+ // running/start transition. V1 retains its historical shared input.
177
+ const capabilityInput = semanticVersion === '2.0'
178
+ ? (0, governed_value_js_1.copyGovernedValue)(governedInput)
179
+ : resolvedInput;
161
180
  step.status = 'running';
162
181
  step.startedAt = new Date().toISOString();
163
182
  this.addEvent(job, 'capability.started', {
@@ -168,7 +187,7 @@ class JobManager {
168
187
  new console_log_sink_js_1.ConsoleLogSink(),
169
188
  new sqlite_log_sink_js_1.SQLiteLogSink(),
170
189
  ]));
171
- const result = await capability.execute(resolvedInput, {
190
+ const result = await capability.execute(capabilityInput, {
172
191
  jobId: job.id,
173
192
  stepId: step.id,
174
193
  logger,
@@ -266,6 +285,25 @@ class JobManager {
266
285
  return event;
267
286
  }
268
287
  }
288
+ function createGovernedAuthorizationContext(jobId, stepId, capability, input, caller) {
289
+ const context = {
290
+ jobId,
291
+ stepId,
292
+ capability: {
293
+ name: capability.name,
294
+ version: capability.version,
295
+ risk: capability.risk,
296
+ },
297
+ caller,
298
+ };
299
+ Object.defineProperty(context, 'input', {
300
+ value: input,
301
+ enumerable: true,
302
+ configurable: false,
303
+ writable: false,
304
+ });
305
+ return context;
306
+ }
269
307
  class AuthorizationDeniedError extends Error {
270
308
  }
271
309
  exports.jobManager = new JobManager();
@@ -15,9 +15,11 @@ export interface ExecutePlanOptions {
15
15
  }
16
16
  export interface OperatorRuntimeOptions {
17
17
  readonly authorizer?: ExecutionAuthorizer;
18
+ readonly planVersions?: readonly string[];
18
19
  }
19
20
  export declare class OperatorRuntime {
20
21
  private readonly authorizer;
22
+ private readonly planVersions;
21
23
  constructor(options?: OperatorRuntimeOptions);
22
24
  use(module: CapabilityModule): void;
23
25
  executePlan(plan: ExecutionPlan, options?: ExecutePlanOptions): Promise<Job>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.operatorRuntime = exports.OperatorRuntime = void 0;
4
+ const plan_admission_error_js_1 = require("./execution/plan-admission-error.js");
4
5
  const job_manager_js_1 = require("./jobs/job-manager.js");
5
6
  const job_memory_js_1 = require("./jobs/job-memory.js");
6
7
  const execution_authorizer_js_1 = require("./permissions/execution-authorizer.js");
@@ -23,10 +24,19 @@ function immutableCaller(caller) {
23
24
  }
24
25
  class OperatorRuntime {
25
26
  authorizer;
27
+ planVersions;
26
28
  constructor(options = {}) {
27
29
  this.authorizer =
28
30
  options.authorizer ??
29
31
  execution_authorizer_js_1.defaultExecutionAuthorizer;
32
+ const versions = options.planVersions ?? ['1.0'];
33
+ if (!Array.isArray(versions) ||
34
+ versions.length === 0 ||
35
+ versions.some((version) => version !== '1.0' && version !== '2.0') ||
36
+ new Set(versions).size !== versions.length) {
37
+ throw new Error('planVersions must contain unique supported semantic versions');
38
+ }
39
+ this.planVersions = new Set(versions);
30
40
  }
31
41
  use(module) {
32
42
  const declared = new Set(module.manifest.capabilities);
@@ -38,7 +48,13 @@ class OperatorRuntime {
38
48
  }
39
49
  }
40
50
  async executePlan(plan, options = {}) {
41
- return job_manager_js_1.jobManager.executePlan(plan, immutableCaller(options.caller), this.authorizer);
51
+ const admissionOwner = {};
52
+ try {
53
+ return await job_manager_js_1.jobManager.executePlan(plan, immutableCaller(options.caller), this.authorizer, admissionOwner, this.planVersions);
54
+ }
55
+ catch (error) {
56
+ throw (0, plan_admission_error_js_1.containForeignAdmissionError)(admissionOwner, error);
57
+ }
42
58
  }
43
59
  async run(goal, options = {}) {
44
60
  const router = planner_router_registry_js_1.plannerRouterRegistry.getDefault();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veil-runtime/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A governed, capability-driven execution runtime",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -63,7 +63,7 @@
63
63
  "better-sqlite3": "^13.0.3"
64
64
  },
65
65
  "devDependencies": {
66
- "@babel/parser": "8.0.5",
66
+ "@babel/parser": "8.0.6",
67
67
  "@types/better-sqlite3": "^9.6.0",
68
68
  "@types/node": "^26.2.0",
69
69
  "fastify": "^5.11.3",