@entrorove/skill 0.1.51 → 0.1.53

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 (51) hide show
  1. package/CAPABILITY-MAP.md +14 -2
  2. package/CHANGELOG.md +12 -0
  3. package/DELIVERY-BOUNDARIES.md +4 -2
  4. package/IDE-EXECUTION.md +25 -3
  5. package/SKILL.md +8 -4
  6. package/cli.mjs +4 -6
  7. package/cloud-mutation-authority.mjs +96 -0
  8. package/connected-cli.mjs +20 -0
  9. package/ide-command.mjs +1 -1
  10. package/ide-hook.mjs +24 -10
  11. package/ide-results.mjs +8 -6
  12. package/ide-session.mjs +44 -1
  13. package/local-approved-plan.mjs +3 -21
  14. package/local-approved-process.mjs +54 -0
  15. package/local-approved-supervisor.mjs +99 -0
  16. package/local-cli.mjs +13 -2
  17. package/local-contracts.mjs +2 -0
  18. package/local-delegated-modify-analysis.mjs +232 -0
  19. package/local-discipline-actions.mjs +2 -0
  20. package/local-discipline-analysis.mjs +11 -5
  21. package/local-discipline-gate.mjs +3 -1
  22. package/local-discipline-repository.mjs +7 -4
  23. package/local-error.mjs +3 -0
  24. package/local-execution.mjs +1 -1
  25. package/local-mutation-authority.mjs +23 -5
  26. package/local-mutation-batch.mjs +1 -0
  27. package/local-mutation-binding.mjs +14 -5
  28. package/local-mutation-consumption.mjs +93 -0
  29. package/local-mutation-pass-store.mjs +6 -4
  30. package/local-mutation-wire.mjs +21 -4
  31. package/local-preserved-mutation-protection.mjs +51 -0
  32. package/local-preserved-mutation-signer.mjs +81 -0
  33. package/local-preserved-mutation-state.mjs +96 -0
  34. package/local-preserved-mutation-wire.mjs +49 -0
  35. package/local-scope-import-expressions.mjs +80 -0
  36. package/local-scope-repository.mjs +32 -21
  37. package/local-typescript-function-reference.mjs +98 -0
  38. package/manifest.json +1 -1
  39. package/mutation-client.mjs +290 -0
  40. package/package.json +22 -3
  41. package/release-state.json +49 -30
  42. package/release.mjs +25 -0
  43. package/releases.json +12 -0
  44. package/remote-client.mjs +2 -1
  45. package/reuse-discipline-release.mjs +3 -0
  46. package/workflow-observation-client.mjs +44 -0
  47. package/workflow-observation-connection.mjs +35 -0
  48. package/workflow-observation-remote.mjs +84 -0
  49. package/workflow-observation-runner.mjs +87 -0
  50. package/workflow-observation-state.mjs +36 -0
  51. package/workflow-observation-wire.mjs +69 -0
