@entrorove/skill 0.1.55 → 0.1.57
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 +1 -1
- package/CHANGELOG.md +10 -0
- package/IDE-EXECUTION.md +18 -4
- package/SKILL.md +4 -4
- package/automatic-feedback-client.mjs +6 -2
- package/connected-cli.mjs +2 -1
- package/ide-command.mjs +3 -1
- package/ide-hook.mjs +22 -6
- package/ide-install.mjs +1 -1
- package/ide-local-acceptance.mjs +134 -0
- package/ide-results.mjs +5 -1
- package/local-approved-plan.mjs +3 -2
- package/local-discipline-contracts.mjs +5 -1
- package/local-mutation-wire.mjs +14 -2
- package/local-preserved-mutation-state.mjs +3 -1
- package/local-preserved-mutation-wire.mjs +8 -1
- package/local-runner-discipline.mjs +7 -2
- package/manifest.json +1 -1
- package/mutation-client.mjs +2 -1
- package/package.json +8 -1
- package/release-state.json +30 -28
- package/release.mjs +1 -0
- package/releases.json +12 -0
- package/reuse-discipline-release.mjs +1 -1
- package/snapshot-retention.mjs +8 -2
- package/workflow-local-acceptance.mjs +35 -0
- package/workflow-local-contracts.mjs +18 -1
- package/workflow-local-runner.mjs +12 -4
- package/workflow-observation-client.mjs +7 -2
- package/workflow-observation-connection.mjs +19 -3
- package/workflow-observation-remote.mjs +19 -4
- package/workflow-observation-wire.mjs +17 -10
|
@@ -6,9 +6,9 @@ import path from 'node:path';
|
|
|
6
6
|
export async function runnerDisciplineBefore(input, authority) {
|
|
7
7
|
if (!authority.plan.policy.discipline) return null;
|
|
8
8
|
const original = await disciplineRepositoryObservation(input, authority.root);
|
|
9
|
-
return { input, original: original.comparison };
|
|
9
|
+
return { input, original: original.comparison, readOnly: authority.plan.policy.discipline.access === 'read-only' };
|
|
10
10
|
}
|
|
11
|
-
export async function runnerDisciplineAfter(before, authority) {
|
|
11
|
+
export async function runnerDisciplineAfter(before, authority, processResult) {
|
|
12
12
|
if (!before) return null;
|
|
13
13
|
const after = (await disciplineRepositoryObservation(before.input, authority.root)).comparison;
|
|
14
14
|
const changed = disciplineRepositoryDifferences(before.original, after);
|
|
@@ -23,5 +23,10 @@ export async function runnerDisciplineAfter(before, authority) {
|
|
|
23
23
|
catch (error) {
|
|
24
24
|
if (error.code !== 'ENTROROVE_STATE_EXISTS' || canonical(await readMutationJson14(file)) !== canonical(record)) throw error;
|
|
25
25
|
}
|
|
26
|
+
if (before.readOnly && changed.length) {
|
|
27
|
+
throw Object.assign(new Error('Explicit read-only execution changed repository bytes or permissions; no successful receipt is issued'),
|
|
28
|
+
{ code: 'ENTROROVE_READ_ONLY_EXECUTION_DRIFT', execution: { schemaVersion: 'entrorove.validator-local-runner/1.0',
|
|
29
|
+
status: 'failed', process: processResult, evidence: [], effects: 'unknown', comparison: report } });
|
|
30
|
+
}
|
|
26
31
|
return { ...report, reportDigest };
|
|
27
32
|
}
|
package/manifest.json
CHANGED
package/mutation-client.mjs
CHANGED
|
@@ -111,7 +111,8 @@ function passBinding(pass, challenge, attestation, options) {
|
|
|
111
111
|
mutationPass14(pass, options);
|
|
112
112
|
if (pass.challengeId !== challenge.challengeId || pass.challengeDigest !== sha256(canonical(challenge))
|
|
113
113
|
|| pass.attestationDigest !== sha256(canonical(attestation)) || pass.snapshotDigest !== attestation.manifest.snapshotDigest
|
|
114
|
-
|| [...fields, 'previousPassId'
|
|
114
|
+
|| [...fields, 'previousPassId', 'verificationMode', 'runnerKeyId', 'memberScopeKeyId', 'memberScopeBindingDigest', 'scopeBindingDigest']
|
|
115
|
+
.some(field => canonical(pass[field]) !== canonical(challenge[field]))
|
|
115
116
|
|| canonical(pass.writeDelegation) !== canonical(challenge.writeDelegation)) fail('Mutation pass differs from its original signed challenge or actual M snapshot');
|
|
116
117
|
}
|
|
117
118
|
async function snapshot(input, challenge, state, location) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"workflow-local-client.mjs",
|
|
72
72
|
"workflow-local-request.mjs",
|
|
73
73
|
"workflow-local-contracts.mjs",
|
|
74
|
+
"workflow-local-acceptance.mjs",
|
|
74
75
|
"local-budget-renewal.mjs",
|
|
75
76
|
"local-probe.mjs",
|
|
76
77
|
"local-flow-lifecycle.mjs",
|
|
@@ -219,6 +220,7 @@
|
|
|
219
220
|
"ide-session.mjs",
|
|
220
221
|
"break-glass.mjs",
|
|
221
222
|
"ide-results.mjs",
|
|
223
|
+
"ide-local-acceptance.mjs",
|
|
222
224
|
"ide-queue.mjs",
|
|
223
225
|
"local-discipline-actions.mjs",
|
|
224
226
|
"local-discipline-analysis.mjs",
|
|
@@ -275,5 +277,10 @@
|
|
|
275
277
|
"tree-sitter-wasms": "0.1.13",
|
|
276
278
|
"@vue/compiler-sfc": "3.5.43",
|
|
277
279
|
"svelte": "5.57.1"
|
|
280
|
+
},
|
|
281
|
+
"repository": {
|
|
282
|
+
"type": "git",
|
|
283
|
+
"url": "git+https://github.com/88208555/entrorove.git",
|
|
284
|
+
"directory": "packages/entrorove-skill"
|
|
278
285
|
}
|
|
279
286
|
}
|
package/release-state.json
CHANGED
|
@@ -1,56 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.57",
|
|
4
|
+
"digest": "9234829890ae7b6f69f54a0467751ab2434e4c77dacb5c095ba3a37fe26423e7",
|
|
5
5
|
"files": {
|
|
6
|
-
"connected-cli.mjs": "
|
|
6
|
+
"connected-cli.mjs": "d5fc2542ae0aa36bf45b0f3d493fe1e7479a4a25c50c8f3730f8b0c4007a7f8f",
|
|
7
7
|
"local-approved-process.mjs": "442856df788e63f38757439ba0657d1c689aaf797631a955fae3dd639e844e6d",
|
|
8
8
|
"local-approved-supervisor.mjs": "87142a4c13d27c8265cc1fd7fc9088988da9bbb02134164e1a7c5417da926ac7",
|
|
9
9
|
"local-mutation-consumption.mjs": "cd8650b9be894639ceb35af88d23c8002b4acff27afad2e64174eb21a9424cfb",
|
|
10
|
-
"workflow-
|
|
11
|
-
"workflow-observation-
|
|
12
|
-
"workflow-observation-
|
|
10
|
+
"workflow-local-acceptance.mjs": "743ae19c1edff473af2596c7356ddff3892ff9acfb3bcaa660b662eacba463a8",
|
|
11
|
+
"workflow-observation-client.mjs": "4ad34e5a467954ad1943a0f003e07dd6c4fb919d197ae9175a8b0c9b6e2aff32",
|
|
12
|
+
"workflow-observation-connection.mjs": "00e887c1430b13fc49c3f51ae814edb7a05c6d61ed7091262ae91a0706fad084",
|
|
13
|
+
"workflow-observation-remote.mjs": "26f7d741d2e00e4cb05240d8999378b6db3cb1c52eaa586ac20f9448fe296e4b",
|
|
13
14
|
"workflow-observation-runner.mjs": "3eb8280ec7abffcb30398e3f4e5f7d0ec3df4385fa720f188ed83757b87cd977",
|
|
14
15
|
"workflow-observation-state.mjs": "a525046df9b3de75b10d2bf35c2b6d7719f12a647e4aee1f1e85dbdbfb167321",
|
|
15
|
-
"workflow-observation-wire.mjs": "
|
|
16
|
+
"workflow-observation-wire.mjs": "90047ca4fe028e4931406ff26d4fe871bcafb28a59c456795aae4f5c4b739e43",
|
|
16
17
|
"local-preserved-mutation-protection.mjs": "95041d4f75253a153c7fd85573ba0457513ffe47a6a6bd00c4f59379b81ce580",
|
|
17
18
|
"local-preserved-mutation-signer.mjs": "2b0771cbdd18ba00267aa441b7892cd04feab9f8543ae5e2703430b1e580747f",
|
|
18
|
-
"local-preserved-mutation-state.mjs": "
|
|
19
|
-
"local-preserved-mutation-wire.mjs": "
|
|
19
|
+
"local-preserved-mutation-state.mjs": "337a172b2d0a03b0d5a89b8483afdd9a043a4999fe38f87679633c98c450126b",
|
|
20
|
+
"local-preserved-mutation-wire.mjs": "2f2f623e768d49af59dbb9bb8245627cb7e72184e5a5d3239d91f395152cf9bf",
|
|
20
21
|
"local-delegated-modify-analysis.mjs": "f87148ddb023b79e241ca97db582991da8d0e1a51b26b1d9bc2799a76efd7297",
|
|
21
|
-
"mutation-client.mjs": "
|
|
22
|
+
"mutation-client.mjs": "4d5fadb0277ce8cc1eaf96f613ef496201910e64d21355627ec68d88c01b5eb9",
|
|
22
23
|
"cloud-mutation-authority.mjs": "c7226e882a4f3be4d2f55728f515d5a815c0aa1f21aa5e4689314e6e6a8c66a2",
|
|
23
24
|
"ide-code-mode.mjs": "f8ba4de33632ab4c6df52dab52f392414b4632c7e94b218b8065677970c075c7",
|
|
24
|
-
"ide-command.mjs": "
|
|
25
|
-
"ide-hook.mjs": "
|
|
26
|
-
"ide-install.mjs": "
|
|
25
|
+
"ide-command.mjs": "3db45dd1d12db670e594b6ced35f2d7778cb01091695b1e240179b1ea34f25ee",
|
|
26
|
+
"ide-hook.mjs": "ae097c61db71a95096f3c3b30b4c59dcab41aedbf88445390b8ad98f81e4cec0",
|
|
27
|
+
"ide-install.mjs": "6322f76d8c0f5c864d8c61f4ee90ae029ccf4bbf1e31fbca7590b78b6c7eaa78",
|
|
27
28
|
"ide-session.mjs": "6df04bca3231954e8b83f6b1b535befff16b6c62c51ca65ae3aa2559180ad7c2",
|
|
28
29
|
"break-glass.mjs": "6ec9f97c5d68bb33121b2374c06b9d4c8eb3419b52d00849fec7124bc63e4582",
|
|
29
|
-
"ide-results.mjs": "
|
|
30
|
+
"ide-results.mjs": "baa0618845ced901334644479bf4e0d298bad3f955bc18846752de6c283a5573",
|
|
31
|
+
"ide-local-acceptance.mjs": "238eec6d7b6093fb8ad022da0ed3383a48ba2ffcf693e3ce8a241cf9624088d8",
|
|
30
32
|
"ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
|
|
31
33
|
"local-discipline-actions.mjs": "418c220b8c8ef59cb8092b6eb680bb75a93b9fad1a09412b1efa98a78afff11b",
|
|
32
34
|
"local-discipline-analysis.mjs": "e887abdd1c6d6ba59ef7323e9035528f0491e5458ca8159985ec26e1ca67a4af",
|
|
33
35
|
"local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
|
|
34
36
|
"local-typescript-function-reference.mjs": "af682b479fe0d69a5ecd62232406bb8078408ec8710733cfed9ae6b059762ba5",
|
|
35
37
|
"local-scope-import-expressions.mjs": "20f1e065582c68d2334e0dfaa130a559ff19886a57f9038dc569f65993bb06c8",
|
|
36
|
-
"local-discipline-contracts.mjs": "
|
|
38
|
+
"local-discipline-contracts.mjs": "ab0c11dcc4a6ffd3cb79b53862c19308eaf0d159deee7f9958266852d977363e",
|
|
37
39
|
"local-discipline-contracts.d.mts": "defc684c78dc88afa1c54340ab4a01ae36db7aa11257cbe1f5cd323f608c78c4",
|
|
38
40
|
"local-discipline-gate.mjs": "e3851157bf1d8bc86eb44212a88acd20acfc2d1152e7d87af6dc2a37afffcd6f",
|
|
39
41
|
"local-discipline-native.mjs": "8b99b5ce951cab3c1426428ad807650c61c0ffb2c346d747b73b99057b382049",
|
|
40
42
|
"local-discipline-operations.mjs": "9fcb7b8889b7718d42e1d37936485d8198d5017d405e0cdbbabdbbd12fcc9d48",
|
|
41
43
|
"local-discipline-repository.mjs": "b6392e02ecae333dfdc8df41f203fd69bed035d88b15b16d112ab1897b3c297c",
|
|
42
44
|
"local-discipline-snapshot.mjs": "8fd35b2cfbece8f64579f144f589a66dec9b6022d50fe931c4c1f886cb83134d",
|
|
43
|
-
"local-runner-discipline.mjs": "
|
|
45
|
+
"local-runner-discipline.mjs": "8d4ada838f81d54963adbb7e433f1ca80ae1934ea126f90c59a49d29742979c4",
|
|
44
46
|
"local-integrity.d.mts": "21357f306234bd841c0d3d90bf2d09f7663f63746f897b9f38190428ae8198b3",
|
|
45
47
|
"local-equivalence-runner.mjs": "df2f406c693c4da7bb30542ec7b4d46a9336f56e9fdd263196c0f3a73d2f6e91",
|
|
46
48
|
"validator-runtime-build.mjs": "da20bd19eb088d79d62e33969b4c075dc2199761b395e40060227f8f86b9519a",
|
|
47
49
|
"runtime/validator-runtime-v7.0.44.mjs": "a3bf9aa24146e48ad5702dd498de9bd1ff270fc4263026abf0bf2a4ec20f5321",
|
|
48
50
|
"runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
|
|
49
|
-
"snapshot-retention.mjs": "
|
|
51
|
+
"snapshot-retention.mjs": "ca46aeb36b1a5a24fe7e967031a71b9a3d33f40cb715d344402cbe0dc9c425cb",
|
|
50
52
|
"snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
|
|
51
|
-
"IDE-EXECUTION.md": "
|
|
53
|
+
"IDE-EXECUTION.md": "cb98887cc9a905a5a69c14f8e95cdffc7801c8a2bf8c60f6b7975867e2f722e7",
|
|
52
54
|
"REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
|
|
53
|
-
"reuse-discipline-release.mjs": "
|
|
55
|
+
"reuse-discipline-release.mjs": "7f55ae10ec3396ed3bc5430a0c64f94c33c2dfbc57b32084caab92fd5fd1586f",
|
|
54
56
|
"local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
|
|
55
57
|
"local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
|
|
56
58
|
"local-final-gate16-worker.mjs": "344d64b6c1c5552b96960e0d8514a876a3dbfa9762ab9eb749038e8351727121",
|
|
@@ -96,13 +98,13 @@
|
|
|
96
98
|
"install-cleanup.mjs": "8a8e1a7dfe37427f86cd4d6e317890d0829894a9949d4216674a716b36edbc54",
|
|
97
99
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
98
100
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
99
|
-
"SKILL.md": "
|
|
100
|
-
"CAPABILITY-MAP.md": "
|
|
101
|
+
"SKILL.md": "96f83041a2f9933876e6a94f487e472e95d029251375449336ae4fc535801ef9",
|
|
102
|
+
"CAPABILITY-MAP.md": "0e85f26ebf2a5be5e6e5d2af66f910db03c69cb268edad1b2541dd3834991f90",
|
|
101
103
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
102
104
|
"DELIVERY-BOUNDARIES.md": "9b9db50ed1bcce9e77ff6527b6bb7eaf12f3b913162ea9cb7fe90db06b393a4f",
|
|
103
|
-
"package.json": "
|
|
105
|
+
"package.json": "3fbb42236d00d8c5a92c1cc4c75de4cea0581b8ee22721fe57aa760513744787",
|
|
104
106
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
105
|
-
"release.mjs": "
|
|
107
|
+
"release.mjs": "a0693132999f5be6030761c3992071d7416a75471f6b73bdd720d52f55ea3c3f",
|
|
106
108
|
"local-cli.mjs": "41e2a737c8587cb5ce469a7661f6d14b5ea4cd073f8286822fc4dc96b67f56a1",
|
|
107
109
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
108
110
|
"local-execution.mjs": "185f34e917440a360b3c4bafd3af245243807d964ae3820b3cffaaa2e6348cc6",
|
|
@@ -123,7 +125,7 @@
|
|
|
123
125
|
"local-chain-plan.mjs": "e45a9170f569c80f7e5532bbc109dbae3c24b696815316e503cb3779135be648",
|
|
124
126
|
"local-chain-release-labels.mjs": "7ce5350d1062d5236f57f10e7e81713c1101463d5e7f252604ef0c2a30840bfe",
|
|
125
127
|
"local-chain-results.mjs": "0455f72cad574cfef87c2c6c0928d6432bba7e5dbaff521a2b62a9575299ca10",
|
|
126
|
-
"local-approved-plan.mjs": "
|
|
128
|
+
"local-approved-plan.mjs": "c2c88ab4085ff35a1811fe43e9a28699cfc9387eda54bfeafae3e7dd9e8cdb19",
|
|
127
129
|
"local-budget-renewal.mjs": "6b436358066fe33b2f6d871fc90d8e7c551edd6255efd73c0d255d0fd35d5ef8",
|
|
128
130
|
"local-probe.mjs": "9b948db1de1e34c7605c4c454cd4aced66d2b57d2cf4471adbb80bcc76dee635",
|
|
129
131
|
"local-budget-grants.mjs": "c5f6542f5d2c3bfe07f3ca5b5bc849bf05d886b06014f720322d530510775d3d",
|
|
@@ -183,7 +185,7 @@
|
|
|
183
185
|
"cloud-watch-client.mjs": "5b93935c19a104d72afed07d4a56746c9617707e075884b93d22050c95cee0c1",
|
|
184
186
|
"cloud-event-stream.mjs": "e0eec9fc3e0613094523929634f25030df5ca5c5b5fe2c1e1a4586ab10574808",
|
|
185
187
|
"ide-presentation.mjs": "3bac21bfc824fcdad63aafb0d7c3326cd6456923f4224106dd5c38682060f315",
|
|
186
|
-
"automatic-feedback-client.mjs": "
|
|
188
|
+
"automatic-feedback-client.mjs": "532312d29e66fa211ea3fe879d9add1dd48841ee5ce82800b4b06252a160a255",
|
|
187
189
|
"task-entry-client.mjs": "869f234402c4149eadf1ec138705fc140e2fa16b2a3ea5dc2b2f51dfb189d7de",
|
|
188
190
|
"task-identity.mjs": "e0e6cf1f48bfa5ee3c5cb86a3c21adfce6d66b4220f4a164ca099263c7a09fb4",
|
|
189
191
|
"task-identity.d.mts": "55472e1ffc81b9d5e5c5bd56c0bf1d18ecde03ee5170a58a15ce9caf222d8761",
|
|
@@ -201,8 +203,8 @@
|
|
|
201
203
|
"local-task-handoff-read-authority.mjs": "7d2991cfac4562039232566d25441aa98106ce27bc9b7bcf6df4ff28d738e167",
|
|
202
204
|
"local-task-handoff-write-archive.mjs": "3668f39d8ab5664ed717994cf1b4bcc7ea23e146a0e06291f851d30337faeb77",
|
|
203
205
|
"local-task-handoff-write-receipts.mjs": "e1321127bd6fcad7840e185639f81ae68a8722a0ee4264a7b504a02b27887647",
|
|
204
|
-
"workflow-local-runner.mjs": "
|
|
205
|
-
"workflow-local-contracts.mjs": "
|
|
206
|
+
"workflow-local-runner.mjs": "baeb0178aa9260f2e02c5c8bc3f1a4934827a5552d0db18fe8ad6acfee579459",
|
|
207
|
+
"workflow-local-contracts.mjs": "ed7807c6230c3f9a2bd7c7a0ca255aec5e8e922fe71a75b95904d87709a615b6",
|
|
206
208
|
"workflow-local-client.mjs": "45c954f694d96bf0ea87ee2dc6902a6763dac2cb1df713b6a5e749632054e9af",
|
|
207
209
|
"workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
|
|
208
210
|
"task-message-entry.mjs": "31d10056cb905237cd5649e8eb067009ff1612ec5dc1061e0373209fbc0b646a",
|
|
@@ -237,7 +239,7 @@
|
|
|
237
239
|
"local-mutation-files.mjs": "087b00e8424a135e783eb432271012a71e55eeb4a5e30119490d1bc0b2160320",
|
|
238
240
|
"local-mutation-snapshot.mjs": "f9077539680fb8d3ff33a962657756a8fe6afa8409b6191334509534216ebfe8",
|
|
239
241
|
"local-mutation-audit.mjs": "5a30ee3446f632e032ffcfe46bcd97d6dfc0f099a76ea5185c3f9a9118c4f6cb",
|
|
240
|
-
"local-mutation-wire.mjs": "
|
|
242
|
+
"local-mutation-wire.mjs": "52f3e4bd90e246abd47163199fed508d9a243770580bb8617bbf58f1da28e01f",
|
|
241
243
|
"local-mutation-binding.mjs": "4440d792453a07a375b635fa5a91db607e58162824baced5c79a9a513f4ec918",
|
|
242
244
|
"local-mutation-pass-store.mjs": "1edc5fcc537dfa04b8ae8b07af554d61ea5911a5da281423ad57214ccf753ad4",
|
|
243
245
|
"local-mutation-authority.mjs": "ebac0cc8126969bafb0aaaeacf71e05124997d5c3214f8ec32b7a4fa14569bcf",
|
package/release.mjs
CHANGED
|
@@ -38,6 +38,7 @@ const memberContinuationSourceFiles = Object.freeze([
|
|
|
38
38
|
'local-approved-process.mjs',
|
|
39
39
|
'local-approved-supervisor.mjs',
|
|
40
40
|
'local-mutation-consumption.mjs',
|
|
41
|
+
'workflow-local-acceptance.mjs',
|
|
41
42
|
'workflow-observation-client.mjs',
|
|
42
43
|
'workflow-observation-connection.mjs',
|
|
43
44
|
'workflow-observation-remote.mjs',
|
package/releases.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.57",
|
|
4
|
+
"date": "2026-10-17",
|
|
5
|
+
"zh": "云端关闭时以原本地 IDE 执行与实际测试完成本地验收,补齐签名步骤续跑、最终验收、钩子与队列恢复;保留原范围、审批和 S/M 证据,明确未做独立验证。修复原规划字段诊断与沙箱模式恢复。",
|
|
6
|
+
"en": "Completed explicit local IDE acceptance with real execution and tests when cloud sandbox is disabled, including signed step progress, final acceptance, hooks and queue recovery while preserving original scope, approvals and S/M evidence without claiming independent verification. Added precise planning diagnostics and original sandbox-mode recovery."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.56",
|
|
10
|
+
"date": "2026-10-17",
|
|
11
|
+
"zh": "补齐原工作流显式只读步骤及无写入结算的本地观察,复核真实受检仓库字节与权限;修复流程列表空输入兼容,返回精确规划前置条件与范围错误,并配置 GitHub OIDC 发布。",
|
|
12
|
+
"en": "Added explicit read-only workflow steps and local observations without fabricated mutation settlements, checking actual repository bytes and modes; fixed empty-input flow listing, precise planning prerequisite and scope errors, and GitHub OIDC publishing metadata."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.55",
|
|
4
16
|
"date": "2026-10-17",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'break-glass.mjs', 'ide-results.mjs', 'ide-queue.mjs'];
|
|
1
|
+
const ide = ['ide-code-mode.mjs', 'ide-command.mjs', 'ide-hook.mjs', 'ide-install.mjs', 'ide-session.mjs', 'break-glass.mjs', 'ide-results.mjs', 'ide-local-acceptance.mjs', 'ide-queue.mjs'];
|
|
2
2
|
const discipline = ['local-discipline-actions.mjs', 'local-discipline-analysis.mjs', 'local-discipline-batch.mjs',
|
|
3
3
|
'local-typescript-function-reference.mjs', 'local-scope-import-expressions.mjs',
|
|
4
4
|
'local-discipline-contracts.mjs', 'local-discipline-contracts.d.mts', 'local-discipline-gate.mjs',
|
package/snapshot-retention.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { canonical, sha256, verifySnapshotManifest } from './local-integrity.mjs
|
|
|
5
5
|
import { withStateLock } from './local-state-lock.mjs';
|
|
6
6
|
import { mutationStateRoot14, atomicMutationJson14, privateMutationDirectory14 } from './local-mutation-files.mjs';
|
|
7
7
|
import { resumeSnapshotRemoval, removeSnapshotWithJournal } from './snapshot-cleanup-journal.mjs';
|
|
8
|
+
import { verifiedLocalWorkflowCompletion } from './ide-local-acceptance.mjs';
|
|
8
9
|
|
|
9
10
|
const id = value => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value);
|
|
10
11
|
async function realDirectory(directory) {
|
|
@@ -24,9 +25,14 @@ async function safeTree(directory) {
|
|
|
24
25
|
}
|
|
25
26
|
return bytes;
|
|
26
27
|
}
|
|
27
|
-
function completedProof(call, result) {
|
|
28
|
+
async function completedProof(call, result, home) {
|
|
28
29
|
if (call.operation !== 'continue' || call.input?.operation !== 'workflow-verdict' || !id(call.input.flowId)) return null;
|
|
29
30
|
const output = result?.output, receipt = result?.feedback;
|
|
31
|
+
if (await verifiedLocalWorkflowCompletion(call, result, home)) return { flowId: output.flowId, requestId: output.requestId,
|
|
32
|
+
receiptId: receipt.receiptId, evaluationDigest: receipt.evaluation.digest,
|
|
33
|
+
verificationMode: 'member-local', independentValidation: 'not-performed' };
|
|
34
|
+
if (output?.localAcceptance || output?.result?.localAcceptance || output?.status === 'local-accepted'
|
|
35
|
+
|| output?.result?.status === 'local-accepted') return null;
|
|
30
36
|
const reports = output?.result?.reports ?? output?.reports;
|
|
31
37
|
if (output?.status !== 'succeeded' || output.flowId !== call.input.flowId || !Array.isArray(reports) || !reports.length
|
|
32
38
|
|| reports.some(report => !['pass','pass-with-risk'].includes(report.verdict)) || receipt?.requestId !== output.requestId
|
|
@@ -74,7 +80,7 @@ async function cleanupRepository(root, repository, proof) {
|
|
|
74
80
|
return { removed, retained: [...retained].sort(), releasedBytes };
|
|
75
81
|
}
|
|
76
82
|
export async function cleanupCompletedSnapshots(call, result, { home = os.homedir() } = {}) {
|
|
77
|
-
const proof = completedProof(call, result);
|
|
83
|
+
const proof = await completedProof(call, result, home);
|
|
78
84
|
if (!proof) return { status: 'preserved', reason: 'verified-original-completion-required' };
|
|
79
85
|
const root = await mutationStateRoot14({ home }), snapshotRoot = path.join(root, 'snapshots');
|
|
80
86
|
if (!await realDirectory(snapshotRoot)) return { status: 'completed', removed: 0, releasedBytes: 0 };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
2
|
+
import { observationExact, observationFail, observationAuthoritySignature } from './workflow-observation-wire.mjs';
|
|
3
|
+
|
|
4
|
+
const identity = ['flowId', 'ownerId', 'taskId', 'taskDigest', 'chainDigest', 'planId', 'planDigest',
|
|
5
|
+
'stepId', 'executionPlanSha256', 'leaseId', 'generation', 'observationId'];
|
|
6
|
+
const digestFields = ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256', 'leaseDigest', 'admissionDigest', 'resultDigest'];
|
|
7
|
+
const checks = ['sourceManifestUnchanged', 'protectedFilesUnchanged', 'repositoryUnchanged'];
|
|
8
|
+
export function verifyWorkflowLocalAcceptance(value, admission, result, reportRequestId) {
|
|
9
|
+
observationExact(value, ['schemaVersion', 'status', 'verificationMode', 'independentValidation', 'runnerKeyId',
|
|
10
|
+
...identity, 'leaseDigest', 'admissionDigest', 'resultDigest', 'requestId', 'acceptedAt', 'authorityReceipt']);
|
|
11
|
+
observationAuthoritySignature(value);
|
|
12
|
+
if (value.schemaVersion !== 'entrorove.member-local-step-acceptance/1.0' || value.status !== 'local-accepted'
|
|
13
|
+
|| value.verificationMode !== 'member-local' || value.runnerKeyId !== null || value.independentValidation !== 'not-performed'
|
|
14
|
+
|| identity.some(field => value[field] !== admission[field])
|
|
15
|
+
|| digestFields.some(field => typeof value[field] !== 'string' || !/^[a-f0-9]{64}$/.test(value[field]))
|
|
16
|
+
|| value.admissionDigest !== sha256(canonical(admission)) || value.resultDigest !== sha256(canonical(result))
|
|
17
|
+
|| !/^[a-f0-9]{64}$/.test(reportRequestId ?? '') || value.requestId !== reportRequestId
|
|
18
|
+
|| typeof value.acceptedAt !== 'string' || !Number.isFinite(Date.parse(value.acceptedAt))
|
|
19
|
+
|| new Date(value.acceptedAt).toISOString() !== value.acceptedAt) {
|
|
20
|
+
observationFail('Local acceptance lost its original signed admission, process result, lease or verification role');
|
|
21
|
+
}
|
|
22
|
+
observationExact(result.checks, checks);
|
|
23
|
+
const process = result.process;
|
|
24
|
+
if (result.schemaVersion !== 'entrorove.local-observation-result/1.0' || result.status !== 'incomplete'
|
|
25
|
+
|| result.runner !== 'local' || result.receipt !== null || result.independentValidation !== 'pending'
|
|
26
|
+
|| result.admissionDigest !== value.admissionDigest || result.observationId !== value.observationId
|
|
27
|
+
|| result.sourceManifestDigest !== sha256(canonical(admission.sourceManifest))
|
|
28
|
+
|| result.executionIsolation !== 'host-permissions' || result.externalEffects !== 'unverified'
|
|
29
|
+
|| process?.started !== true || process.status !== 'succeeded' || process.exitCode !== 0 || process.signal !== null
|
|
30
|
+
|| process.timedOut !== false || process.outputLimitExceeded !== false || checks.some(field => result.checks[field] !== true)
|
|
31
|
+
|| !/^[a-f0-9]{64}$/.test(result.beforeRepositoryDigest ?? '') || result.beforeRepositoryDigest !== result.afterRepositoryDigest) {
|
|
32
|
+
observationFail('Local acceptance cannot replace a failed, changed or independently unverified process outcome');
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
@@ -40,11 +40,19 @@ export const WORKFLOW_EXECUTION_PLAN_SCHEMA = exact({ schemaVersion: { const: 'v
|
|
|
40
40
|
});
|
|
41
41
|
export const WORKFLOW_ACCEPTED_PLAN_SCHEMA = exact({ schemaVersion: { const: 'entrorove.workflow-local-plan/1.0' },
|
|
42
42
|
planId: id, flowId: id, ownerId: id, taskDigest: sha256, chainDigest: sha256, chainRevision: integer(1),
|
|
43
|
-
acceptedAt: timestamp, expiresAt: timestamp, runnerKeyId: sha256,
|
|
43
|
+
acceptedAt: timestamp, expiresAt: timestamp, runnerKeyId: { anyOf: [sha256, { type: 'null' }] },
|
|
44
44
|
steps: boundedArray(exact({ stepId: id, sequence: integer(1, 128),
|
|
45
45
|
dependsOn: { ...boundedArray(id, 0, 128), uniqueItems: true }, executionPlan: WORKFLOW_EXECUTION_PLAN_SCHEMA,
|
|
46
46
|
executionPlanSha256: sha256 }), 1, 128), authorityReceipt,
|
|
47
47
|
});
|
|
48
|
+
WORKFLOW_ACCEPTED_PLAN_SCHEMA.properties.verificationMode = enumeration(['independent', 'member-local']);
|
|
49
|
+
WORKFLOW_ACCEPTED_PLAN_SCHEMA.properties.memberScopeKeyId = sha256;
|
|
50
|
+
WORKFLOW_ACCEPTED_PLAN_SCHEMA.properties.memberScopeBindingDigest = sha256;
|
|
51
|
+
WORKFLOW_ACCEPTED_PLAN_SCHEMA.allOf = [{
|
|
52
|
+
if: { properties: { verificationMode: { const: 'member-local' } }, required: ['verificationMode'] },
|
|
53
|
+
then: { properties: { runnerKeyId: { type: 'null' } }, required: ['memberScopeKeyId', 'memberScopeBindingDigest'] },
|
|
54
|
+
else: { properties: { runnerKeyId: sha256, memberScopeKeyId: false, memberScopeBindingDigest: false } },
|
|
55
|
+
}];
|
|
48
56
|
WORKFLOW_ACCEPTED_PLAN_SCHEMA.properties.blueprintBinding = exact({
|
|
49
57
|
schemaVersion: { const: 'entrorove.blueprint-plan-binding/1.0' }, blueprintId: id, revision: integer(0),
|
|
50
58
|
blueprintSha256: { type: 'string', pattern: '^sha256:[0-9a-f]{64}$' }, machineTasksDigest: sha256,
|
|
@@ -54,7 +62,16 @@ export const WORKFLOW_LOCAL_LEASE_SCHEMA = exact({ schemaVersion: { const: 'entr
|
|
|
54
62
|
chainRevision: integer(1), stepId: id, sequence: integer(1, 128), generation: integer(1), issuedAt: timestamp, expiresAt: timestamp,
|
|
55
63
|
verifiedDependencies: { ...boundedArray(exact({ stepId: id, evidenceDigest: sha256 }), 0, 128), uniqueItems: true }, authorityReceipt,
|
|
56
64
|
});
|
|
65
|
+
WORKFLOW_LOCAL_LEASE_SCHEMA.properties.localDependencies = { ...boundedArray(exact({ stepId: id, acceptanceDigest: sha256 }), 0, 128), uniqueItems: true };
|
|
66
|
+
WORKFLOW_LOCAL_LEASE_SCHEMA.allOf = [{ if: { required: ['localDependencies'] },
|
|
67
|
+
then: { properties: { verifiedDependencies: { type: 'array', maxItems: 0 } } } }];
|
|
57
68
|
export const WORKFLOW_LOCAL_OPERATION_SCHEMA = object({ repositoryRoot: absolutePath, home: absolutePath,
|
|
58
69
|
acceptedPlan: WORKFLOW_ACCEPTED_PLAN_SCHEMA, lease: WORKFLOW_LOCAL_LEASE_SCHEMA, stepId: id,
|
|
59
70
|
approvalPath: absolutePath, signerConfigPath: absolutePath,
|
|
60
71
|
}, ['repositoryRoot', 'acceptedPlan', 'lease', 'stepId', 'approvalPath', 'signerConfigPath']);
|
|
72
|
+
WORKFLOW_LOCAL_OPERATION_SCHEMA.allOf = [{
|
|
73
|
+
if: { properties: { acceptedPlan: { type: 'object', properties: { verificationMode: { const: 'member-local' } }, required: ['verificationMode'] } } },
|
|
74
|
+
then: { properties: { lease: { type: 'object', required: ['localDependencies'],
|
|
75
|
+
properties: { verifiedDependencies: { type: 'array', maxItems: 0 } } } } },
|
|
76
|
+
else: { properties: { lease: { type: 'object', properties: { localDependencies: false } } } },
|
|
77
|
+
}];
|
|
@@ -64,6 +64,7 @@ function lifetime(start, end, max, now, permitExpired = false) {
|
|
|
64
64
|
}
|
|
65
65
|
function validatePlan(plan) {
|
|
66
66
|
exact(plan, ['schemaVersion', 'planId', 'flowId', 'ownerId', 'taskDigest', 'chainDigest', 'chainRevision', 'acceptedAt', 'expiresAt', 'runnerKeyId', 'steps',
|
|
67
|
+
...['verificationMode', 'memberScopeKeyId', 'memberScopeBindingDigest'].filter(field => Object.hasOwn(plan, field)),
|
|
67
68
|
...(plan.blueprintBinding ? ['blueprintBinding'] : [])], 'ENTROROVE_WORKFLOW_PLAN_FIELDS', 'accepted plan');
|
|
68
69
|
if (plan.blueprintBinding) {
|
|
69
70
|
const binding = exact(plan.blueprintBinding, ['schemaVersion', 'blueprintId', 'revision', 'blueprintSha256', 'machineTasksDigest'],
|
|
@@ -74,8 +75,12 @@ function validatePlan(plan) {
|
|
|
74
75
|
fail('ENTROROVE_WORKFLOW_BLUEPRINT_BINDING', 'Blueprint plan binding is invalid');
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
const local = plan.verificationMode === 'member-local';
|
|
79
|
+
const validRole = local ? plan.runnerKeyId === null && SHA.test(plan.memberScopeKeyId ?? '') && SHA.test(plan.memberScopeBindingDigest ?? '')
|
|
80
|
+
: (plan.verificationMode === undefined || plan.verificationMode === 'independent') && SHA.test(plan.runnerKeyId ?? '')
|
|
81
|
+
&& !Object.hasOwn(plan, 'memberScopeKeyId') && !Object.hasOwn(plan, 'memberScopeBindingDigest');
|
|
77
82
|
if (plan.schemaVersion !== WORKFLOW_LOCAL_PLAN_SCHEMA || !SHA.test(plan.taskDigest) || !SHA.test(plan.chainDigest)
|
|
78
|
-
|| !
|
|
83
|
+
|| !validRole || !positive(plan.chainRevision) || !Array.isArray(plan.steps) || !plan.steps.length || plan.steps.length > 128) fail('ENTROROVE_WORKFLOW_PLAN_FIELDS', 'accepted plan contract is invalid');
|
|
79
84
|
for (const field of ['planId', 'flowId', 'ownerId']) safeId(plan[field], field);
|
|
80
85
|
const seen = new Set();
|
|
81
86
|
for (const [index, step] of plan.steps.entries()) {
|
|
@@ -280,13 +285,16 @@ async function prepare(input, create = true) {
|
|
|
280
285
|
if (input.executionAttemptNonce !== undefined) safeId(input.executionAttemptNonce, 'executionAttemptNonce');
|
|
281
286
|
if (process.platform === 'win32' || typeof process.getuid !== 'function') fail('ENTROROVE_POSIX_REQUIRED', 'workflow local runner requires POSIX');
|
|
282
287
|
const configured = publicKey(process.env.ENTROROVE_WORKFLOW_PLAN_PUBLIC_KEY?.trim(), 'ENTROROVE_WORKFLOW_AUTHORITY_KEY');
|
|
283
|
-
const plan = validatePlan(verifyAuthority(input.acceptedPlan, configured));
|
|
288
|
+
const plan = validatePlan(verifyAuthority(input.acceptedPlan, configured));
|
|
289
|
+
if (plan.verificationMode === 'member-local') fail('ENTROROVE_INDEPENDENT_VERIFICATION_REQUIRED',
|
|
290
|
+
'A member-local plan cannot run through the independent Validator signer entry; use its original workflow-observe-step action');
|
|
291
|
+
const lease = verifyAuthority(input.lease, configured);
|
|
284
292
|
const step = validateLease(lease, plan, input.stepId);
|
|
285
293
|
lifetime(plan.acceptedAt, plan.expiresAt, MAX_PLAN_MS, Date.now(), true); lifetime(lease.issuedAt, lease.expiresAt, MAX_LEASE_MS, Date.now(), true);
|
|
286
294
|
const root = await realDirectory(input.repositoryRoot, 'ENTROROVE_ROOT_INVALID');
|
|
287
295
|
const home = await realDirectory(input.home ?? os.homedir(), 'ENTROROVE_HOME_INVALID');
|
|
288
296
|
const directory = await privateHierarchy(home, ['.entrorove', 'workflow-local', sha(root).slice(0, 32), plan.flowId, step.stepId], create);
|
|
289
|
-
return { root, plan, lease, step, file: path.join(directory, 'state.json') };
|
|
297
|
+
return { root, home, plan, lease, step, file: path.join(directory, 'state.json') };
|
|
290
298
|
}
|
|
291
299
|
|
|
292
300
|
export async function observeWorkflowLocalStep(rawInput) {
|
|
@@ -323,7 +331,7 @@ export async function runWorkflowLocalStep(rawInput) {
|
|
|
323
331
|
});
|
|
324
332
|
const signer = await loadSigner(authority.root, input.signerConfigPath, authority.plan.runnerKeyId);
|
|
325
333
|
if (typeof approvedRunner.runApprovedPlanObject !== 'function') fail('ENTROROVE_WORKFLOW_RUNNER_UNAVAILABLE', 'approved object plan runner is unavailable');
|
|
326
|
-
execution = await approvedRunner.runApprovedPlanObject({ repositoryRoot: authority.root, executionPlan: authority.step.executionPlan,
|
|
334
|
+
execution = await approvedRunner.runApprovedPlanObject({ repositoryRoot: authority.root, home: authority.home, flowId: authority.plan.flowId, executionPlan: authority.step.executionPlan,
|
|
327
335
|
approvalPath: input.approvalPath, signerConfigPath: input.signerConfigPath, executionDeadlineAt: authority.lease.expiresAt });
|
|
328
336
|
return await finalize(authority, signedResult(authority, execution, signer));
|
|
329
337
|
} catch (error) {
|
|
@@ -9,6 +9,7 @@ const messages = {
|
|
|
9
9
|
ENTROROVE_LOCAL_OBSERVATION_PREREQUISITE: 'Local observation requires cloud sandbox disabled and its original live execution lease',
|
|
10
10
|
ENTROROVE_SCOPE_RECEIPT_EXPIRED: 'Refresh the original scope evidence and reuse the original observation input',
|
|
11
11
|
ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED: 'Observe the settled modifications in fresh scope evidence before local testing',
|
|
12
|
+
ENTROROVE_MEMBER_LOCAL_MODE_PREREQUISITE: 'Disable cloud sandbox and reuse only the original cached process report; do not restart its process or source writes',
|
|
12
13
|
};
|
|
13
14
|
function blocked(error, input) {
|
|
14
15
|
const cause = error && typeof error === 'object' ? error : {};
|
|
@@ -16,15 +17,19 @@ function blocked(error, input) {
|
|
|
16
17
|
? cause.code : 'ENTROROVE_LOCAL_OBSERVATION';
|
|
17
18
|
const output = cause.output, requestId = safeRequest(cause.requestId) ? cause.requestId : undefined;
|
|
18
19
|
const recoverable = output?.status === 'blocked' && output.recoverable === true
|
|
19
|
-
&& (output.observationPrerequisite?.effect === 'none' ||
|
|
20
|
+
&& (output.observationPrerequisite?.effect === 'none' || code === 'ENTROROVE_MEMBER_LOCAL_MODE_PREREQUISITE'
|
|
21
|
+
&& output.effects === 'none' && output.localModePrerequisite?.originalOperation === 'member-observation-report'
|
|
22
|
+
&& output.localModePrerequisite.serverEffect === 'none' && output.localModePrerequisite.localProcessMayRestart === false
|
|
23
|
+
&& output.localModePrerequisite.cachedLocalProcessResultRequired === true || ['ENTROROVE_SCOPE_RECEIPT_EXPIRED',
|
|
20
24
|
'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)
|
|
25
|
+
const reason = ['sandbox-enabled', 'cloud-sandbox-enabled', 'lease-expired', 'receipt-expired', 'after-write-observation-required'].includes(output?.reason)
|
|
22
26
|
? output.reason : undefined;
|
|
23
27
|
return { schemaVersion: 'entrorove.local-observation-connection/1.0', status: 'blocked',
|
|
24
28
|
...(safeId(input?.flowId) ? { flowId: input.flowId } : {}), independentValidation: 'pending', code,
|
|
25
29
|
message: messages[code] ?? 'Local observation could not continue; its original command and result remain preserved',
|
|
26
30
|
...(recoverable ? { prerequisite: { status: 'blocked', code, recoverable: true, ...(reason ? { reason } : {}),
|
|
27
31
|
nextAction: { operation: 'workflow-observe-step', reuseOriginalInput: true,
|
|
32
|
+
...(code === 'ENTROROVE_MEMBER_LOCAL_MODE_PREREQUISITE' ? { localProcessMayRestart: false, cachedLocalProcessResultRequired: true } : {}),
|
|
28
33
|
...(safeRequest(output.requestId) ? { resumeRequestId: output.requestId } : {}) } } } : {}),
|
|
29
34
|
...(cause.outcomeUnknown === true && requestId ? { outcomeUnknown: true, requestId, repeatPost: false,
|
|
30
35
|
nextAction: { operation: 'workflow-observe-step', reuseOriginalInput: true, method: 'GET' } } : {}),
|
|
@@ -5,9 +5,12 @@ import { observationAdmission, observationFail } from './workflow-observation-wi
|
|
|
5
5
|
import { observationLocation, readObservationState } from './workflow-observation-state.mjs';
|
|
6
6
|
import { runWorkflowObservation } from './workflow-observation-runner.mjs';
|
|
7
7
|
import { observationRemote, authorizeObservation } from './workflow-observation-remote.mjs';
|
|
8
|
+
import { verifyWorkflowLocalAcceptance } from './workflow-local-acceptance.mjs';
|
|
9
|
+
import { buildInvocation } from './remote-client.mjs';
|
|
8
10
|
|
|
9
11
|
async function connect(input, state, location, dependencies) {
|
|
10
|
-
const
|
|
12
|
+
const fields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation', ...(Object.hasOwn(input, 'settlementId') ? ['settlementId'] : [])];
|
|
13
|
+
const original = Object.fromEntries(fields.map(key => [key, input[key]]));
|
|
11
14
|
const admitted = state.admitted ?? await observationRemote('admitted', 'member-observation-admit', original, state, location, dependencies);
|
|
12
15
|
const admission = observationAdmission(admitted.admission, input, { allowExpired: Boolean(state.result) });
|
|
13
16
|
await authorizeObservation(admission, dependencies, !state.result);
|
|
@@ -21,12 +24,25 @@ async function connect(input, state, location, dependencies) {
|
|
|
21
24
|
const reported = state.reported ?? await observationRemote('reported', 'member-observation-report', {
|
|
22
25
|
observationId: admission.observationId, admissionDigest: sha256(canonical(admission)), result,
|
|
23
26
|
}, state, location, dependencies);
|
|
24
|
-
if (reported.observationId !== admission.observationId ||
|
|
27
|
+
if (reported.observationId !== admission.observationId || !['pending-independent', 'local-accepted'].includes(reported.observationStatus)
|
|
25
28
|
|| reported.resultDigest !== sha256(canonical(result)) || canonical(reported.result) !== canonical(result)) {
|
|
26
29
|
observationFail('Stored local observation differs from its actual original result');
|
|
27
30
|
}
|
|
28
|
-
|
|
31
|
+
const summary = { ...result, taskId: input.taskId, flowId: input.flowId, stepId: input.stepId, cached, stored: true,
|
|
29
32
|
resultDigest: sha256(canonical(result)), grantsExecutionAuthority: false, grantsValidatorAuthority: false };
|
|
33
|
+
if (reported.observationStatus !== 'local-accepted') {
|
|
34
|
+
if (Object.hasOwn(reported, 'localAcceptance')) observationFail('A pending observation cannot claim local acceptance');
|
|
35
|
+
return summary;
|
|
36
|
+
}
|
|
37
|
+
const requestId = buildInvocation('member-observation-report', {
|
|
38
|
+
observationId: admission.observationId, admissionDigest: sha256(canonical(admission)), result,
|
|
39
|
+
}).requestId;
|
|
40
|
+
const localAcceptance = verifyWorkflowLocalAcceptance(reported.localAcceptance, admission, result, requestId);
|
|
41
|
+
if (reported.status !== 'local-accepted' || reported.independentValidation !== 'not-performed') {
|
|
42
|
+
observationFail('Local acceptance output cannot change its distinct independent verification status');
|
|
43
|
+
}
|
|
44
|
+
return { ...summary, status: 'local-accepted', localObservationStatus: 'local-accepted', independentValidation: 'not-performed',
|
|
45
|
+
localAcceptance, result: structuredClone(result) };
|
|
30
46
|
}
|
|
31
47
|
export async function connectWorkflowObservation(repositoryRoot, original, dependencies) {
|
|
32
48
|
const location = await observationLocation(repositoryRoot, original, dependencies.home, trustedOrigin(dependencies.apiOrigin()));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { canonical } from './local-integrity.mjs';
|
|
1
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
2
2
|
import { buildInvocation, invokeRemoteResult } from './remote-client.mjs';
|
|
3
3
|
import { requestTransport, parseTransportResponse } from './remote-transport.mjs';
|
|
4
4
|
import { ideClientHeaders } from './ide-presentation.mjs';
|
|
@@ -25,7 +25,7 @@ async function recover(pending, dependencies) {
|
|
|
25
25
|
response = await requestTransport(origin, `/api/skill/status/${pending.requestId}`, { headers: {
|
|
26
26
|
authorization: `Bearer ${token}`, ...ideClientHeaders() } });
|
|
27
27
|
result = parseTransportResponse(response, 'original local observation status');
|
|
28
|
-
if (
|
|
28
|
+
if (result?.output?.requestId !== pending.requestId || !response.ok && response.status >= 500
|
|
29
29
|
|| ['pending', 'running'].includes(result.output.status)) throw unknown(pending.requestId);
|
|
30
30
|
result.feedback ??= await queryAutomaticFeedback(pending.requestId, dependencies);
|
|
31
31
|
verifyAutomaticFeedback(result.feedback, pending.requestId, pending.requestId);
|
|
@@ -38,6 +38,19 @@ function prerequisite(output) {
|
|
|
38
38
|
|| ['ENTROROVE_SCOPE_RECEIPT_EXPIRED', 'ENTROROVE_SCOPE_RUNNER_REQUIRED', 'ENTROROVE_SCOPE_SNAPSHOT_REQUIRED',
|
|
39
39
|
'ENTROROVE_SCOPE_AFTER_WRITE_REQUIRED'].includes(output.code));
|
|
40
40
|
}
|
|
41
|
+
function cachedReportPrerequisite(result, pending, state, location) {
|
|
42
|
+
const output = result?.output, proof = output?.localModePrerequisite, next = output?.nextAction;
|
|
43
|
+
return pending.operation === 'member-observation-report' && state.result
|
|
44
|
+
&& canonical(pending.input.result) === canonical(state.result) && output?.requestId === pending.requestId
|
|
45
|
+
&& output.flowId === location.identity.flowId && output.status === 'blocked' && output.recoverable === true
|
|
46
|
+
&& output.code === 'ENTROROVE_MEMBER_LOCAL_MODE_PREREQUISITE' && output.effects === 'none'
|
|
47
|
+
&& output.reason === 'cloud-sandbox-enabled' && proof?.serverEffect === 'none'
|
|
48
|
+
&& proof.originalOperation === pending.operation && proof.newSourceWritesByThisRequest === false
|
|
49
|
+
&& proof.localProcessMayRestart === false && proof.cachedLocalProcessResultRequired === true
|
|
50
|
+
&& next?.kind === 'disable-cloud-sandbox-then-resume-original-request' && next.originalOperation === pending.operation
|
|
51
|
+
&& next.reuseOriginalInput === true && next.resumeRequestId === pending.requestId && next.preserveOriginalFlow === true
|
|
52
|
+
&& result.feedback?.acknowledgement?.stored === true && result.feedback.resultDigest === sha256(canonical(output));
|
|
53
|
+
}
|
|
41
54
|
export async function observationRemote(stage, operation, input, state, location, dependencies) {
|
|
42
55
|
const requestId = buildInvocation(operation, input).requestId;
|
|
43
56
|
let response, result;
|
|
@@ -58,8 +71,10 @@ export async function observationRemote(stage, operation, input, state, location
|
|
|
58
71
|
throw unknown(requestId);
|
|
59
72
|
}
|
|
60
73
|
}
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
const statuses = ['reported', 'succeeded', ...(operation === 'member-observation-report' ? ['local-accepted'] : [])];
|
|
75
|
+
if (!response.ok || !statuses.includes(result?.output?.status)) {
|
|
76
|
+
if (operation === 'member-observation-admit' && prerequisite(result?.output)
|
|
77
|
+
|| cachedReportPrerequisite(result, state.pending, state, location)) {
|
|
63
78
|
delete state.pending; await saveObservationState(location, state);
|
|
64
79
|
throw Object.assign(new Error(result.output.message ?? 'Original local observation prerequisite is unresolved'),
|
|
65
80
|
{ code: result.output.code, output: result.output });
|
|
@@ -3,14 +3,16 @@ import { canonical, sha256 } from './local-integrity.mjs';
|
|
|
3
3
|
import { mutationId14, mutationPaths14 } from './local-mutation-files.mjs';
|
|
4
4
|
|
|
5
5
|
export const observationFail = message => { throw Object.assign(new Error(message), { code: 'ENTROROVE_LOCAL_OBSERVATION' }); };
|
|
6
|
-
export const
|
|
6
|
+
export const observationReadOnlyIdentityFields = ['taskId', 'flowId', 'stepId', 'leaseId', 'generation'];
|
|
7
|
+
export const observationIdentityFields = [...observationReadOnlyIdentityFields, 'settlementId'];
|
|
7
8
|
export function observationExact(value, fields) {
|
|
8
9
|
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
9
10
|
|| canonical(Object.keys(value).sort()) !== canonical([...fields].sort())) observationFail('Local observation fields are invalid');
|
|
10
11
|
}
|
|
11
12
|
export function observationInput(input) {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const fields = Object.hasOwn(input ?? {}, 'settlementId') ? observationIdentityFields : observationReadOnlyIdentityFields;
|
|
14
|
+
observationExact(input, fields);
|
|
15
|
+
for (const field of fields.filter(field => field !== 'generation')) mutationId14(input[field], field);
|
|
14
16
|
if (!Number.isSafeInteger(input.generation) || input.generation < 1) observationFail('Local observation generation is invalid');
|
|
15
17
|
return input;
|
|
16
18
|
}
|
|
@@ -36,7 +38,7 @@ function files(values, modes) {
|
|
|
36
38
|
&& (!Number.isSafeInteger(file.mode) || file.mode < 0 || file.mode > 511)) observationFail('Local observation file baseline is invalid');
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
function
|
|
41
|
+
export function observationAuthoritySignature(value) {
|
|
40
42
|
try {
|
|
41
43
|
const encoded = process.env.ENTROROVE_WORKFLOW_PLAN_PUBLIC_KEY, der = Buffer.from(encoded ?? '', 'base64');
|
|
42
44
|
const key = createPublicKey({ key: der, type: 'spki', format: 'der' }), receipt = value.authorityReceipt;
|
|
@@ -50,18 +52,23 @@ function signature(value) {
|
|
|
50
52
|
} catch { observationFail('Local observation requires its original pinned public cloud admission signature'); }
|
|
51
53
|
}
|
|
52
54
|
export function observationAdmission(value, input, { allowExpired = false } = {}) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const readOnly = !Object.hasOwn(input, 'settlementId');
|
|
56
|
+
const fields = readOnly ? observationReadOnlyIdentityFields : observationIdentityFields;
|
|
57
|
+
observationExact(value, [...fields, 'schemaVersion', 'purpose', 'observationId', 'ownerId', 'taskDigest',
|
|
58
|
+
'chainDigest', 'epoch', 'planId', 'planDigest', 'executionPlanSha256', 'repositoryDigest',
|
|
59
|
+
...(readOnly ? ['sourceKind'] : ['settlementDigest']), 'command',
|
|
55
60
|
'sourceManifest', 'protectedFiles', 'issuedAt', 'expiresAt', 'authorityReceipt']);
|
|
56
|
-
|
|
61
|
+
observationAuthoritySignature(value);
|
|
57
62
|
const start = Date.parse(value.issuedAt), end = Date.parse(value.expiresAt);
|
|
58
63
|
if (value.schemaVersion !== 'entrorove.local-observation-admission/1.0' || value.purpose !== 'observe-approved-command'
|
|
59
|
-
|| value.epoch !== 0 ||
|
|
64
|
+
|| value.epoch !== 0 || fields.some(field => value[field] !== input[field])
|
|
65
|
+
|| readOnly && value.sourceKind !== 'original-read-only-plan'
|
|
60
66
|
|| value.repositoryDigest !== sha256(input.repositoryRoot).slice(0, 32) || !Number.isFinite(start) || !Number.isFinite(end)
|
|
61
67
|
|| start > Date.now() || end <= start || end - start > 600_000 || !allowExpired && end <= Date.now()
|
|
62
68
|
|| !/^[a-f0-9-]{36}$/.test(value.ownerId)
|
|
63
|
-
|| ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256',
|
|
64
|
-
|
|
69
|
+
|| ['taskDigest', 'chainDigest', 'planDigest', 'executionPlanSha256', ...(readOnly ? [] : ['settlementDigest'])]
|
|
70
|
+
.some(field => !/^[a-f0-9]{64}$/.test(value[field]))) {
|
|
71
|
+
observationFail('Local observation admission is expired or lost its original task, repository or source binding');
|
|
65
72
|
}
|
|
66
73
|
mutationId14(value.observationId, 'observationId'); mutationId14(value.planId, 'planId');
|
|
67
74
|
command(value.command); files(value.sourceManifest, true); files(value.protectedFiles, false);
|