@entrorove/skill 0.1.52 → 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 +7 -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-gate.mjs +3 -1
- 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/manifest.json +1 -1
- package/mutation-client.mjs +290 -0
- package/package.json +19 -2
- package/release-state.json +43 -26
- package/release.mjs +25 -0
- package/releases.json +6 -0
- package/remote-client.mjs +2 -1
- 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,290 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
3
|
+
import { withStateLock } from './local-state-lock.mjs';
|
|
4
|
+
import { mutationId14, mutationPaths14, mutationTarget14, mutationFail14, privateMutationDirectory14,
|
|
5
|
+
readMutationJson14, atomicMutationJson14 } from './local-mutation-files.mjs';
|
|
6
|
+
import { preparePreservedMutationSigner, preservedMutationLocation } from './local-preserved-mutation-signer.mjs';
|
|
7
|
+
import { snapshotPlan14, snapshotVerify14 } from './local-mutation-snapshot.mjs';
|
|
8
|
+
import { attestMutationSnapshot14 } from './local-mutation-binding.mjs';
|
|
9
|
+
import { issueMutationPass14 } from './local-mutation-authority.mjs';
|
|
10
|
+
import { readStoredMutationPass14 } from './local-mutation-pass-store.mjs';
|
|
11
|
+
import { mutationChallenge14, mutationPass14 } from './local-mutation-wire.mjs';
|
|
12
|
+
import { buildInvocation, invokeRemoteResult } from './remote-client.mjs';
|
|
13
|
+
import { parseTransportResponse, requestTransport, trustedOrigin } from './remote-transport.mjs';
|
|
14
|
+
import { acknowledgeAutomaticFeedback, verifyAutomaticFeedback, queryAutomaticFeedback } from './automatic-feedback-client.mjs';
|
|
15
|
+
import { ideClientHeaders } from './ide-presentation.mjs';
|
|
16
|
+
import { appendInvocation, readInvocations } from './local-invocations.mjs';
|
|
17
|
+
import { bootstrapCloudMutationAuthority } from './cloud-mutation-authority.mjs';
|
|
18
|
+
import { attestMutationConsumption14, preservedMutationConsumptionState } from './local-mutation-consumption.mjs';
|
|
19
|
+
|
|
20
|
+
const schema = 'entrorove.mutation-connection-state/1.0';
|
|
21
|
+
const fields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation', 'runnerId', 'paths'];
|
|
22
|
+
const fail = message => mutationFail14('ENTROROVE_MUTATION_CONNECTION_BINDING', message);
|
|
23
|
+
const unknown = requestId => Object.assign(new Error(`Mutation connection outcome is unknown; recover original requestId=${requestId} without repeating its POST`),
|
|
24
|
+
{ outcomeUnknown: true, requestId });
|
|
25
|
+
const notSent = requestId => Object.assign(new Error('Mutation operation was not transmitted; repair its local or discovery prerequisite and reuse the original input'),
|
|
26
|
+
{ code: 'ENTROROVE_MUTATION_CONNECTION_NOT_SENT', requestId, knownBeforeTransmission: true });
|
|
27
|
+
const save = (location, value) => atomicMutationJson14(location.file, value, { privateFrom: location.stateRoot, replace: true });
|
|
28
|
+
function inputIdentity(input) {
|
|
29
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)
|
|
30
|
+
|| canonical(Object.keys(input).sort()) !== canonical([...fields, ...['previousPassId'].filter(field => Object.hasOwn(input, field))].sort())
|
|
31
|
+
|| !Number.isSafeInteger(input.generation) || input.generation < 1) {
|
|
32
|
+
mutationFail14('ENTROROVE_MUTATION_CONNECTION_INPUT', 'Mutation connection requires its exact original task, flow, step, lease, generation, runner and existing paths');
|
|
33
|
+
}
|
|
34
|
+
for (const field of fields.filter(field => !['generation', 'paths'].includes(field))) mutationId14(input[field], field);
|
|
35
|
+
if (Object.hasOwn(input, 'previousPassId')) mutationId14(input.previousPassId, 'previousPassId');
|
|
36
|
+
return { ...input, paths: mutationPaths14(input.paths) };
|
|
37
|
+
}
|
|
38
|
+
async function readConnection(location, identity) {
|
|
39
|
+
let value;
|
|
40
|
+
try { value = await readMutationJson14(location.file); }
|
|
41
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
42
|
+
if (!value) return { schemaVersion: schema, identity };
|
|
43
|
+
if (value.schemaVersion !== schema || canonical(value.identity) !== canonical(identity)
|
|
44
|
+
|| value.pending && (value.pending.requestId !== buildInvocation(value.pending.operation, value.pending.input).requestId
|
|
45
|
+
|| !['mutation-snapshot-challenge', 'mutation-pass-issue', 'mutation-pass-settle'].includes(value.pending.operation))) {
|
|
46
|
+
fail('Mutation connection journal differs from the original repository, request or physical key');
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
async function recoverRemote(pending, dependencies) {
|
|
51
|
+
try {
|
|
52
|
+
const token = await dependencies.readCredential(dependencies.home), origin = trustedOrigin(dependencies.apiOrigin());
|
|
53
|
+
const response = await requestTransport(origin, `/api/skill/status/${pending.requestId}`, { headers: { authorization: `Bearer ${token}`, ...ideClientHeaders() } });
|
|
54
|
+
const result = await parseTransportResponse(response, 'original mutation connection status');
|
|
55
|
+
if (!response.ok || result?.output?.requestId !== pending.requestId || ['running', 'pending'].includes(result.output.status)) throw unknown(pending.requestId);
|
|
56
|
+
result.feedback ??= await queryAutomaticFeedback(pending.requestId, dependencies);
|
|
57
|
+
verifyAutomaticFeedback(result.feedback, pending.requestId, pending.requestId);
|
|
58
|
+
result.feedback = await acknowledgeAutomaticFeedback(result.feedback, origin, token, ideClientHeaders());
|
|
59
|
+
await appendInvocation({ kind: 'remote', operation: 'status', requestId: pending.requestId, event: 'status', usage: result.usage ?? [] }, dependencies.home);
|
|
60
|
+
return { response, result };
|
|
61
|
+
} catch { throw unknown(pending.requestId); }
|
|
62
|
+
}
|
|
63
|
+
async function knownNotSent(error, pending, home) {
|
|
64
|
+
if (error?.requestId && error.requestId !== pending.requestId || error?.retryableBeforeTransmission === true) return true;
|
|
65
|
+
try {
|
|
66
|
+
const records = (await readInvocations(home)).filter(value => value.kind === 'remote' && value.requestId === pending.requestId);
|
|
67
|
+
return !records.length || records.at(-1).event === 'not-sent';
|
|
68
|
+
} catch (cause) { return cause.code === 'ENOENT'; }
|
|
69
|
+
}
|
|
70
|
+
async function remote(stage, operation, input, state, location, dependencies) {
|
|
71
|
+
const requestId = buildInvocation(operation, input).requestId;
|
|
72
|
+
let response, result;
|
|
73
|
+
if (state.pending) {
|
|
74
|
+
if (state.pending.stage !== stage || state.pending.operation !== operation || canonical(state.pending.input) !== canonical(input)) {
|
|
75
|
+
fail('Recover the original mutation request before another operation or input');
|
|
76
|
+
}
|
|
77
|
+
({ response, result } = await recoverRemote(state.pending, dependencies));
|
|
78
|
+
} else {
|
|
79
|
+
state.pending = { stage, operation, input, requestId };
|
|
80
|
+
try { await save(location, state); } catch { throw notSent(requestId); }
|
|
81
|
+
try { ({ response, result } = await invokeRemoteResult({ operation, input }, dependencies)); }
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (await knownNotSent(error, state.pending, dependencies.home)) {
|
|
84
|
+
delete state.pending;
|
|
85
|
+
try { await save(location, state); } catch { throw notSent(requestId); }
|
|
86
|
+
throw notSent(requestId);
|
|
87
|
+
}
|
|
88
|
+
throw unknown(requestId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
delete state.pending;
|
|
92
|
+
if (!response.ok || !['succeeded', 'reported'].includes(result?.output?.status)) {
|
|
93
|
+
try { await save(location, state); } catch { throw unknown(requestId); }
|
|
94
|
+
throw Object.assign(new Error(result?.output?.message ?? `Mutation connection blocked at ${operation}`),
|
|
95
|
+
{ code: result?.output?.code ?? 'ENTROROVE_MUTATION_CONNECTION_BLOCKED', output: result?.output, requestId });
|
|
96
|
+
}
|
|
97
|
+
state[stage] = result.output;
|
|
98
|
+
if (result.feedback) state.feedback = result.feedback;
|
|
99
|
+
try { await save(location, state); } catch { throw unknown(requestId); }
|
|
100
|
+
return result.output;
|
|
101
|
+
}
|
|
102
|
+
function challengeBinding(challenge, identity, mutator, options) {
|
|
103
|
+
mutationChallenge14(challenge, options);
|
|
104
|
+
if ([...fields, 'previousPassId'].some(field => canonical(challenge[field]) !== canonical(identity[field])) || challenge.repositoryDigest !== sha256(identity.repositoryRoot).slice(0, 32)
|
|
105
|
+
|| challenge.newFilePaths.length || !challenge.writeDelegation
|
|
106
|
+
|| challenge.writeDelegation.mutationKeyId !== mutator.mutationKeyId || challenge.writeDelegation.mutationPublicKey !== mutator.mutationPublicKey) {
|
|
107
|
+
fail('Mutation challenge must retain the original task, lease, physical repository, paths and separate M signer');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function passBinding(pass, challenge, attestation, options) {
|
|
111
|
+
mutationPass14(pass, options);
|
|
112
|
+
if (pass.challengeId !== challenge.challengeId || pass.challengeDigest !== sha256(canonical(challenge))
|
|
113
|
+
|| pass.attestationDigest !== sha256(canonical(attestation)) || pass.snapshotDigest !== attestation.manifest.snapshotDigest
|
|
114
|
+
|| [...fields, 'previousPassId'].some(field => canonical(pass[field]) !== canonical(challenge[field]))
|
|
115
|
+
|| canonical(pass.writeDelegation) !== canonical(challenge.writeDelegation)) fail('Mutation pass differs from its original signed challenge or actual M snapshot');
|
|
116
|
+
}
|
|
117
|
+
async function snapshot(input, challenge, state, location) {
|
|
118
|
+
if (!state.planned) {
|
|
119
|
+
if (state.planStarted) {
|
|
120
|
+
const recovered = await snapshotVerify14(input);
|
|
121
|
+
state.planned = { snapshotId: recovered.receipt.snapshotId, snapshotDigest: recovered.receipt.snapshotDigest };
|
|
122
|
+
} else {
|
|
123
|
+
state.planStarted = true; await save(location, state);
|
|
124
|
+
try { state.planned = await snapshotPlan14({ ...input, paths: challenge.paths, contractDigest: challenge.scopeContractDigest }); }
|
|
125
|
+
catch (error) { delete state.planStarted; await save(location, state); throw error; }
|
|
126
|
+
}
|
|
127
|
+
await save(location, state);
|
|
128
|
+
}
|
|
129
|
+
const verified = await snapshotVerify14(input);
|
|
130
|
+
if (verified.verified !== true || verified.receipt.snapshotDigest !== state.planned.snapshotDigest
|
|
131
|
+
|| canonical(verified.receipt.paths) !== canonical(challenge.paths) || verified.receipt.contractDigest !== challenge.scopeContractDigest) {
|
|
132
|
+
fail('Original mutation snapshot copies or paths failed their actual verification');
|
|
133
|
+
}
|
|
134
|
+
return verified;
|
|
135
|
+
}
|
|
136
|
+
async function authorizeCachedConnection(identity, pass, dependencies) {
|
|
137
|
+
const token = await dependencies.readCredential(dependencies.home), origin = trustedOrigin(dependencies.apiOrigin());
|
|
138
|
+
let response, status;
|
|
139
|
+
try {
|
|
140
|
+
response = await requestTransport(origin, `/api/skill/flows/${identity.flowId}/status`, {
|
|
141
|
+
headers: { authorization: `Bearer ${token}`, ...ideClientHeaders() },
|
|
142
|
+
});
|
|
143
|
+
status = await parseTransportResponse(response, 'original mutation flow status');
|
|
144
|
+
} catch {
|
|
145
|
+
mutationFail14('ENTROROVE_MUTATION_CONNECTION_AUTHORIZATION', 'Current account could not confirm the original active mutation flow');
|
|
146
|
+
}
|
|
147
|
+
if (!response.ok || status?.schemaVersion !== 'entrorove.flow-status/1.0' || status.flow?.flowId !== identity.flowId
|
|
148
|
+
|| status.flow.ownerId !== pass.ownerId || status.flow.stepId !== identity.stepId || status.flow.status !== 'running'
|
|
149
|
+
|| !['execute', 'merge', 'deliver'].includes(status.workflow?.phase) || status.workflow.terminalAt
|
|
150
|
+
|| ['pending', 'declined'].includes(status.workflow.authorization)) {
|
|
151
|
+
mutationFail14('ENTROROVE_MUTATION_CONNECTION_AUTHORIZATION', 'Current account must retain the original owned active flow and business step');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function connect(identity, local, mutator, location, dependencies) {
|
|
155
|
+
const state = await readConnection(location, identity);
|
|
156
|
+
const challenged = state.challenged ?? await remote('challenged', 'mutation-snapshot-challenge',
|
|
157
|
+
{ ...Object.fromEntries(fields.map(field => [field, identity[field]])),
|
|
158
|
+
...(identity.previousPassId ? { previousPassId: identity.previousPassId } : {}), mutationPublicKey: mutator.mutationPublicKey }, state, location, dependencies);
|
|
159
|
+
const challenge = challenged.challenge;
|
|
160
|
+
challengeBinding(challenge, identity, mutator, { live: !state.gatePassPath });
|
|
161
|
+
const input = { ...local, snapshotId: challenge.snapshotId };
|
|
162
|
+
if (state.gatePassPath) {
|
|
163
|
+
passBinding(state.issued.pass, challenge, state.attestation, { live: false });
|
|
164
|
+
const stored = await readStoredMutationPass14({ ...input, gatePassPath: state.gatePassPath }, { live: false });
|
|
165
|
+
if (canonical(stored) !== canonical(state.issued.pass)) fail('Installed mutation pass differs from its original connection');
|
|
166
|
+
await authorizeCachedConnection(identity, stored, dependencies);
|
|
167
|
+
const consumption = await preservedMutationConsumptionState({ ...input, gatePassPath: state.gatePassPath });
|
|
168
|
+
if (consumption.complete) return { schemaVersion: 'entrorove.mutation-connection/1.0', status: 'blocked', flowId: identity.flowId,
|
|
169
|
+
code: 'ENTROROVE_MUTATION_SETTLEMENT_REQUIRED', gatePassPath: state.gatePassPath,
|
|
170
|
+
message: 'Original physical writes are consumed; settle their actual M receipt with fresh S evidence before requesting the next restricted pass',
|
|
171
|
+
localAction: { command: 'npx @entrorove/skill mutation-settle <absolute repositoryRoot>',
|
|
172
|
+
input: { taskId: stored.taskId, flowId: stored.flowId, stepId: stored.stepId, gatePassPath: state.gatePassPath } } };
|
|
173
|
+
passBinding(state.issued.pass, challenge, state.attestation);
|
|
174
|
+
return connected(identity, state);
|
|
175
|
+
}
|
|
176
|
+
await snapshot(input, challenge, state, location);
|
|
177
|
+
if (!state.attestation) {
|
|
178
|
+
const observed = await attestMutationSnapshot14({ ...input, challenge, signerConfigPath: mutator.signerConfigPath });
|
|
179
|
+
if (observed.verified !== true || observed.attestation.keyId !== mutator.mutationKeyId) fail('Actual mutation snapshot was not signed by its separate M key');
|
|
180
|
+
state.attestation = observed.attestation; await save(location, state);
|
|
181
|
+
}
|
|
182
|
+
const issued = state.issued ?? await remote('issued', 'mutation-pass-issue',
|
|
183
|
+
{ challengeId: challenge.challengeId, attestation: state.attestation }, state, location, dependencies);
|
|
184
|
+
passBinding(issued.pass, challenge, state.attestation);
|
|
185
|
+
await authorizeCachedConnection(identity, issued.pass, dependencies);
|
|
186
|
+
const installed = await issueMutationPass14({ ...input, mutationPass: issued.pass });
|
|
187
|
+
if (canonical(installed.pass) !== canonical(issued.pass) || typeof installed.gatePassPath !== 'string') fail('Mutation gate did not install its exact original server pass');
|
|
188
|
+
state.gatePassPath = installed.gatePassPath; await save(location, state);
|
|
189
|
+
return connected(identity, state);
|
|
190
|
+
}
|
|
191
|
+
function connected(identity, state) {
|
|
192
|
+
return { schemaVersion: 'entrorove.mutation-connection/1.0', status: 'succeeded', taskId: identity.taskId,
|
|
193
|
+
flowId: identity.flowId, stepId: identity.stepId, gatePassPath: state.gatePassPath, pass: state.issued.pass,
|
|
194
|
+
purpose: 'preserved-existing-modify', grantsExecutionAuthority: false, grantsValidatorAuthority: false,
|
|
195
|
+
...(state.feedback ? { feedback: state.feedback } : {}) };
|
|
196
|
+
}
|
|
197
|
+
export async function connectMutation(repositoryRoot, input, dependencies) {
|
|
198
|
+
const original = inputIdentity(input);
|
|
199
|
+
const local = { repositoryRoot, home: dependencies.home, taskId: original.taskId, flowId: original.flowId, stepId: original.stepId };
|
|
200
|
+
const location = await preservedMutationLocation(local);
|
|
201
|
+
local.repositoryRoot = location.root;
|
|
202
|
+
for (const relative of original.paths) {
|
|
203
|
+
const target = await mutationTarget14(location.root, relative, true);
|
|
204
|
+
if (!target.exists || !target.stat.isFile()) fail('Mutation connection permits only concrete existing files');
|
|
205
|
+
}
|
|
206
|
+
await bootstrapCloudMutationAuthority(dependencies);
|
|
207
|
+
const mutator = await preparePreservedMutationSigner(local);
|
|
208
|
+
const identity = { ...original, repositoryRoot: location.root, apiOrigin: trustedOrigin(dependencies.apiOrigin()), mutationKeyId: mutator.mutationKeyId };
|
|
209
|
+
const directory = path.join(location.directory, 'connections'); await privateMutationDirectory14(directory, { privateFrom: location.stateRoot });
|
|
210
|
+
const file = path.join(directory, `${sha256(canonical(identity))}.json`), journal = { ...location, file };
|
|
211
|
+
return withStateLock(file, () => connect(identity, local, mutator, journal, dependencies));
|
|
212
|
+
}
|
|
213
|
+
function settlementBinding(output, pass, receipt, expectedSettlementId) {
|
|
214
|
+
if (output?.settled !== true || typeof output.settlementId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(output.settlementId)
|
|
215
|
+
|| expectedSettlementId !== undefined && output.settlementId !== expectedSettlementId
|
|
216
|
+
|| output.passId !== pass.passId || output.consumptionReceiptDigest !== sha256(canonical(receipt))
|
|
217
|
+
|| output.claimsVerifiedExecution !== false || output.claimsCompletion !== false) fail('Mutation settlement must preserve its original actual M receipt without claiming independent validation or completion');
|
|
218
|
+
}
|
|
219
|
+
async function settleMutation(repositoryRoot, input, dependencies) {
|
|
220
|
+
const required = ['flowId', 'stepId', 'gatePassPath'], optional = ['taskId'].filter(field => Object.hasOwn(input ?? {}, field));
|
|
221
|
+
if (!input || canonical(Object.keys(input).sort()) !== canonical([...required, ...optional].sort())
|
|
222
|
+
|| !path.isAbsolute(input.gatePassPath ?? '')) fail('Mutation settlement requires the exact original stored pass and task step');
|
|
223
|
+
for (const field of ['flowId', 'stepId', ...optional]) mutationId14(input[field], field);
|
|
224
|
+
await bootstrapCloudMutationAuthority(dependencies);
|
|
225
|
+
const local = { ...input, repositoryRoot, home: dependencies.home }, original = await preservedMutationConsumptionState(local);
|
|
226
|
+
const { pass, binding } = original;
|
|
227
|
+
await authorizeCachedConnection({ flowId: pass.flowId, stepId: pass.stepId }, pass, dependencies);
|
|
228
|
+
const directory = path.join(binding.directory, 'settlements'); await privateMutationDirectory14(directory, { privateFrom: binding.stateRoot });
|
|
229
|
+
const location = { ...binding, file: path.join(directory, `${pass.passId}.json`) };
|
|
230
|
+
const identity = { flowId: pass.flowId, stepId: pass.stepId, passDigest: sha256(canonical(pass)),
|
|
231
|
+
repositoryRoot: binding.root, apiOrigin: trustedOrigin(dependencies.apiOrigin()), mutationKeyId: binding.binding.keyId };
|
|
232
|
+
return withStateLock(location.file, async () => {
|
|
233
|
+
const state = await readConnection(location, identity);
|
|
234
|
+
if (!state.settled && !state.pending && (!state.consumptionReceipt || state.rejection?.effects === 'none'
|
|
235
|
+
&& state.rejection.code === 'ENTROROVE_MUTATION_CONSUMPTION_EXPIRED')) {
|
|
236
|
+
const observed = await attestMutationConsumption14(local, { allowExpiredReplay: true, renewExpired: state.rejection?.effects === 'none'
|
|
237
|
+
&& state.rejection.code === 'ENTROROVE_MUTATION_CONSUMPTION_EXPIRED' });
|
|
238
|
+
state.consumptionReceipt = observed.consumptionReceipt; delete state.rejection; await save(location, state);
|
|
239
|
+
}
|
|
240
|
+
if (!state.consumptionReceipt) fail('Original mutation settlement receipt is missing; recover its original request without signing another');
|
|
241
|
+
let output = state.settled;
|
|
242
|
+
if (!output) {
|
|
243
|
+
try { output = await remote('settled', 'mutation-pass-settle', { passId: pass.passId, consumptionReceipt: state.consumptionReceipt }, state, location, dependencies); }
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (error.output?.effects === 'none') { state.rejection = { effects: 'none', code: error.output.code }; await save(location, state); }
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
settlementBinding(output, pass, state.consumptionReceipt, state.settlementId);
|
|
250
|
+
if (state.settlementId === undefined) { state.settlementId = output.settlementId; await save(location, state); }
|
|
251
|
+
return { schemaVersion: 'entrorove.mutation-settlement/1.0', status: 'reported', settled: true,
|
|
252
|
+
flowId: pass.flowId, taskId: pass.taskId, passId: pass.passId, settlementId: output.settlementId,
|
|
253
|
+
consumptionReceiptDigest: output.consumptionReceiptDigest,
|
|
254
|
+
claimsVerifiedExecution: false, claimsCompletion: false, ...(state.feedback ? { feedback: state.feedback } : {}),
|
|
255
|
+
observationAction: { command: 'npx @entrorove/skill workflow-observe-step <absolute repositoryRoot>', independentValidation: 'pending',
|
|
256
|
+
input: { taskId: pass.taskId, flowId: pass.flowId, stepId: pass.stepId, leaseId: pass.leaseId,
|
|
257
|
+
generation: pass.generation, settlementId: output.settlementId } },
|
|
258
|
+
localAction: { command: 'npx @entrorove/skill mutation-connect <absolute repositoryRoot>',
|
|
259
|
+
input: { ...Object.fromEntries(fields.map(field => [field, pass[field]])), previousPassId: pass.passId } } };
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async function runMutationClient(operation, repositoryRoot, input, dependencies, work) {
|
|
263
|
+
let preflight = true;
|
|
264
|
+
try {
|
|
265
|
+
await dependencies.install();
|
|
266
|
+
if (!await dependencies.agreementAccepted(dependencies.home)) return dependencies.agreementRequired(dependencies.home);
|
|
267
|
+
preflight = false;
|
|
268
|
+
return await work(repositoryRoot, input, dependencies);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
const requestId = /^[a-f0-9]{64}$/.test(error?.requestId ?? '') ? error.requestId : undefined;
|
|
272
|
+
if (error?.outcomeUnknown === true && requestId) return {
|
|
273
|
+
schemaVersion: 'entrorove.mutation-connection/1.0', status: 'blocked', flowId: input?.flowId, requestId,
|
|
274
|
+
code: 'ENTROROVE_MUTATION_CONNECTION_OUTCOME_UNKNOWN', outcomeUnknown: true, recoverable: true,
|
|
275
|
+
message: `Original mutation request outcome is unknown; rerun ${operation} with the same input to recover only its original GET, without repeating its POST`,
|
|
276
|
+
nextAction: { operation, requestId, method: 'GET', reuseOriginalInput: true, repeatPost: false },
|
|
277
|
+
};
|
|
278
|
+
const typed = typeof error?.code === 'string' && error.code.startsWith('ENTROROVE_');
|
|
279
|
+
const knownBeforeTransmission = preflight || error?.knownBeforeTransmission === true;
|
|
280
|
+
return { schemaVersion: 'entrorove.mutation-connection/1.0', status: 'blocked', flowId: input?.flowId,
|
|
281
|
+
code: typed ? error.code : 'ENTROROVE_MUTATION_CONNECTION_LOCAL_PREREQUISITE',
|
|
282
|
+
message: typed ? error.message : 'Mutation connection stopped at a local prerequisite; preserve its journal and repair before reusing the original input',
|
|
283
|
+
...(requestId ? { requestId } : {}), ...(knownBeforeTransmission ? { knownBeforeTransmission: true } : {}),
|
|
284
|
+
recovery: { operation, reuseOriginalInput: true, preserveJournal: true,
|
|
285
|
+
...(knownBeforeTransmission ? { remoteAttemptedThisRun: false } : {}) },
|
|
286
|
+
...(error?.output ? { output: error.output } : {}) };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export const runMutationConnect = (repositoryRoot, input, dependencies) => runMutationClient('mutation-connect', repositoryRoot, input, dependencies, connectMutation);
|
|
290
|
+
export const runMutationSettle = (repositoryRoot, input, dependencies) => runMutationClient('mutation-settle', repositoryRoot, input, dependencies, settleMutation);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -38,6 +38,13 @@
|
|
|
38
38
|
"local-mutation-snapshot.mjs",
|
|
39
39
|
"local-mutation-audit.mjs",
|
|
40
40
|
"local-mutation-wire.mjs",
|
|
41
|
+
"local-preserved-mutation-protection.mjs",
|
|
42
|
+
"local-preserved-mutation-signer.mjs",
|
|
43
|
+
"local-preserved-mutation-state.mjs",
|
|
44
|
+
"local-preserved-mutation-wire.mjs",
|
|
45
|
+
"local-delegated-modify-analysis.mjs",
|
|
46
|
+
"mutation-client.mjs",
|
|
47
|
+
"cloud-mutation-authority.mjs",
|
|
41
48
|
"local-mutation-binding.mjs",
|
|
42
49
|
"local-mutation-pass-store.mjs",
|
|
43
50
|
"local-mutation-authority.mjs",
|
|
@@ -232,7 +239,17 @@
|
|
|
232
239
|
"IDE-EXECUTION.md",
|
|
233
240
|
"REUSE-DISCIPLINE.md",
|
|
234
241
|
"DELIVERY-BOUNDARIES.md",
|
|
235
|
-
"reuse-discipline-release.mjs"
|
|
242
|
+
"reuse-discipline-release.mjs",
|
|
243
|
+
"connected-cli.mjs",
|
|
244
|
+
"local-approved-process.mjs",
|
|
245
|
+
"local-approved-supervisor.mjs",
|
|
246
|
+
"local-mutation-consumption.mjs",
|
|
247
|
+
"workflow-observation-client.mjs",
|
|
248
|
+
"workflow-observation-connection.mjs",
|
|
249
|
+
"workflow-observation-remote.mjs",
|
|
250
|
+
"workflow-observation-runner.mjs",
|
|
251
|
+
"workflow-observation-state.mjs",
|
|
252
|
+
"workflow-observation-wire.mjs"
|
|
236
253
|
],
|
|
237
254
|
"scripts": {
|
|
238
255
|
"test": "node test/run.mjs",
|
package/release-state.json
CHANGED
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.53",
|
|
4
|
+
"digest": "3508e9d78536c07403711408fe6dc6d71d137820f34d3d164c4b5898be923df5",
|
|
5
5
|
"files": {
|
|
6
|
+
"connected-cli.mjs": "260c0fc99586c24bd4d0351a14e0744849a612ebe6f1bd7951e4fce200daf920",
|
|
7
|
+
"local-approved-process.mjs": "442856df788e63f38757439ba0657d1c689aaf797631a955fae3dd639e844e6d",
|
|
8
|
+
"local-approved-supervisor.mjs": "87142a4c13d27c8265cc1fd7fc9088988da9bbb02134164e1a7c5417da926ac7",
|
|
9
|
+
"local-mutation-consumption.mjs": "cd8650b9be894639ceb35af88d23c8002b4acff27afad2e64174eb21a9424cfb",
|
|
10
|
+
"workflow-observation-client.mjs": "5e67bc32a8625de9523498f694655c378ae34af59c4aa2f0e07efcd72347dbc0",
|
|
11
|
+
"workflow-observation-connection.mjs": "9235e9247b11e1d51628c1784eb1cb1e8e4b79d25ff791883722592dcf8583c0",
|
|
12
|
+
"workflow-observation-remote.mjs": "1e5a8b9c2b9b095242ad28a1d5ebce5944efdae74383fb20d6676b93b240c5a7",
|
|
13
|
+
"workflow-observation-runner.mjs": "3eb8280ec7abffcb30398e3f4e5f7d0ec3df4385fa720f188ed83757b87cd977",
|
|
14
|
+
"workflow-observation-state.mjs": "a525046df9b3de75b10d2bf35c2b6d7719f12a647e4aee1f1e85dbdbfb167321",
|
|
15
|
+
"workflow-observation-wire.mjs": "846d0b48b7799bfca227e30faea025a5a919896538f963ce886cbdb4c75c0253",
|
|
16
|
+
"local-preserved-mutation-protection.mjs": "95041d4f75253a153c7fd85573ba0457513ffe47a6a6bd00c4f59379b81ce580",
|
|
17
|
+
"local-preserved-mutation-signer.mjs": "2b0771cbdd18ba00267aa441b7892cd04feab9f8543ae5e2703430b1e580747f",
|
|
18
|
+
"local-preserved-mutation-state.mjs": "1ff7939d668e39ec641a7cf46a4ce3014c63f3898207cc874f21170376479671",
|
|
19
|
+
"local-preserved-mutation-wire.mjs": "d302bd4b115338c0d1bf69718ce2596dbceb595431ff3f57b998ea67d94cb34c",
|
|
20
|
+
"local-delegated-modify-analysis.mjs": "f87148ddb023b79e241ca97db582991da8d0e1a51b26b1d9bc2799a76efd7297",
|
|
21
|
+
"mutation-client.mjs": "8d27b267dc660510aa1406c44d4005e44b972a1260f90657de4fa0e75b7b6c3e",
|
|
22
|
+
"cloud-mutation-authority.mjs": "c7226e882a4f3be4d2f55728f515d5a815c0aa1f21aa5e4689314e6e6a8c66a2",
|
|
6
23
|
"ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
|
|
7
|
-
"ide-command.mjs": "
|
|
8
|
-
"ide-hook.mjs": "
|
|
24
|
+
"ide-command.mjs": "f2fe57312f83a11b318f1c98db51a6a47a7ff45b53f392acce32e458976bd06c",
|
|
25
|
+
"ide-hook.mjs": "be328cc646ea6ec5dc18e5458dd1f829897cc386d74bda0b56cbd14f53c98827",
|
|
9
26
|
"ide-install.mjs": "10247efadcfeef5da001022d12080bd0f789e67fdf7862bcc65db73dda3bbe57",
|
|
10
|
-
"ide-session.mjs": "
|
|
27
|
+
"ide-session.mjs": "453b9510ab825c2927a4fae8250ee8a8eb5ac10b23cf024af84a7c5916ba53dd",
|
|
11
28
|
"break-glass.mjs": "6ec9f97c5d68bb33121b2374c06b9d4c8eb3419b52d00849fec7124bc63e4582",
|
|
12
|
-
"ide-results.mjs": "
|
|
29
|
+
"ide-results.mjs": "7ed5caf8e77cf42ae6d62cc1ed42b1639bca6069d809b90a399bec5192fb903e",
|
|
13
30
|
"ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
|
|
14
|
-
"local-discipline-actions.mjs": "
|
|
31
|
+
"local-discipline-actions.mjs": "418c220b8c8ef59cb8092b6eb680bb75a93b9fad1a09412b1efa98a78afff11b",
|
|
15
32
|
"local-discipline-analysis.mjs": "e887abdd1c6d6ba59ef7323e9035528f0491e5458ca8159985ec26e1ca67a4af",
|
|
16
33
|
"local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
|
|
17
34
|
"local-typescript-function-reference.mjs": "af682b479fe0d69a5ecd62232406bb8078408ec8710733cfed9ae6b059762ba5",
|
|
18
35
|
"local-scope-import-expressions.mjs": "6623dea5501c4dc24e12c99e0776d65650bbe752c85c9509d022ba1f61bd5111",
|
|
19
36
|
"local-discipline-contracts.mjs": "6cf8a3d132fdb9074eb1441600afe7aa199df33e88a71a660d9db534c14f19e4",
|
|
20
37
|
"local-discipline-contracts.d.mts": "defc684c78dc88afa1c54340ab4a01ae36db7aa11257cbe1f5cd323f608c78c4",
|
|
21
|
-
"local-discipline-gate.mjs": "
|
|
38
|
+
"local-discipline-gate.mjs": "e3851157bf1d8bc86eb44212a88acd20acfc2d1152e7d87af6dc2a37afffcd6f",
|
|
22
39
|
"local-discipline-native.mjs": "8b99b5ce951cab3c1426428ad807650c61c0ffb2c346d747b73b99057b382049",
|
|
23
40
|
"local-discipline-operations.mjs": "9fcb7b8889b7718d42e1d37936485d8198d5017d405e0cdbbabdbbd12fcc9d48",
|
|
24
41
|
"local-discipline-repository.mjs": "b6392e02ecae333dfdc8df41f203fd69bed035d88b15b16d112ab1897b3c297c",
|
|
@@ -31,7 +48,7 @@
|
|
|
31
48
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
32
49
|
"snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
|
|
33
50
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
34
|
-
"IDE-EXECUTION.md": "
|
|
51
|
+
"IDE-EXECUTION.md": "fe8ff87dde885832b34a369985f02b567efa096ed1c1e79a0e490280c8540bf5",
|
|
35
52
|
"REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
|
|
36
53
|
"reuse-discipline-release.mjs": "2935898d1aa807fb982a78d7ab5cb1abe05e3c8fc945dc0909da8098872aa1ac",
|
|
37
54
|
"local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
|
|
@@ -75,25 +92,25 @@
|
|
|
75
92
|
"local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
|
|
76
93
|
"local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
|
|
77
94
|
"local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
|
|
78
|
-
"cli.mjs": "
|
|
95
|
+
"cli.mjs": "74b72be1190c4ff79279a8b911a85cc654ea860574691eb79eefb5fbab4a6fbe",
|
|
79
96
|
"install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
|
|
80
97
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
81
98
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
82
|
-
"SKILL.md": "
|
|
83
|
-
"CAPABILITY-MAP.md": "
|
|
99
|
+
"SKILL.md": "d21f5085c0a5f830d5fd8b65e3a9da1e6d15512db69a4a8daab3e94fb8738f0b",
|
|
100
|
+
"CAPABILITY-MAP.md": "69b2c49b111d651969dea7430f4df7950d8fdf2f0f151d8386fa04968b872ac0",
|
|
84
101
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
85
|
-
"DELIVERY-BOUNDARIES.md": "
|
|
86
|
-
"package.json": "
|
|
102
|
+
"DELIVERY-BOUNDARIES.md": "9b9db50ed1bcce9e77ff6527b6bb7eaf12f3b913162ea9cb7fe90db06b393a4f",
|
|
103
|
+
"package.json": "6a0566be47288eba3fb8b327e19d50df62f47a90e101ff952b49cedd951c04b7",
|
|
87
104
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
88
|
-
"release.mjs": "
|
|
89
|
-
"local-cli.mjs": "
|
|
105
|
+
"release.mjs": "6292dd8a4ef82c5674f4aa80a8e70059f378162162b83aae750fa55697ba90d3",
|
|
106
|
+
"local-cli.mjs": "41e2a737c8587cb5ce469a7661f6d14b5ea4cd073f8286822fc4dc96b67f56a1",
|
|
90
107
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
91
|
-
"local-execution.mjs": "
|
|
108
|
+
"local-execution.mjs": "185f34e917440a360b3c4bafd3af245243807d964ae3820b3cffaaa2e6348cc6",
|
|
92
109
|
"local-integrity.mjs": "e6d6eace80c610f3c7299d4f8644b0dc023aa9e3d8d64b26535ea7fbced248f1",
|
|
93
110
|
"local-evidence-verification.mjs": "9b7dea31777505272f63616fd62af7603338f703bb1285fc565d6451715c7ce0",
|
|
94
111
|
"local-formula.mjs": "706d5ec0cb2caf95a547531a81ca746fe246d4c53d902e03595a3b360e8c85af",
|
|
95
112
|
"local-ledger.mjs": "2009fb68aadcd7292b72f1d0d99f378d03644be1c5b4b4150d9f6a0b3f7c15ce",
|
|
96
|
-
"local-error.mjs": "
|
|
113
|
+
"local-error.mjs": "f01213b21d84402dc5257aaa3ae4a5fa78931a956e9b8526b1e7dbf1b20b6b5d",
|
|
97
114
|
"blueprint-criterion-wire.mjs": "fb6b7dcce0b97a699e3e8968372767a3fddd83fedc750998573f789177243072",
|
|
98
115
|
"blueprint-criterion-runner.mjs": "761c0e670b4b20c6bed53e5ca5d0178759505c3977ea2ed1b7223383c534bc4c",
|
|
99
116
|
"local-chain-bindings.mjs": "b8eff999ccd17b53c614a3c9f5376da78fcaa8632bb3e07bc1adcceda2e75f6c",
|
|
@@ -106,7 +123,7 @@
|
|
|
106
123
|
"local-chain-plan.mjs": "e45a9170f569c80f7e5532bbc109dbae3c24b696815316e503cb3779135be648",
|
|
107
124
|
"local-chain-release-labels.mjs": "7ce5350d1062d5236f57f10e7e81713c1101463d5e7f252604ef0c2a30840bfe",
|
|
108
125
|
"local-chain-results.mjs": "0455f72cad574cfef87c2c6c0928d6432bba7e5dbaff521a2b62a9575299ca10",
|
|
109
|
-
"local-approved-plan.mjs": "
|
|
126
|
+
"local-approved-plan.mjs": "26731b534f4c276c6235cb73bf6950943061947967a93f477f4bcd64acde72a6",
|
|
110
127
|
"local-budget-renewal.mjs": "6b436358066fe33b2f6d871fc90d8e7c551edd6255efd73c0d255d0fd35d5ef8",
|
|
111
128
|
"local-probe.mjs": "9b948db1de1e34c7605c4c454cd4aced66d2b57d2cf4471adbb80bcc76dee635",
|
|
112
129
|
"local-budget-grants.mjs": "c5f6542f5d2c3bfe07f3ca5b5bc849bf05d886b06014f720322d530510775d3d",
|
|
@@ -155,7 +172,7 @@
|
|
|
155
172
|
"local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
|
|
156
173
|
"local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
|
|
157
174
|
"local-task-routing.mjs": "6e2e506b2fb0e2e8d8c5147d7df32849b57a4e94a23634f01a355d45735858e4",
|
|
158
|
-
"remote-client.mjs": "
|
|
175
|
+
"remote-client.mjs": "0d1ec6506fe53a1e33c31c5ba19d14e30e00e85e11eaf7ff8277ea798b9f36c9",
|
|
159
176
|
"cloud-task-client.mjs": "11acb1a033c94cdfbdfb177a3402ad97f6a5226f577138c8cf6395dc3ebb1a39",
|
|
160
177
|
"scope-runner-client.mjs": "646ab4f80d4a8209e922fd45f40efd968f0744d9dfc03188b222c1a2e11e83b8",
|
|
161
178
|
"native-client.mjs": "6f5575909bae9daba643287f876ffa0f9ff1c53a367b32a40145d740456d6605",
|
|
@@ -188,7 +205,7 @@
|
|
|
188
205
|
"workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
|
|
189
206
|
"task-message-entry.mjs": "31d10056cb905237cd5649e8eb067009ff1612ec5dc1061e0373209fbc0b646a",
|
|
190
207
|
"task-message-cli.mjs": "3ca65e96c28129b3bd5cac962d106e0859a1f17df1f6a66ee543c6efc22f918d",
|
|
191
|
-
"local-contracts.mjs": "
|
|
208
|
+
"local-contracts.mjs": "b59fcdace17b0b56117c564f8c15322465243e69adede182c5dde9204836c99c",
|
|
192
209
|
"local-contract-types.mjs": "85e71fa4dd67e351a4b67573c740bcdb1661cede53f097189f11837771e75e91",
|
|
193
210
|
"local-archguard-ast.mjs": "7ff7df9dab0d98ed62983add751c80edea598bc94ef4f38ede0ebd4d41226c87",
|
|
194
211
|
"local-archguard-checkpoint.mjs": "37896902520cc995720c96288e4a5ed3585952b55c9c35be281f91fa754c71ec",
|
|
@@ -218,11 +235,11 @@
|
|
|
218
235
|
"local-mutation-files.mjs": "087b00e8424a135e783eb432271012a71e55eeb4a5e30119490d1bc0b2160320",
|
|
219
236
|
"local-mutation-snapshot.mjs": "f9077539680fb8d3ff33a962657756a8fe6afa8409b6191334509534216ebfe8",
|
|
220
237
|
"local-mutation-audit.mjs": "5a30ee3446f632e032ffcfe46bcd97d6dfc0f099a76ea5185c3f9a9118c4f6cb",
|
|
221
|
-
"local-mutation-wire.mjs": "
|
|
222
|
-
"local-mutation-binding.mjs": "
|
|
223
|
-
"local-mutation-pass-store.mjs": "
|
|
224
|
-
"local-mutation-authority.mjs": "
|
|
225
|
-
"local-mutation-batch.mjs": "
|
|
238
|
+
"local-mutation-wire.mjs": "7af0c585e8dd6936c0ff17f491c64a198744629f063279c31589d887ec83ae9c",
|
|
239
|
+
"local-mutation-binding.mjs": "4440d792453a07a375b635fa5a91db607e58162824baced5c79a9a513f4ec918",
|
|
240
|
+
"local-mutation-pass-store.mjs": "1edc5fcc537dfa04b8ae8b07af554d61ea5911a5da281423ad57214ccf753ad4",
|
|
241
|
+
"local-mutation-authority.mjs": "ebac0cc8126969bafb0aaaeacf71e05124997d5c3214f8ec32b7a4fa14569bcf",
|
|
242
|
+
"local-mutation-batch.mjs": "de27686b5f99a99b02f0218a3fa159e9469d6b19c595117287d6dd37a6efcb3b",
|
|
226
243
|
"local-mutation-batch-receipt.mjs": "b0e6a8522867255d899e60b66677885933f6a1c4c771cbb52f84dbbbdcf5e5c2",
|
|
227
244
|
"calctool-runtime-entry.mjs": "98d2bc8df38d16131fc56db0a3d4d260531ba4df412baa5ddc4b8b5c5ca99fe9",
|
|
228
245
|
"calctool-runtime-build.mjs": "3c5a9bd40bc1abe02e3b6e307809eea4eaaf198e8f0db6f5d6b2583dd68a59d2",
|
package/release.mjs
CHANGED
|
@@ -28,7 +28,26 @@ const coordination17SourceFiles = Object.freeze([
|
|
|
28
28
|
'local-validator-isolation17.mjs',
|
|
29
29
|
'local-calctool-composition17.mjs',
|
|
30
30
|
])
|
|
31
|
+
const preservedMutationSourceFiles = Object.freeze([
|
|
32
|
+
'local-preserved-mutation-protection.mjs', 'local-preserved-mutation-signer.mjs',
|
|
33
|
+
'local-preserved-mutation-state.mjs', 'local-preserved-mutation-wire.mjs',
|
|
34
|
+
'local-delegated-modify-analysis.mjs', 'mutation-client.mjs', 'cloud-mutation-authority.mjs',
|
|
35
|
+
])
|
|
36
|
+
const memberContinuationSourceFiles = Object.freeze([
|
|
37
|
+
'connected-cli.mjs',
|
|
38
|
+
'local-approved-process.mjs',
|
|
39
|
+
'local-approved-supervisor.mjs',
|
|
40
|
+
'local-mutation-consumption.mjs',
|
|
41
|
+
'workflow-observation-client.mjs',
|
|
42
|
+
'workflow-observation-connection.mjs',
|
|
43
|
+
'workflow-observation-remote.mjs',
|
|
44
|
+
'workflow-observation-runner.mjs',
|
|
45
|
+
'workflow-observation-state.mjs',
|
|
46
|
+
'workflow-observation-wire.mjs',
|
|
47
|
+
])
|
|
31
48
|
const sourceFiles = [
|
|
49
|
+
...memberContinuationSourceFiles,
|
|
50
|
+
...preservedMutationSourceFiles,
|
|
32
51
|
...REUSE_DISCIPLINE_SOURCE_FILES,
|
|
33
52
|
...FINAL_GATE16_PACKAGE_FILES,
|
|
34
53
|
'local-merge-archguard-verification.d.mts',
|
|
@@ -212,6 +231,12 @@ const calctoolReleaseLabels = Object.freeze({
|
|
|
212
231
|
})
|
|
213
232
|
|
|
214
233
|
const mutationReleaseLabels = Object.freeze({
|
|
234
|
+
...Object.fromEntries(memberContinuationSourceFiles.map(file => [file, {
|
|
235
|
+
zh: '原任务连续编辑与本地测试观察', en: 'original-task continuation and local command observation',
|
|
236
|
+
}])),
|
|
237
|
+
...Object.fromEntries(preservedMutationSourceFiles.map(file => [file, {
|
|
238
|
+
zh: '保留运行时依赖的受限本地编辑授权', en: 'restricted local editing with preserved runtime dependencies',
|
|
239
|
+
}])),
|
|
215
240
|
'local-mutation-files.mjs': { zh: '原任务短时写入授权与文件快照隔离', en: 'Original task short-lived mutation authority and file snapshot isolation' },
|
|
216
241
|
'local-mutation-snapshot.mjs': { zh: '原任务短时写入授权与文件快照隔离', en: 'Original task short-lived mutation authority and file snapshot isolation' },
|
|
217
242
|
'local-mutation-audit.mjs': { zh: '原任务短时写入授权与文件快照隔离', en: 'Original task short-lived mutation authority and file snapshot isolation' },
|
package/releases.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.53",
|
|
4
|
+
"date": "2026-10-17",
|
|
5
|
+
"zh": "补齐原任务受限编辑、真实写后结算与连续修改,保留未知依赖和独立验证边界;沙箱关闭后可运行原批准命令并保存本地观察。修复前置条件、断线恢复和停止循环,独立监督器约束崩溃后的子进程。本地成功不代表独立验证或交付。",
|
|
6
|
+
"en": "Added original-task restricted editing, real write settlement and subsequent modifications while preserving unknown dependencies and independent validation. With cloud sandbox disabled, retain actual observations of the approved command. Fixed prerequisite recovery, disconnections and Stop loops; an independent supervisor bounds child processes after CLI failure. Local success does not certify independent verification or delivery."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.1.52",
|
|
4
10
|
"date": "2026-10-17",
|
package/remote-client.mjs
CHANGED
|
@@ -37,7 +37,8 @@ function discoveredAjv() {
|
|
|
37
37
|
export function buildInvocation(operation, input) {
|
|
38
38
|
if (!['capabilities', 'task-describe', 'message-route', 'message-accept', 'task-resume', 'task-checkpoint', 'flow-cancel', 'flow-abandon',
|
|
39
39
|
'message-status', 'message-delivery-start', 'message-delivery-report', 'message-resolve', 'message-complete',
|
|
40
|
-
'handoff-prepare', 'handoff-release', 'handoff-claim', 'handoff-status', 'handoff-resume', 'handoff-execution-authority', 'mutation-snapshot-challenge', 'mutation-pass-issue',
|
|
40
|
+
'handoff-prepare', 'handoff-release', 'handoff-claim', 'handoff-status', 'handoff-resume', 'handoff-execution-authority', 'mutation-snapshot-challenge', 'mutation-pass-issue', 'mutation-pass-settle',
|
|
41
|
+
'member-observation-admit', 'member-observation-report', 'member-observation-status',
|
|
41
42
|
'scope-runner-prepare', 'scope-runner-register', 'scope-runner-challenge', 'scope-runner-attest',
|
|
42
43
|
'start', 'continue'].includes(operation)) throw new Error('Invalid entrorove operation');
|
|
43
44
|
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Input must be a JSON object');
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { bootstrapCloudMutationAuthority } from './cloud-mutation-authority.mjs';
|
|
2
|
+
import { observationInput } from './workflow-observation-wire.mjs';
|
|
3
|
+
|
|
4
|
+
const safeId = value => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
|
|
5
|
+
const safeRequest = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
6
|
+
const messages = {
|
|
7
|
+
ENTROROVE_LOCAL_OBSERVATION_UNKNOWN: 'Original observation outcome is unknown; recover its original GET without repeating execution',
|
|
8
|
+
ENTROROVE_LOCAL_OBSERVATION_NOT_SENT: 'Observation was not transmitted; repair its local or discovery prerequisite and reuse the original input',
|
|
9
|
+
ENTROROVE_LOCAL_OBSERVATION_PREREQUISITE: 'Local observation requires cloud sandbox disabled and its original live execution lease',
|
|
10
|
+
ENTROROVE_SCOPE_RECEIPT_EXPIRED: 'Refresh the original scope evidence and reuse the original observation input',
|
|
11
|
+
ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED: 'Observe the settled modifications in fresh scope evidence before local testing',
|
|
12
|
+
};
|
|
13
|
+
function blocked(error, input) {
|
|
14
|
+
const cause = error && typeof error === 'object' ? error : {};
|
|
15
|
+
const code = typeof cause.code === 'string' && /^ENTROROVE_[A-Z0-9_]{1,100}$/.test(cause.code)
|
|
16
|
+
? cause.code : 'ENTROROVE_LOCAL_OBSERVATION';
|
|
17
|
+
const output = cause.output, requestId = safeRequest(cause.requestId) ? cause.requestId : undefined;
|
|
18
|
+
const recoverable = output?.status === 'blocked' && output.recoverable === true
|
|
19
|
+
&& (output.observationPrerequisite?.effect === 'none' || ['ENTROROVE_SCOPE_RECEIPT_EXPIRED',
|
|
20
|
+
'ENTROROVE_SCOPE_RUNNER_REQUIRED', 'ENTROROVE_SCOPE_SNAPSHOT_REQUIRED', 'ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED'].includes(code));
|
|
21
|
+
const reason = ['sandbox-enabled', 'lease-expired', 'receipt-expired', 'after-write-observation-required'].includes(output?.reason)
|
|
22
|
+
? output.reason : undefined;
|
|
23
|
+
return { schemaVersion: 'entrorove.local-observation-connection/1.0', status: 'blocked',
|
|
24
|
+
...(safeId(input?.flowId) ? { flowId: input.flowId } : {}), independentValidation: 'pending', code,
|
|
25
|
+
message: messages[code] ?? 'Local observation could not continue; its original command and result remain preserved',
|
|
26
|
+
...(recoverable ? { prerequisite: { status: 'blocked', code, recoverable: true, ...(reason ? { reason } : {}),
|
|
27
|
+
nextAction: { operation: 'workflow-observe-step', reuseOriginalInput: true,
|
|
28
|
+
...(safeRequest(output.requestId) ? { resumeRequestId: output.requestId } : {}) } } } : {}),
|
|
29
|
+
...(cause.outcomeUnknown === true && requestId ? { outcomeUnknown: true, requestId, repeatPost: false,
|
|
30
|
+
nextAction: { operation: 'workflow-observe-step', reuseOriginalInput: true, method: 'GET' } } : {}),
|
|
31
|
+
...(cause.knownBeforeTransmission === true && requestId ? { requestId, knownBeforeTransmission: true } : {}) };
|
|
32
|
+
}
|
|
33
|
+
export async function runWorkflowObserveStep(repositoryRoot, input, dependencies) {
|
|
34
|
+
try {
|
|
35
|
+
observationInput(input);
|
|
36
|
+
await dependencies.install();
|
|
37
|
+
if (!await dependencies.agreementAccepted(dependencies.home)) return dependencies.agreementRequired(dependencies.home);
|
|
38
|
+
await bootstrapCloudMutationAuthority(dependencies);
|
|
39
|
+
const { connectWorkflowObservation } = await import('./workflow-observation-connection.mjs');
|
|
40
|
+
return await connectWorkflowObservation(repositoryRoot, input, dependencies);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return blocked(error, input);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
2
|
+
import { withStateLock } from './local-state-lock.mjs';
|
|
3
|
+
import { trustedOrigin } from './remote-transport.mjs';
|
|
4
|
+
import { observationAdmission, observationFail } from './workflow-observation-wire.mjs';
|
|
5
|
+
import { observationLocation, readObservationState } from './workflow-observation-state.mjs';
|
|
6
|
+
import { runWorkflowObservation } from './workflow-observation-runner.mjs';
|
|
7
|
+
import { observationRemote, authorizeObservation } from './workflow-observation-remote.mjs';
|
|
8
|
+
|
|
9
|
+
async function connect(input, state, location, dependencies) {
|
|
10
|
+
const original = Object.fromEntries(['taskId', 'flowId', 'stepId', 'leaseId', 'generation', 'settlementId'].map(key => [key, input[key]]));
|
|
11
|
+
const admitted = state.admitted ?? await observationRemote('admitted', 'member-observation-admit', original, state, location, dependencies);
|
|
12
|
+
const admission = observationAdmission(admitted.admission, input, { allowExpired: Boolean(state.result) });
|
|
13
|
+
await authorizeObservation(admission, dependencies, !state.result);
|
|
14
|
+
const cached = Boolean(state.result), result = await runWorkflowObservation(input, admission, state, location,
|
|
15
|
+
{ authorize: () => authorizeObservation(admission, dependencies, true) });
|
|
16
|
+
if (result.runner !== 'local' || result.receipt !== null || result.independentValidation !== 'pending'
|
|
17
|
+
|| result.executionIsolation !== 'host-permissions' || result.externalEffects !== 'unverified'
|
|
18
|
+
|| !['incomplete', 'failed'].includes(result.status) || result.admissionDigest !== sha256(canonical(admission))) {
|
|
19
|
+
observationFail('Local observation cannot replace independent validation or its original admitted result');
|
|
20
|
+
}
|
|
21
|
+
const reported = state.reported ?? await observationRemote('reported', 'member-observation-report', {
|
|
22
|
+
observationId: admission.observationId, admissionDigest: sha256(canonical(admission)), result,
|
|
23
|
+
}, state, location, dependencies);
|
|
24
|
+
if (reported.observationId !== admission.observationId || reported.observationStatus !== 'pending-independent'
|
|
25
|
+
|| reported.resultDigest !== sha256(canonical(result)) || canonical(reported.result) !== canonical(result)) {
|
|
26
|
+
observationFail('Stored local observation differs from its actual original result');
|
|
27
|
+
}
|
|
28
|
+
return { ...result, taskId: input.taskId, flowId: input.flowId, stepId: input.stepId, cached, stored: true,
|
|
29
|
+
resultDigest: sha256(canonical(result)), grantsExecutionAuthority: false, grantsValidatorAuthority: false };
|
|
30
|
+
}
|
|
31
|
+
export async function connectWorkflowObservation(repositoryRoot, original, dependencies) {
|
|
32
|
+
const location = await observationLocation(repositoryRoot, original, dependencies.home, trustedOrigin(dependencies.apiOrigin()));
|
|
33
|
+
const input = { ...original, repositoryRoot: location.root, home: location.home };
|
|
34
|
+
return withStateLock(location.file, async () => connect(input, await readObservationState(location), location, dependencies));
|
|
35
|
+
}
|