@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.
- package/CAPABILITY-MAP.md +14 -2
- package/CHANGELOG.md +12 -0
- package/DELIVERY-BOUNDARIES.md +4 -2
- package/IDE-EXECUTION.md +25 -3
- package/SKILL.md +8 -4
- package/cli.mjs +4 -6
- package/cloud-mutation-authority.mjs +96 -0
- package/connected-cli.mjs +20 -0
- package/ide-command.mjs +1 -1
- package/ide-hook.mjs +24 -10
- package/ide-results.mjs +8 -6
- package/ide-session.mjs +44 -1
- package/local-approved-plan.mjs +3 -21
- package/local-approved-process.mjs +54 -0
- package/local-approved-supervisor.mjs +99 -0
- package/local-cli.mjs +13 -2
- package/local-contracts.mjs +2 -0
- package/local-delegated-modify-analysis.mjs +232 -0
- package/local-discipline-actions.mjs +2 -0
- package/local-discipline-analysis.mjs +11 -5
- package/local-discipline-gate.mjs +3 -1
- package/local-discipline-repository.mjs +7 -4
- package/local-error.mjs +3 -0
- package/local-execution.mjs +1 -1
- package/local-mutation-authority.mjs +23 -5
- package/local-mutation-batch.mjs +1 -0
- package/local-mutation-binding.mjs +14 -5
- package/local-mutation-consumption.mjs +93 -0
- package/local-mutation-pass-store.mjs +6 -4
- package/local-mutation-wire.mjs +21 -4
- package/local-preserved-mutation-protection.mjs +51 -0
- package/local-preserved-mutation-signer.mjs +81 -0
- package/local-preserved-mutation-state.mjs +96 -0
- package/local-preserved-mutation-wire.mjs +49 -0
- package/local-scope-import-expressions.mjs +80 -0
- package/local-scope-repository.mjs +32 -21
- package/local-typescript-function-reference.mjs +98 -0
- package/manifest.json +1 -1
- package/mutation-client.mjs +290 -0
- package/package.json +22 -3
- package/release-state.json +49 -30
- package/release.mjs +25 -0
- package/releases.json +12 -0
- package/remote-client.mjs +2 -1
- package/reuse-discipline-release.mjs +3 -0
- package/workflow-observation-client.mjs +44 -0
- package/workflow-observation-connection.mjs +35 -0
- package/workflow-observation-remote.mjs +84 -0
- package/workflow-observation-runner.mjs +87 -0
- package/workflow-observation-state.mjs +36 -0
- 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 {
|
|
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(`
|
|
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;
|
package/local-contracts.mjs
CHANGED
|
@@ -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 (
|
|
82
|
-
if (ts.
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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.
|
|
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',
|
package/local-execution.mjs
CHANGED
|
@@ -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']);
|