@@ -0,0 +1,54 @@
1
+ import { fork } from 'node:child_process';
2
+ import { performance } from 'node:perf_hooks';
3
+
4
+ const SUPERVISOR = new URL('./local-approved-supervisor.mjs', import.meta.url);
5
+ const MESSAGE_SCHEMA = 'entrorove.approved-process-supervision/1.0';
6
+ const RUNNER_SCHEMA = 'entrorove.validator-local-runner/1.0';
7
+
8
+ function launchInput(authority) {
9
+ const now = Date.now(), policy = authority.policy;
10
+ const leaseDeadline = authority.executionDeadlineAt === undefined ? Infinity : Date.parse(authority.executionDeadlineAt);
11
+ const startDeadlineAt = authority.startDeadlineAt === undefined ? null : Date.parse(authority.startDeadlineAt);
12
+ if ((!Number.isFinite(leaseDeadline) && leaseDeadline !== Infinity) || leaseDeadline <= now
13
+ || (startDeadlineAt !== null && (!Number.isFinite(startDeadlineAt) || startDeadlineAt <= now))) {
14
+ throw Object.assign(new Error('workflow execution lease expired before process launch'), { code: 'ENTROROVE_WORKFLOW_EXPIRED' });
15
+ }
16
+ return { schemaVersion: MESSAGE_SCHEMA, root: authority.root, executable: policy.executable, args: policy.args,
17
+ environment: policy.environment, timeoutMs: policy.timeoutMs, maxOutputBytes: policy.maxOutputBytes,
18
+ objectPlan: Boolean(authority.objectPlan), deadlineAt: Math.min(now + policy.timeoutMs, leaseDeadline), startDeadlineAt,
19
+ leaseDeadline: leaseDeadline <= now + policy.timeoutMs };
20
+ }
21
+
22
+ function supervisionError(code, message, effects = 'unknown') {
23
+ return Object.assign(new Error(message), { code,
24
+ execution: { schemaVersion: RUNNER_SCHEMA, status: 'failed', evidence: [], effects } });
25
+ }
26
+
27
+ export async function runApprovedProcess(authority) {
28
+ const started = performance.now(), input = launchInput(authority);
29
+ return new Promise((resolve, reject) => {
30
+ let finished = false;
31
+ const supervisor = fork(SUPERVISOR, [], { execPath: process.execPath, execArgv: [], env: {}, detached: true,
32
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'], serialization: 'json' });
33
+ const finish = (error, result) => {
34
+ if (finished) return; finished = true;
35
+ if (supervisor.connected) supervisor.disconnect();
36
+ if (error) reject(error); else resolve({ ...result, durationMs: Math.max(0, Math.round(performance.now() - started)) });
37
+ };
38
+ supervisor.on('message', message => {
39
+ if (message?.schemaVersion !== MESSAGE_SCHEMA || message.type !== 'result') return;
40
+ if (message.error) {
41
+ const error = Object.assign(new Error(message.error.message), { code: message.error.code });
42
+ if (message.process) error.execution = { schemaVersion: RUNNER_SCHEMA, status: 'failed', process: message.process,
43
+ evidence: [], effects: message.process.started === true ? 'unknown' : 'none' };
44
+ finish(error); return;
45
+ }
46
+ finish(null, message.process);
47
+ });
48
+ supervisor.once('error', () => finish(supervisionError('ENTROROVE_PROCESS_SUPERVISOR', 'Approved process supervision could not start', 'none')));
49
+ supervisor.once('close', () => { if (!finished) finish(supervisionError('ENTROROVE_PROCESS_SUPERVISOR', 'Approved process supervision ended without a result')); });
50
+ supervisor.send(input, error => {
51
+ if (error) finish(supervisionError('ENTROROVE_PROCESS_SUPERVISOR', 'Approved process supervision could not receive its frozen command', 'none'));
52
+ });
53
+ });
54
+ }
@@ -0,0 +1,99 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { performance } from 'node:perf_hooks';
5
+
6
+ const MESSAGE_SCHEMA = 'entrorove.approved-process-supervision/1.0';
7
+ const RUNNER_SCHEMA = 'entrorove.validator-local-runner/1.0';
8
+ const FIELDS = ['schemaVersion', 'root', 'executable', 'args', 'environment', 'timeoutMs', 'maxOutputBytes', 'objectPlan', 'deadlineAt', 'startDeadlineAt', 'leaseDeadline'];
9
+ const ENV = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
+
11
+ function validInput(value) {
12
+ return value && typeof value === 'object' && !Array.isArray(value)
13
+ && Object.keys(value).sort().join('|') === [...FIELDS].sort().join('|') && value.schemaVersion === MESSAGE_SCHEMA
14
+ && typeof value.root === 'string' && path.isAbsolute(value.root) && !value.root.includes('\0')
15
+ && typeof value.executable === 'string' && path.isAbsolute(value.executable) && !value.executable.includes('\0')
16
+ && Array.isArray(value.args) && value.args.length <= 128
17
+ && value.args.every(item => typeof item === 'string' && !item.includes('\0') && item.length <= 4096)
18
+ && value.environment && typeof value.environment === 'object' && !Array.isArray(value.environment)
19
+ && Object.keys(value.environment).length <= 64
20
+ && Object.entries(value.environment).every(([key, item]) => ENV.test(key) && typeof item === 'string' && !item.includes('\0') && item.length <= 16_384)
21
+ && Number.isSafeInteger(value.timeoutMs) && value.timeoutMs > 0 && value.timeoutMs <= 300_000
22
+ && Number.isSafeInteger(value.maxOutputBytes) && value.maxOutputBytes > 0 && value.maxOutputBytes <= 1_048_576
23
+ && typeof value.objectPlan === 'boolean' && typeof value.leaseDeadline === 'boolean'
24
+ && (value.startDeadlineAt === null || Number.isSafeInteger(value.startDeadlineAt))
25
+ && Number.isSafeInteger(value.deadlineAt) && value.deadlineAt <= Date.now() + value.timeoutMs;
26
+ }
27
+
28
+ function resultMessage(processResult, error) {
29
+ return { schemaVersion: MESSAGE_SCHEMA, type: 'result', process: processResult,
30
+ ...(error ? { error: { code: error.code ?? 'ENTROROVE_PROCESS_SUPERVISOR', message: String(error.message).slice(0, 1024) } } : {}) };
31
+ }
32
+
33
+ function deliver(value) {
34
+ if (!process.connected) return;
35
+ process.send(value, () => { if (process.connected) process.disconnect(); });
36
+ }
37
+
38
+ function assertLaunchable(input) {
39
+ if (!process.connected || input.deadlineAt <= Date.now() || input.startDeadlineAt !== null && input.startDeadlineAt <= Date.now()) {
40
+ throw Object.assign(new Error('workflow execution lease expired before process launch'), { code: 'ENTROROVE_WORKFLOW_EXPIRED' });
41
+ }
42
+ }
43
+
44
+ function supervise(input) {
45
+ const started = performance.now(), stdout = [], stderr = [], stdoutHash = createHash('sha256'), stderrHash = createHash('sha256');
46
+ let bytes = 0, dropped = 0, timedOut = false, terminating = false, spawned = false, closed = false;
47
+ let processStartedAt = null, spawnError = null, killTimer;
48
+ assertLaunchable(input);
49
+ const child = spawn(input.executable, input.args, { cwd: input.root, env: { ...input.environment },
50
+ shell: false, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
51
+ const signalGroup = signal => { if (!closed && child.pid) { try { process.kill(-child.pid, signal); } catch {} } };
52
+ const terminate = () => {
53
+ if (terminating || closed) return; terminating = true; signalGroup('SIGTERM');
54
+ killTimer = setTimeout(() => signalGroup('SIGKILL'), 250);
55
+ };
56
+ const consume = (target, hash, chunk) => {
57
+ const data = Buffer.from(chunk); hash.update(data);
58
+ const keep = Math.max(0, Math.min(data.length, input.maxOutputBytes - bytes));
59
+ if (keep) target.push(data.subarray(0, keep)); bytes += keep; dropped += data.length - keep;
60
+ if (dropped) terminate();
61
+ };
62
+ const timer = setTimeout(() => { timedOut = true; terminate(); }, Math.max(1, input.deadlineAt - Date.now()));
63
+ process.once('disconnect', terminate); process.once('SIGTERM', terminate); process.once('SIGINT', terminate);
64
+ child.stdout.on('data', chunk => consume(stdout, stdoutHash, chunk)); child.stderr.on('data', chunk => consume(stderr, stderrHash, chunk));
65
+ child.once('spawn', () => { spawned = true; processStartedAt = new Date().toISOString(); });
66
+ child.once('error', error => { spawnError = error; terminate(); });
67
+ child.once('exit', () => signalGroup('SIGKILL'));
68
+ child.once('close', (exitCode, signal) => {
69
+ closed = true; clearTimeout(timer); clearTimeout(killTimer);
70
+ process.removeListener('disconnect', terminate); process.removeListener('SIGTERM', terminate); process.removeListener('SIGINT', terminate);
71
+ const processResult = { schemaVersion: RUNNER_SCHEMA,
72
+ status: exitCode === 0 && signal === null && !timedOut && !dropped && !spawnError ? 'succeeded' : 'failed',
73
+ exitCode, signal, timedOut, outputLimitExceeded: dropped > 0, durationMs: Math.max(0, Math.round(performance.now() - started)),
74
+ stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8'),
75
+ stdoutSha256: stdoutHash.digest('hex'), stderrSha256: stderrHash.digest('hex'), capturedBytes: bytes, droppedBytes: dropped,
76
+ ...(input.objectPlan ? { started: spawned, startedAt: processStartedAt, finishedAt: new Date().toISOString(),
77
+ executionDeadlineExceeded: timedOut && input.leaseDeadline } : {}) };
78
+ deliver(resultMessage(processResult, spawnError));
79
+ });
80
+ if (!process.connected) terminate();
81
+ }
82
+
83
+ function main() {
84
+ if (process.argv.length !== 2 || typeof process.send !== 'function' || !process.connected) { process.exitCode = 2; return; }
85
+ const startup = setTimeout(() => { if (process.connected) process.disconnect(); }, 5_000);
86
+ const disconnect = () => clearTimeout(startup); process.once('disconnect', disconnect);
87
+ process.once('message', input => {
88
+ clearTimeout(startup); process.removeListener('disconnect', disconnect);
89
+ if (!process.connected) return;
90
+ if (!validInput(input)) {
91
+ deliver(resultMessage(null, { code: 'ENTROROVE_PROCESS_SUPERVISOR', message: 'Approved process supervision requires one bounded frozen command' })); return;
92
+ }
93
+ try { supervise(input); }
94
+ catch (error) { deliver(resultMessage(null, error.code === 'ENTROROVE_WORKFLOW_EXPIRED' ? error
95
+ : { code: 'ENTROROVE_PROCESS_SUPERVISOR', message: 'Approved process supervision could not launch its frozen command' })); }
96
+ });
97
+ }
98
+
99
+ main();
package/local-cli.mjs CHANGED
@@ -28,6 +28,10 @@ import { DISCIPLINE_LOCAL_OPERATIONS } from './local-discipline-operations.mjs';
28
28
  import { assertIdeExecution, settleBreakGlassSession } from './ide-session.mjs';
29
29
  import { runManagedWorkflowLocalStep } from './workflow-local-client.mjs';
30
30
  import { localErrorEnvelope } from './local-error.mjs';
31
+ import { preparePreservedMutationSigner } from './local-preserved-mutation-signer.mjs';
32
+ import { hydrateCloudMutationAuthority } from './cloud-mutation-authority.mjs';
33
+ import { attestMutationConsumption14 } from './local-mutation-consumption.mjs';
34
+ import { apiUrl } from './remote-client.mjs';
31
35
 
32
36
  async function stdinJson() {
33
37
  let raw = '';
@@ -40,6 +44,8 @@ async function stdinJson() {
40
44
  }
41
45
 
42
46
  const operations = {
47
+ 'mutation-consumption-attest': attestMutationConsumption14,
48
+ 'mutation-delegate-prepare': preparePreservedMutationSigner,
43
49
  ...DISCIPLINE_LOCAL_OPERATIONS,
44
50
  'snapshot-plan': snapshotPlan, 'snapshot-verify': snapshotVerify, 'mutation-snapshot-attest': attestMutationSnapshot14, 'mutate-gate': mutateGate,
45
51
  'gate-issue': issueMutationPass, 'gate-verify': verifyMutationPass, 'guarded-write': guardedWrite,
@@ -79,7 +85,11 @@ function capabilitiesFor(group) {
79
85
  }
80
86
 
81
87
  async function checkedLocalInput(operation, input) {
82
- try { return validateLocalInput(operation, input); }
88
+ try {
89
+ const validated = validateLocalInput(operation, input);
90
+ if (['mutation-snapshot-attest', 'mutation-consumption-attest', 'workflow-run-step', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write', 'guarded-delete', 'guarded-move', 'discipline-review', 'discipline-check'].includes(operation)) await hydrateCloudMutationAuthority(input.home, new URL(apiUrl).origin);
91
+ return validated;
92
+ }
83
93
  catch (cause) {
84
94
  if (['guarded-write', 'guarded-delete', 'guarded-move', 'gate-issue', 'mutate-gate', 'mutation-snapshot-attest'].includes(operation)) await recordMutationDenied14(input, cause, operation);
85
95
  throw cause;
@@ -94,9 +104,10 @@ export async function runLocal(args = [], dependencies) {
94
104
  process.stdout.write(`${JSON.stringify(capabilitiesFor(args[1]))}\n`); return;
95
105
  }
96
106
  const handler = operations[operation];
97
- if (!handler) throw new Error(`Usage: entrorove local <${['capabilities', ...Object.keys(operations)].join('|')}> [repositoryRoot]`);
107
+ if (!handler && process.stdin.isTTY) throw new Error(`Unsupported local operation: ${operation}`);
98
108
  const input = await stdinJson();
99
109
  await assertIdeExecution(['local', ...args], input);
110
+ if (!handler) throw new Error(`Usage: entrorove local <${['capabilities', ...Object.keys(operations)].join('|')}> [repositoryRoot]`);
100
111
  if (args[1] && input.repositoryRoot === undefined) input.repositoryRoot = args[1];
101
112
  const validated = await checkedLocalInput(operation, input);
102
113
  const supplied = operation === 'workflow-run-step' || Object.hasOwn(LOCAL_CHAIN_OPERATIONS, operation) ? dependencies : undefined;
@@ -29,6 +29,8 @@ add('flow-cancel|flow-abandon', flowLifecycle, ['repositoryRoot', 'flowId']);
29
29
  add('snapshot-plan', { ...step, paths, snapshotId: identifier, contractDigest: nullable(sha256), handoffCandidate: HANDOFF_SNAPSHOT_CANDIDATE_SCHEMA }, ['repositoryRoot', 'flowId', 'stepId', 'paths']);
30
30
  add('snapshot-verify', { ...step, snapshotId: identifier }, ['repositoryRoot', 'flowId', 'stepId', 'snapshotId']);
31
31
  const gate = { ...step, taskId: identifier, mutationPass: record };
32
+ add('mutation-delegate-prepare', { ...step, taskId: identifier }, ['repositoryRoot', 'home', 'flowId']);
33
+ add('mutation-consumption-attest', { ...step, taskId: identifier, gatePassPath: text }, ['repositoryRoot', 'flowId', 'stepId', 'gatePassPath']);
32
34
  add('mutate-gate|gate-issue', gate, ['repositoryRoot', 'flowId', 'stepId', 'mutationPass']);
33
35
  add('mutation-snapshot-attest', { ...step, taskId: identifier, snapshotId: identifier, challenge: record, signerConfigPath: text },
34
36
  ['repositoryRoot', 'flowId', 'stepId', 'snapshotId', 'challenge', 'signerConfigPath']);
@@ -0,0 +1,232 @@
1
+ import ts from 'typescript';
2
+ import { parse as parseVue, parseCache } from '@vue/compiler-sfc';
3
+ import { canonical } from './local-integrity.mjs';
4
+ import { createScopeImportResolver } from './local-scope-import-expressions.mjs';
5
+ import { isDynamicFunctionReference, isTypeOnlyReference } from './local-typescript-function-reference.mjs';
6
+
7
+ const fail = () => { throw Object.assign(new Error('Preserved modification changed an existing definition, public contract, dependency, or unsupported source boundary'),
8
+ { code: 'ENTROROVE_MUTATION_DELEGATION_CHANGE' }); };
9
+ const printer = ts.createPrinter({ removeComments: true });
10
+ const print = node => node ? printer.printNode(ts.EmitHint.Unspecified, node, node.getSourceFile()) : null;
11
+ function unwrap(node) {
12
+ while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)
13
+ || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node))) node = node.expression;
14
+ return node;
15
+ }
16
+ function names(node) {
17
+ if (!node) return [];
18
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isPrivateIdentifier(node)) return [node.text];
19
+ if (ts.isObjectBindingPattern(node) || ts.isArrayBindingPattern(node)) return node.elements.flatMap(item => ts.isBindingElement(item) ? names(item.name) : []);
20
+ return [print(node)];
21
+ }
22
+ function owner(node) {
23
+ if (node.name) return names(node.name).join(',');
24
+ if (node.parent?.name && (ts.isVariableDeclaration(node.parent) || ts.isPropertyAssignment(node.parent))) return names(node.parent.name).join(',');
25
+ return '<anonymous>';
26
+ }
27
+ function scope(node) {
28
+ const parts = [];
29
+ for (let parent = node.parent; parent && !ts.isSourceFile(parent); parent = parent.parent) {
30
+ if (ts.isFunctionLike(parent) || ts.isClassLike(parent) || ts.isModuleDeclaration(parent)) parts.unshift(`${parent.kind}:${owner(parent)}`);
31
+ }
32
+ return parts.join('/');
33
+ }
34
+ const modifiers = node => node.modifiers?.map(value => value.kind) ?? [];
35
+ function signature(node) {
36
+ return { kind: node.kind, name: node.name ? print(node.name) : null, modifiers: modifiers(node), optional: Boolean(node.questionToken),
37
+ generator: Boolean(node.asteriskToken), types: node.typeParameters?.map(print) ?? [], type: print(node.type),
38
+ parameters: node.parameters?.map(print) ?? [] };
39
+ }
40
+ function exported(node, exportNames) {
41
+ if (names(node.name).some(name => exportNames.has(name))) return true;
42
+ for (let current = node; current && !ts.isSourceFile(current); current = current.parent) {
43
+ if (current !== node && ts.isFunctionLike(current)) return false;
44
+ if (current.modifiers?.some(value => [ts.SyntaxKind.ExportKeyword, ts.SyntaxKind.DefaultKeyword].includes(value.kind))
45
+ || ts.isExportAssignment(current)) return true;
46
+ }
47
+ return false;
48
+ }
49
+ function exportedNames(source) {
50
+ const result = new Set();
51
+ for (const node of source.statements) {
52
+ if (ts.isExportDeclaration(node) && !node.moduleSpecifier && node.exportClause && ts.isNamedExports(node.exportClause)) {
53
+ for (const item of node.exportClause.elements) result.add((item.propertyName ?? item.name).text);
54
+ }
55
+ if (ts.isExportAssignment(node) && ts.isIdentifier(unwrap(node.expression))) result.add(unwrap(node.expression).text);
56
+ }
57
+ const common = node => {
58
+ if (commonJs(node)) {
59
+ const value = unwrap(node.right);
60
+ if (ts.isIdentifier(value)) result.add(value.text);
61
+ if (ts.isObjectLiteralExpression(value)) for (const property of value.properties) {
62
+ if (ts.isShorthandPropertyAssignment(property)) result.add(property.name.text);
63
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(unwrap(property.initializer))) result.add(unwrap(property.initializer).text);
64
+ }
65
+ }
66
+ ts.forEachChild(node, common);
67
+ };
68
+ common(source);
69
+ return result;
70
+ }
71
+ function isDefinition(node) {
72
+ return ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)
73
+ || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isEnumMember(node)
74
+ || ts.isPropertySignature(node) || ts.isMethodSignature(node) || ts.isPropertyDeclaration(node) || ts.isMethodDeclaration(node)
75
+ || ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isGetAccessorDeclaration(node)
76
+ || ts.isSetAccessorDeclaration(node) || ts.isModuleDeclaration(node) || ts.isImportSpecifier(node) || ts.isImportClause(node)
77
+ || ts.isNamespaceImport(node) || ts.isImportEqualsDeclaration(node) || ts.isTypeParameterDeclaration(node);
78
+ }
79
+ const increment = (map, key) => map.set(key, (map.get(key) ?? 0) + 1);
80
+ function variableContract(node) {
81
+ const value = unwrap(node.initializer);
82
+ return { ...signature(node), binding: node.parent.flags & (ts.NodeFlags.Const | ts.NodeFlags.Let),
83
+ callable: value && ts.isFunctionLike(value) ? signature(value) : null,
84
+ alias: value && (ts.isIdentifier(value) || ts.isPropertyAccessExpression(value) || ts.isElementAccessExpression(value)) ? print(value) : null };
85
+ }
86
+ function isDependency(node) {
87
+ return ts.isImportDeclaration(node) || ts.isExportDeclaration(node) || ts.isImportEqualsDeclaration(node) || ts.isImportTypeNode(node)
88
+ || ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
89
+ || ts.isIdentifier(node.expression) && node.expression.text === 'require');
90
+ }
91
+ function dependency(node, source, resolve) {
92
+ const call = ts.isCallExpression(node);
93
+ return { syntax: print(node), alternatives: call ? resolve(node.arguments[0]) : null,
94
+ comments: source.text.slice(node.getStart(source), node.end).match(/\/\*[\s\S]*?\*\//g) ?? [] };
95
+ }
96
+ function commonJs(node) {
97
+ return ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken
98
+ && /^(?:module\.exports|exports)(?:\.[A-Za-z_$][\w$]*|\[[\s\S]+\])?$/.test(print(node.left) ?? '');
99
+ }
100
+ function sourceChecker(source) {
101
+ const filename = `/${source.fileName.replace(/^\/+/, '')}`;
102
+ const matches = value => `/${value.replace(/^\/+/, '')}` === filename;
103
+ return ts.createProgram([filename], { noLib: true, noResolve: true, allowJs: true }, {
104
+ getSourceFile: value => matches(value) ? source : undefined, getDefaultLibFileName: () => '/none.d.ts', writeFile: () => {},
105
+ getCurrentDirectory: () => '/', getDirectories: () => [], fileExists: matches,
106
+ readFile: value => matches(value) ? source.text : undefined, getCanonicalFileName: value => value,
107
+ useCaseSensitiveFileNames: () => true, getNewLine: () => '\n',
108
+ }).getTypeChecker();
109
+ }
110
+ function exportedObject(value) {
111
+ return value.properties.map(property => {
112
+ const initializer = ts.isPropertyAssignment(property) ? unwrap(property.initializer) : property;
113
+ return { name: print(property.name), kind: property.kind,
114
+ alias: ts.isIdentifier(initializer) || ts.isPropertyAccessExpression(initializer) ? print(initializer) : null,
115
+ callable: ts.isFunctionLike(initializer) ? signature(initializer) : null };
116
+ });
117
+ }
118
+ function unknownReference(node, context) {
119
+ if (isTypeOnlyReference(node)) return false;
120
+ if (ts.isIdentifier(node) && (['eval', 'createRequire'].includes(node.text) || isDynamicFunctionReference(node, context))) return true;
121
+ if (ts.isIdentifier(node) && node.text === 'require' && !(ts.isCallExpression(node.parent) && node.parent.expression === node)
122
+ && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)) return true;
123
+ if (ts.isElementAccessExpression(node) && (!ts.isStringLiteralLike(node.argumentExpression)
124
+ || ['Function', 'eval', 'require', 'createRequire'].includes(node.argumentExpression.text))) return true;
125
+ return ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)
126
+ && ['Object', 'Reflect'].includes(node.expression.expression.text) && ['get', 'set', 'defineProperty'].includes(node.expression.name.text);
127
+ }
128
+ function scriptProjection(part) {
129
+ const kind = /\.[jt]sx$/i.test(part.path) ? ts.ScriptKind.TSX : /\.[cm]?js$/i.test(part.path) ? ts.ScriptKind.JS : ts.ScriptKind.TS;
130
+ const source = ts.createSourceFile(part.path, part.content, ts.ScriptTarget.Latest, true, kind);
131
+ if (source.parseDiagnostics.length) fail();
132
+ const result = { dependencies: [], definitions: new Map(), signatures: new Map(), public: [], unknown: new Map() };
133
+ const exports = exportedNames(source), resolve = createScopeImportResolver(source);
134
+ let visited = 0, checker;
135
+ const inferred = node => {
136
+ checker ??= sourceChecker(source);
137
+ return checker.typeToString(checker.getTypeAtLocation(node), node, ts.TypeFormatFlags.NoTruncation);
138
+ };
139
+ const visit = node => {
140
+ if (++visited > 200_000) fail();
141
+ const key = `${scope(node)}/${node.kind}:${names(node.name).join(',')}`;
142
+ if (isDependency(node)) result.dependencies.push(dependency(node, source, resolve));
143
+ if (isDefinition(node)) for (const name of names(node.name)) increment(result.definitions, `${scope(node)}/${node.kind}:${name}`);
144
+ if (ts.isClassExpression(node)) increment(result.definitions, `${scope(node)}/${node.kind}:${owner(node)}`);
145
+ if (ts.isFunctionLike(node)) increment(result.signatures, canonical({ scope: scope(node), signature: signature(node) }));
146
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) increment(result.signatures, canonical({ key, contract: print(node) }));
147
+ if (exported(node, exports) && isDefinition(node)) {
148
+ const contract = ts.isVariableDeclaration(node) ? variableContract(node) : signature(node);
149
+ result.public.push({ key, contract });
150
+ if (ts.isFunctionLike(node) || ts.isVariableDeclaration(node) && (contract.callable || contract.alias)) {
151
+ result.public.push({ key, inferred: inferred(node) });
152
+ }
153
+ if (ts.isPropertyAssignment(node) && ['props', 'emits'].includes(names(node.name)[0])) result.public.push({ key, metadata: print(node.initializer) });
154
+ if (ts.isClassDeclaration(node)) result.public.push({ key, heritage: node.heritageClauses?.map(print) ?? [] });
155
+ }
156
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)
157
+ && ['defineProps', 'defineEmits', 'defineSlots', 'defineModel', 'withDefaults'].includes(node.expression.text)) {
158
+ result.public.push({ macro: print(node) });
159
+ }
160
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'defineExpose') {
161
+ result.public.push({ expose: node.arguments.map(value => ts.isObjectLiteralExpression(value) ? exportedObject(value) : print(value)) });
162
+ }
163
+ if (ts.isExportAssignment(node) && ![ts.SyntaxKind.ObjectLiteralExpression, ts.SyntaxKind.CallExpression,
164
+ ts.SyntaxKind.FunctionExpression, ts.SyntaxKind.ClassExpression, ts.SyntaxKind.ArrowFunction].includes(unwrap(node.expression).kind)) {
165
+ result.public.push({ defaultAlias: print(node.expression), equals: node.isExportEquals });
166
+ }
167
+ if (commonJs(node)) result.public.push({ commonJs: print(node.left), contract: ts.isFunctionLike(unwrap(node.right))
168
+ ? { signature: signature(unwrap(node.right)), inferred: inferred(unwrap(node.right)) }
169
+ : ts.isObjectLiteralExpression(unwrap(node.right)) ? exportedObject(unwrap(node.right)) : print(node.right) });
170
+ if (unknownReference(node, part.context)) increment(result.unknown, print(node));
171
+ ts.forEachChild(node, visit);
172
+ };
173
+ visit(source);
174
+ return result;
175
+ }
176
+ function templateParts(root, path) {
177
+ const parts = [], seen = new WeakSet();
178
+ const walk = node => {
179
+ if (!node || typeof node !== 'object' || seen.has(node)) return;
180
+ seen.add(node);
181
+ if (node.type === 7 && ['on', 'for'].includes(node.name) && node.exp?.content) {
182
+ parts.push({ path: `${path}.ts`, content: node.name === 'for' ? `for (${node.exp.content}) {}` : node.exp.content });
183
+ seen.add(node.exp);
184
+ }
185
+ if (node.type === 4 && !node.isStatic && node.content) parts.push({ path: `${path}.ts`, content: `void (${node.content});` });
186
+ for (const [key, child] of Object.entries(node)) if (!['loc', 'ast', 'parent', 'codegenNode'].includes(key)) {
187
+ if (Array.isArray(child)) child.forEach(walk); else walk(child);
188
+ }
189
+ };
190
+ walk(root);
191
+ return parts;
192
+ }
193
+ function parts(path, content) {
194
+ if (!/\.vue$/i.test(path)) {
195
+ if (!/\.[cm]?[jt]sx?$/i.test(path)) fail();
196
+ return { metadata: [], scripts: [{ path, content }] };
197
+ }
198
+ const parsed = parseVue(content, { filename: path, sourceMap: false, templateParseOptions: { expressionPlugins: ['typescript'] } });
199
+ parseCache.clear();
200
+ if (parsed.errors.length) fail();
201
+ const value = parsed.descriptor, scripts = [];
202
+ const metadata = [value.template?.attrs ?? null, value.styles.map(block => ({ attrs: block.attrs, content: block.content })),
203
+ value.customBlocks.map(block => ({ type: block.type, attrs: block.attrs, content: block.content }))];
204
+ if (value.template?.lang) fail();
205
+ for (const [index, block] of [value.script, value.scriptSetup].entries()) {
206
+ metadata.push(block?.attrs ?? null);
207
+ if (!block) continue;
208
+ const extension = { javascript: 'js', typescript: 'ts' }[block.lang] ?? block.lang ?? 'js';
209
+ if (!['js', 'ts', 'jsx', 'tsx'].includes(extension)) fail();
210
+ scripts.push({ path: `${path}.${index}.${extension}`, content: block.src ? `import ${JSON.stringify(block.src)};` : block.content,
211
+ context: { vueComponent: true, vueSetup: block === value.scriptSetup } });
212
+ }
213
+ scripts.push(...templateParts(value.template?.ast, path));
214
+ return { metadata, scripts };
215
+ }
216
+ function retained(before, after) {
217
+ for (const [key, count] of before) if ((after.get(key) ?? 0) < count) fail();
218
+ }
219
+ export function assertDelegatedSourceModification(input) {
220
+ const { path, before, after } = input ?? {};
221
+ if (typeof path !== 'string' || typeof before !== 'string' || typeof after !== 'string'
222
+ || Buffer.byteLength(before) > 1_048_576 || Buffer.byteLength(after) > 1_048_576) fail();
223
+ try {
224
+ const original = parts(path, before), candidate = parts(path, after);
225
+ if (canonical(original.metadata) !== canonical(candidate.metadata) || original.scripts.length !== candidate.scripts.length) fail();
226
+ for (let index = 0; index < original.scripts.length; index++) {
227
+ const a = scriptProjection(original.scripts[index]), b = scriptProjection(candidate.scripts[index]);
228
+ if (canonical(a.dependencies) !== canonical(b.dependencies) || canonical(a.public) !== canonical(b.public)) fail();
229
+ retained(a.definitions, b.definitions); retained(a.signatures, b.signatures); retained(b.unknown, a.unknown);
230
+ }
231
+ } catch { fail(); }
232
+ }
@@ -11,6 +11,7 @@ import { auditMutation14, withMutationAudit14 } from './local-mutation-audit.mjs
11
11
  import { mutationFail14, removeMutationProjectFile14, moveMutationProjectFile14 } from './local-mutation-files.mjs';
12
12
 
13
13
  function actionIntent(input, value, kind, review) {
14
+ if (value.pass.writeDelegation) mutationFail14('ENTROROVE_MUTATION_DELEGATION_CHANGE', 'Preserved mutation forbids structural file actions');
14
15
  if (!value.pass.disciplinePolicy) mutationFail14('ENTROROVE_DISCIPLINE_NEEDS_HUMAN', `${kind} needs a separately approved original change declaration`);
15
16
  const policy = disciplineApprovedPolicy(value.pass.disciplinePolicy), intent = policy.writes.find(write => write.targetPath === value.target);
16
17
  const { usageReviewDigest, ...supplied } = input.declaration ?? {};
@@ -63,6 +64,7 @@ async function perform(input, kind, original, state) {
63
64
  async function guardedAction(input, kind) {
64
65
  return withMutationAudit14(input, `guarded-${kind}`, async () => {
65
66
  const value = await checkedPass(input), pass = value.pass;
67
+ if (pass.writeDelegation) mutationFail14('ENTROROVE_MUTATION_DELEGATION_CHANGE', 'Preserved mutation forbids structural file actions');
66
68
  return withStateLock(value.locations.consumptionPath, () => withTaskHandoffWriteFence({ ...input,
67
69
  epoch: pass.epoch, passId: pass.passId, leaseId: pass.leaseId }, state => perform(input, kind, value, state)));
68
70
  });
@@ -1,6 +1,8 @@
1
1
  import ts from 'typescript';
2
2
  import { archguardScriptSource } from './local-archguard-ast.mjs';
3
3
  import { canonical, sha256 } from './local-integrity.mjs';
4
+ import { isTypeOnlyReference, isDynamicFunctionReference } from './local-typescript-function-reference.mjs';
5
+ import { createScopeImportResolver } from './local-scope-import-expressions.mjs';
4
6
 
5
7
  export const disciplineAnalysisLimits = Object.freeze({ fileBytes: 1_048_576, sourceBytes: 8_388_608, files: 512, nodes: 200_000 });
6
8
  function analysisLimit(file, reason, limit, required) {
@@ -77,12 +79,15 @@ function definition(source, node) {
77
79
  return { ...location(source, node), symbol, name, exported: Boolean(exported), contract: Boolean(contract),
78
80
  signatureDigest: contract ? sha256(signature) : null };
79
81
  }
80
- function unresolvedNode(node) {
81
- if (ts.isElementAccessExpression(node) && nameOf(node.argumentExpression) === null) return true;
82
- if (ts.isIdentifier(node) && ['eval', 'Function', 'createRequire'].includes(node.text)) return true;
82
+ function unresolvedNode(node, resolve) {
83
+ if (isTypeOnlyReference(node)) return false;
84
+ if (ts.isElementAccessExpression(node) && (nameOf(node.argumentExpression) === null
85
+ || ['eval', 'Function', 'createRequire', 'require'].includes(nameOf(node.argumentExpression)))) return true;
86
+ if (ts.isIdentifier(node) && (['eval', 'createRequire'].includes(node.text) || isDynamicFunctionReference(node))) return true;
83
87
  if (ts.isCallExpression(node)) {
84
88
  if (node.expression.kind === ts.SyntaxKind.ImportKeyword || ts.isIdentifier(node.expression) && node.expression.text === 'require') {
85
- return node.arguments.length !== 1 || !ts.isStringLiteral(node.arguments[0]);
89
+ return !node.arguments.length || node.arguments.length > (node.expression.kind === ts.SyntaxKind.ImportKeyword ? 2 : 1)
90
+ || resolve(node.arguments[0]) === null;
86
91
  }
87
92
  if (ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)
88
93
  && ['Reflect', 'Object'].includes(node.expression.expression.text) && ['get', 'set', 'defineProperty'].includes(node.expression.name.text)) return true;
@@ -148,8 +153,9 @@ export function analyzeDisciplineSources(files, { edges = [], unresolved = [], r
148
153
  const program = programFor(sources, edges), checker = program.getTypeChecker(), definitions = [], mappings = [], references = new Map();
149
154
  const unknown = [...unresolved, ...unsupported];
150
155
  for (const source of sources.values()) {
156
+ const resolve = createScopeImportResolver(source, checker);
151
157
  const visit = node => {
152
- if (unresolvedNode(node)) unknown.push({ ...location(source, node), reason: 'dynamic-reference' });
158
+ if (unresolvedNode(node, resolve)) unknown.push({ ...location(source, node), reason: 'dynamic-reference' });
153
159
  if (symbolNode(node)) {
154
160
  const entry = definition(source, node);
155
161
  if (entry) { const symbol = checker.getSymbolAtLocation(node.name); definitions.push({ entry, symbol, nameNode: node.name }); }
@@ -5,6 +5,7 @@ import { disciplineDuplicateMappings, disciplineFormattingLines } from './local-
5
5
  import { disciplineRepositoryObservation, assertDisciplineRepository } from './local-discipline-repository.mjs';
6
6
  import { readDisciplineSnapshot } from './local-discipline-snapshot.mjs';
7
7
  import { readSnapshotCopy14 } from './local-mutation-snapshot.mjs';
8
+ import { assertDelegatedSourceModification } from './local-delegated-modify-analysis.mjs';
8
9
 
9
10
  const needs = message => mutationFail14('ENTROROVE_DISCIPLINE_NEEDS_HUMAN', message);
10
11
  function declaredWrite(input, pass, baseline) {
@@ -73,11 +74,12 @@ export async function disciplineWritePreflight(input, value, content) {
73
74
  const review = await readDisciplineSnapshot(snapshot), baseline = snapshot.manifest.files.find(file => file.path === target);
74
75
  await assertDisciplineRepository(input, binding.root, review, ledger);
75
76
  const intent = declaredWrite(input, pass, baseline);
77
+ const original = baseline.exists ? (await readSnapshotCopy14(input, snapshot.snapshotRoot, snapshot.manifest, target)).content.toString('utf8') : '';
78
+ if (pass.writeDelegation) assertDelegatedSourceModification({ path: target, before: original, after: content });
76
79
  const candidate = await disciplineRepositoryObservation(input, binding.root, { analyze: true, overrides: new Map([[target, content]]) });
77
80
  const removed = checkRemoved(review, candidate.analysis, target, intent, snapshot.manifest.disciplineReviewDigest, input.declaration);
78
81
  checkContracts(review, candidate.analysis, target, intent);
79
82
  const reuse = checkReuse(review, candidate.analysis, target, intent, pass.parallelPaths);
80
- const original = baseline.exists ? (await readSnapshotCopy14(input, snapshot.snapshotRoot, snapshot.manifest, target)).content.toString('utf8') : '';
81
83
  const formatting = baseline.exists ? disciplineFormattingLines(original, content, target) : [];
82
84
  if (formatting.length) mutationFail14('ENTROROVE_DISCIPLINE_FORMAT_SCOPE', `Formatting touches lines without functional changes: ${target}:${formatting.join(',')}`);
83
85
  return { reviewDigest: snapshot.manifest.disciplineReviewDigest, declaredKind: baseline.exists ? 'modify' : 'create', removed, reuse,
@@ -90,25 +90,28 @@ export async function disciplineRepositoryObservation(input, root, { analyze = f
90
90
  if (!analyze) return { comparison };
91
91
  // The existing resolver supplies actual aliases, package exports and import edges.
92
92
  // Unsupported/dynamic references stay unknown; they are never converted to unused.
93
- const roots = sources.filter(file => paths.includes(file.path) && /\.[cm]?[jt]sx?$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
93
+ const roots = sources.filter(file => paths.includes(file.path) && /\.(?:[cm]?[jt]sx?|vue)$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
94
94
  let graph;
95
95
  try { graph = roots.length ? await importGraph(root, roots, input, disciplineAnalysisLimits) : { edges: [], dynamicSources: [], complete: true }; }
96
96
  catch (error) {
97
97
  if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_SCOPE_LIMIT', 'ENTROROVE_BUDGET_SCOPE'].includes(error.code)) throw error;
98
98
  graph = { edges: [], dynamicSources: [], complete: false }; unresolved.push({ reason: error.code, message: error.message });
99
99
  }
100
- for (const [relative, content] of overrides) if (semanticSource(relative) && /\.[cm]?[jt]sx?$/i.test(relative)) {
100
+ for (const [relative, content] of overrides) if (semanticSource(relative) && /\.(?:[cm]?[jt]sx?|vue)$/i.test(relative)) {
101
101
  try {
102
102
  const candidate = await scopeCandidateImports(root, { path: relative, content }, input);
103
103
  graph.edges = [...graph.edges.filter(edge => edge.from !== relative), ...candidate.edges];
104
- if (candidate.dynamic) unresolved.push({ path: relative, reason: 'candidate-dynamic-import' });
104
+ if (candidate.dynamic) unresolved.push(...(candidate.unresolvedImports?.length ? candidate.unresolvedImports
105
+ : [{ path: relative, reason: 'candidate-dynamic-import' }]));
105
106
  } catch (error) {
106
107
  if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_BUDGET_SCOPE'].includes(error.code)) throw error;
107
108
  unresolved.push({ path: relative, reason: error.code });
108
109
  }
109
110
  }
110
111
  const analysis = analyzeDisciplineSources(sources, { edges: graph.edges, requiredPaths: [...required],
111
- unresolved: [...unresolved, ...graph.dynamicSources.map(item => ({ path: item.path, reason: 'dynamic-import' }))] });
112
+ unresolved: [...unresolved, ...(graph.unresolvedImports ?? []),
113
+ ...graph.dynamicSources.filter(item => !graph.unresolvedImports?.some(site => site.path === item.path))
114
+ .map(item => ({ path: item.path, reason: 'dynamic-import' }))] });
112
115
  analysis.definitions.push(...await nativeDisciplineDefinitions(sources));
113
116
  return { comparison, analysis, scanComplete: graph.complete && analysis.unresolved.length === 0 };
114
117
  }
package/local-error.mjs CHANGED
@@ -13,6 +13,8 @@ export const LOCAL_ERROR_CODE_CATALOG = Object.freeze({
13
13
  ENTROROVE_DISCIPLINE_USAGE_UNKNOWN: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
14
14
  ENTROROVE_DISCIPLINE_REVIEW_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
15
15
  ENTROROVE_DISCIPLINE_DATA_SNAPSHOT_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
16
+ ENTROROVE_MUTATION_DELEGATION_CHANGE: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
17
+ ENTROROVE_MUTATION_DELEGATION_INVALID: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
16
18
  ENTROROVE_IDE_GUARD_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
17
19
  ENTROROVE_IDE_GUARD_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
18
20
  });
@@ -25,6 +27,7 @@ const BLOCKED_CODES = new Set([
25
27
  'ENTROROVE_GIT_UNAVAILABLE',
26
28
  'ENTROROVE_MUTATE_NOT_ACCEPTED', 'ENTROROVE_MUTATION_AUTHORITY_REQUIRED',
27
29
  'ENTROROVE_MUTATION_PASS_INVALID', 'ENTROROVE_MUTATION_PASS_EXPIRED',
30
+ 'ENTROROVE_MUTATION_DELEGATION_CHANGE', 'ENTROROVE_MUTATION_DELEGATION_INVALID',
28
31
  'ENTROROVE_BUDGET_LEASE_EXPIRED', 'ENTROROVE_BUDGET_AUTHORITY_REQUIRED',
29
32
  'ENTROROVE_SWARM_AUTHORITY_INVALID', 'ENTROROVE_SWARM_CLOUD_AUTHORITY_REQUIRED',
30
33
  'ENTROROVE_HANDOFF_FENCE', 'ENTROROVE_HANDOFF_WRITE_REVOKED',
@@ -277,7 +277,7 @@ export async function finalGate(input) {
277
277
  }
278
278
 
279
279
  const LOCAL_OPERATION_GROUPS = Object.freeze(['core', 'budget', 'scope', 'merge', 'chain', 'handoff', 'archguard', 'calctool', 'swarm', 'validator', 'formula', 'evidence']);
280
- const PASS_REQUIRED_OPERATIONS = new Set(['mutation-snapshot-attest', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write',
280
+ const PASS_REQUIRED_OPERATIONS = new Set(['mutation-snapshot-attest', 'mutation-consumption-attest', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write',
281
281
  'discipline-review', 'discipline-check', 'guarded-delete', 'guarded-move',
282
282
  'merge-verified', 'rollback', 'archguard-contract-create', 'archguard-contract-update', 'archguard-checkpoint', 'archguard-scope-request',
283
283
  'budget-handoff', 'budget-transition', 'spawn-bind', 'peer-complete', 'complete']);