@entrorove/skill 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/AGREEMENT.md +6 -6
  2. package/CAPABILITY-MAP.md +6 -0
  3. package/CHANGELOG.md +10 -0
  4. package/SKILL.md +18 -4
  5. package/archguard-runtime-build.mjs +15 -0
  6. package/blueprint-criterion-runner.mjs +110 -0
  7. package/blueprint-criterion-wire.mjs +78 -0
  8. package/calctool-runtime-build.mjs +59 -0
  9. package/calctool-runtime-entry.mjs +92 -0
  10. package/cli.mjs +35 -83
  11. package/local-approved-plan.mjs +74 -15
  12. package/local-archguard-ast.mjs +371 -0
  13. package/local-archguard-checkpoint.mjs +123 -0
  14. package/local-archguard-components.mjs +177 -0
  15. package/local-archguard-contract.mjs +109 -0
  16. package/local-archguard-contracts.mjs +42 -0
  17. package/local-archguard-evaluate.mjs +120 -0
  18. package/local-archguard-language-ast.mjs +165 -0
  19. package/local-archguard-mutation-fence.mjs +28 -0
  20. package/local-archguard-operations.mjs +25 -0
  21. package/local-archguard-scope.mjs +64 -0
  22. package/local-archguard-signing.mjs +52 -0
  23. package/local-archguard-snapshot.mjs +126 -0
  24. package/local-archguard-state.mjs +200 -0
  25. package/local-archguard.mjs +74 -0
  26. package/local-budget-git-attributes.mjs +49 -0
  27. package/local-budget-git-loader.mjs +61 -0
  28. package/local-budget-git-native-attributes.mjs +81 -0
  29. package/local-budget-git-native-darwin.mjs +104 -0
  30. package/local-budget-git-native-linux.mjs +93 -0
  31. package/local-budget-git-native-reads.mjs +88 -0
  32. package/local-budget-git-native-request.mjs +53 -0
  33. package/local-budget-git-native-runtime.mjs +18 -0
  34. package/local-budget-git-native.c +313 -0
  35. package/local-budget-git-native.mjs +109 -0
  36. package/local-budget-git-outcome.mjs +33 -0
  37. package/local-budget-git-scratch-loader.mjs +158 -0
  38. package/local-budget-git-scratch.mjs +112 -0
  39. package/local-budget-git-system-attributes.mjs +44 -0
  40. package/local-budget-git.mjs +92 -0
  41. package/local-budget-grants.mjs +82 -0
  42. package/local-budget-handoff-completion.mjs +70 -0
  43. package/local-budget-handoff-contracts.mjs +53 -0
  44. package/local-budget-handoff.mjs +229 -0
  45. package/local-budget-lock.mjs +55 -0
  46. package/local-budget-reads.mjs +135 -0
  47. package/local-budget-renewal.mjs +86 -34
  48. package/local-budget-source-bindings.mjs +51 -0
  49. package/local-budget-store.mjs +131 -0
  50. package/local-budget-transition.mjs +46 -0
  51. package/local-budget.mjs +185 -0
  52. package/local-calctool-cli.mjs +24 -0
  53. package/local-calctool-contracts.mjs +42 -0
  54. package/local-calctool-runtime.mjs +10 -0
  55. package/local-calctool-storage.mjs +101 -0
  56. package/local-calctool.mjs +103 -0
  57. package/local-chain-bindings.mjs +93 -0
  58. package/local-chain-broker.mjs +61 -0
  59. package/local-chain-contracts.mjs +21 -0
  60. package/local-chain-controller.mjs +321 -0
  61. package/local-chain-human.mjs +48 -0
  62. package/local-chain-lock.mjs +54 -0
  63. package/local-chain-operations.mjs +7 -0
  64. package/local-chain-plan.mjs +232 -0
  65. package/local-chain-release-labels.mjs +12 -0
  66. package/local-chain-results.mjs +75 -0
  67. package/local-cli.mjs +65 -24
  68. package/local-contract-types.mjs +46 -0
  69. package/local-contracts.mjs +126 -0
  70. package/local-credential.mjs +101 -0
  71. package/local-decimal.mjs +85 -0
  72. package/local-error.mjs +23 -0
  73. package/local-evidence-verification.mjs +199 -0
  74. package/local-execution.mjs +115 -304
  75. package/local-final-gate16-bundle.mjs +21 -0
  76. package/local-final-gate16-checks.mjs +88 -0
  77. package/local-final-gate16-contract.mjs +15 -0
  78. package/local-final-gate16-plan.mjs +66 -0
  79. package/local-final-gate16-proof.mjs +75 -0
  80. package/local-final-gate16-release.mjs +17 -0
  81. package/local-final-gate16-state.mjs +70 -0
  82. package/local-final-gate16-worker.mjs +19 -0
  83. package/local-final-gate16.mjs +137 -0
  84. package/local-formula-ast.mjs +106 -0
  85. package/local-formula-engine.mjs +125 -0
  86. package/local-formula-tests.mjs +61 -0
  87. package/local-formula.mjs +25 -125
  88. package/local-integrity.mjs +22 -41
  89. package/local-invocations.mjs +142 -0
  90. package/local-ledger.mjs +15 -9
  91. package/local-merge-archguard-verification.d.mts +44 -0
  92. package/local-merge-archguard-verification.mjs +240 -0
  93. package/local-merge-archguard.mjs +138 -0
  94. package/local-merge-artifacts.mjs +86 -0
  95. package/local-merge-authority.mjs +89 -0
  96. package/local-merge-contracts.mjs +56 -0
  97. package/local-merge-controller.mjs +170 -0
  98. package/local-merge-head.mjs +56 -0
  99. package/local-merge-journal.mjs +77 -0
  100. package/local-merge-mutation.mjs +97 -0
  101. package/local-merge-rollback.mjs +126 -0
  102. package/local-merge-snapshot-rollback.mjs +26 -0
  103. package/local-merge-storage.mjs +103 -0
  104. package/local-merge-targets.mjs +47 -0
  105. package/local-merge-transaction.mjs +133 -0
  106. package/local-mutation-audit.mjs +34 -0
  107. package/local-mutation-authority.mjs +103 -0
  108. package/local-mutation-batch-receipt.mjs +84 -0
  109. package/local-mutation-batch.mjs +143 -0
  110. package/local-mutation-binding.mjs +89 -0
  111. package/local-mutation-files.mjs +270 -0
  112. package/local-mutation-pass-store.mjs +63 -0
  113. package/local-mutation-snapshot.mjs +200 -0
  114. package/local-mutation-wire.mjs +89 -0
  115. package/local-probe.mjs +133 -11
  116. package/local-scope-candidate.mjs +28 -0
  117. package/local-scope-context.mjs +28 -0
  118. package/local-scope-files.mjs +106 -0
  119. package/local-scope-git-attributes.mjs +200 -0
  120. package/local-scope-git-comparison.mjs +8 -0
  121. package/local-scope-git-history.mjs +72 -0
  122. package/local-scope-git-reads.mjs +66 -0
  123. package/local-scope-git.mjs +124 -0
  124. package/local-scope-repository.mjs +249 -0
  125. package/local-scope-signature.mjs +70 -0
  126. package/local-scope-snapshot.mjs +39 -0
  127. package/local-scope-state.mjs +204 -0
  128. package/local-scope-workspaces.mjs +83 -0
  129. package/local-swarm-authority.mjs +143 -0
  130. package/local-swarm-baseline.mjs +32 -0
  131. package/local-swarm-completion.mjs +102 -0
  132. package/local-swarm-contracts.mjs +47 -0
  133. package/local-swarm-mutation.mjs +142 -0
  134. package/local-swarm-priority.mjs +237 -0
  135. package/local-swarm-state.mjs +102 -0
  136. package/local-swarm-storage.mjs +135 -0
  137. package/local-swarm.mjs +179 -0
  138. package/local-task-handoff-completion-wire.mjs +71 -0
  139. package/local-task-handoff-contracts.mjs +66 -0
  140. package/local-task-handoff-fence.mjs +478 -0
  141. package/local-task-handoff-observation.mjs +41 -0
  142. package/local-task-handoff-overlay-wire.mjs +94 -0
  143. package/local-task-handoff-read-authority.mjs +236 -0
  144. package/local-task-handoff-storage.mjs +73 -0
  145. package/local-task-handoff-write-archive.mjs +80 -0
  146. package/local-task-handoff-write-constraints.mjs +66 -0
  147. package/local-task-handoff-write-receipts.mjs +99 -0
  148. package/local-task-handoff-write-wire.mjs +99 -0
  149. package/local-task-routing.mjs +202 -22
  150. package/manifest.json +1 -1
  151. package/package.json +164 -3
  152. package/release-state.json +164 -18
  153. package/release.mjs +242 -4
  154. package/releases.json +12 -0
  155. package/remote-client.mjs +127 -36
  156. package/remote-transport.mjs +80 -0
  157. package/review-client.mjs +38 -0
  158. package/runtime/archguard-runtime-v7.0.44.mjs +314 -0
  159. package/runtime/calctool-runtime-v7.0.43.mjs +1910 -0
  160. package/runtime/git-native-darwin-v1.mjs +5 -0
  161. package/runtime/git-native-linux-v1.mjs +5 -0
  162. package/task-message-cli.mjs +47 -0
  163. package/task-message-entry.mjs +159 -0
  164. package/workflow-local-client.mjs +35 -0
  165. package/workflow-local-contracts.mjs +58 -0
  166. package/workflow-local-request.mjs +53 -0
  167. package/workflow-local-runner.mjs +350 -0
@@ -0,0 +1,237 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { canonical, sha256 } from './local-integrity.mjs';
4
+ import { readSignedSwarmMaterial, readSwarmCloudAuthority, requireSwarmCloudMaterial } from './local-swarm-authority.mjs';
5
+ import { canonicalSwarmPaths, SWARM_PRIORITIES, swarmPathsOverlap } from './local-swarm-state.mjs';
6
+ import { readTaskHandoffFence } from './local-task-handoff-fence.mjs';
7
+ import { revalidateSwarmCompletion } from './local-swarm-completion.mjs';
8
+
9
+ const CONTROL_FIELDS = ['intentRevision', 'nextIntentSequence', 'intents', 'checkpointRequests'];
10
+ const INTENT_FIELDS = ['intentId', 'sequence', 'status', 'priority', 'flowId', 'stepId', 'workerId', 'leaseId', 'paths',
11
+ 'expiresAt', 'registeredAt', 'home', 'acceptedPlan', 'lease', 'delegationGrant', 'executionAuthority', 'executionAttemptNonce'];
12
+ const REQUEST_FIELDS = ['requestId', 'intentId', 'targetGrantDigest', 'requestedAt', 'paths', 'reason'];
13
+ const STATUSES = ['waiting', 'active', 'completed', 'expired', 'revoked'];
14
+ function fail(message, code = 'ENTROROVE_SWARM_STATE_INVALID') { throw Object.assign(new Error(message), { code }); }
15
+ function exact(value, fields) {
16
+ if (!value || typeof value !== 'object' || Array.isArray(value)
17
+ || canonical(Object.keys(value).sort()) !== canonical([...fields].sort())) fail('durable write intent fields are invalid');
18
+ }
19
+ function hash(value) { return typeof value === 'string' && /^[0-9a-f]{64}$/.test(value); }
20
+ function instant(value) { return typeof value === 'string' && Number.isFinite(Date.parse(value)); }
21
+ function advance(index, changes) {
22
+ if (!Number.isSafeInteger(index.intentRevision + 1)) fail('durable intent revision is exhausted');
23
+ return { ...index, ...changes, intentRevision: index.intentRevision + 1 };
24
+ }
25
+ function controls(value, authority) {
26
+ if (!value || value.schemaVersion !== 'entrorove.swarm-ownership/1.0' || value.repositoryRoot !== authority.root
27
+ || value.repositoryDigest !== authority.repositoryDigest || !hash(value.markerDigest) || !Array.isArray(value.records)) {
28
+ fail('write intent index lost its initialized physical ownership binding');
29
+ }
30
+ const present = CONTROL_FIELDS.filter(field => Object.hasOwn(value, field));
31
+ if (present.length !== CONTROL_FIELDS.length || !Number.isSafeInteger(value.intentRevision) || value.intentRevision < 0
32
+ || !Number.isSafeInteger(value.nextIntentSequence) || value.nextIntentSequence < 0
33
+ || !Array.isArray(value.intents) || value.intents.length > 1024 || value.nextIntentSequence !== value.intents.length
34
+ || value.intentRevision < value.nextIntentSequence || !Array.isArray(value.checkpointRequests) || value.checkpointRequests.length > 1024) {
35
+ fail('durable write intent history, counter or checkpoint queue is missing or changed; reconcile before new admission');
36
+ }
37
+ return value;
38
+ }
39
+ function material(intent, authority) {
40
+ return { repositoryRoot: authority.root, home: intent.home, flowId: intent.flowId, stepId: intent.stepId,
41
+ workerId: intent.workerId, paths: intent.paths, acceptedPlan: intent.acceptedPlan, lease: intent.lease,
42
+ delegationGrant: intent.delegationGrant, executionAuthority: intent.executionAuthority,
43
+ executionAttemptNonce: intent.executionAttemptNonce };
44
+ }
45
+ async function storedAuthority(intent, authority) {
46
+ exact(intent, INTENT_FIELDS); requireSwarmCloudMaterial(intent);
47
+ const input = material(intent, authority), verified = readSignedSwarmMaterial(input, 'report');
48
+ const { grant, lease } = verified;
49
+ if (intent.intentId !== sha256(canonical(intent.delegationGrant)) || intent.flowId !== grant.flowId
50
+ || intent.stepId !== grant.stepId || intent.workerId !== grant.workerId || intent.leaseId !== lease.leaseId
51
+ || intent.priority !== grant.priority || !STATUSES.includes(intent.status) || !instant(intent.registeredAt)
52
+ || Date.parse(intent.registeredAt) > Date.now() || !instant(intent.expiresAt)
53
+ || canonical(canonicalSwarmPaths(intent.paths)) !== canonical(grant.paths)
54
+ || grant.localRepositoryDigest !== authority.repositoryDigest || typeof intent.home !== 'string' || !path.isAbsolute(intent.home)) {
55
+ fail('stored intent changed the actual signed worker, priority, path or repository');
56
+ }
57
+ const homeStat = await fs.lstat(intent.home);
58
+ if (!homeStat.isDirectory() || homeStat.isSymbolicLink() || await fs.realpath(intent.home) !== intent.home) fail('intent execution home is not its original actual directory');
59
+ const fence = await readTaskHandoffFence({ repositoryRoot: authority.root, home: intent.home, flowId: grant.flowId });
60
+ const attempt = fence.executionAttempts.find(value => value.authority.attemptNonce === intent.executionAttemptNonce);
61
+ if (!attempt || canonical(attempt.authority) !== canonical(intent.executionAuthority) || attempt.executionHome !== intent.home
62
+ || attempt.authority.schemaVersion !== 'entrorove.task-execution-epoch-authority/1.0' || attempt.authority.status !== 'active'
63
+ || attempt.authority.taskId !== grant.taskId || attempt.authority.leaseId !== lease.leaseId || attempt.authority.generation !== lease.generation
64
+ || ['flowId', 'ownerId', 'taskDigest', 'chainDigest', 'runnerKeyId', 'localRepositoryDigest'].some(field => attempt.authority[field] !== grant[field])) {
65
+ fail('stored intent lost its actual registered signed nonce, execution epoch or original business lease');
66
+ }
67
+ const expires = Math.min(Date.parse(grant.expiresAt), Date.parse(lease.expiresAt), Date.parse(attempt.authority.expiresAt));
68
+ if (!Number.isFinite(expires) || intent.expiresAt !== new Date(expires).toISOString()
69
+ || Date.parse(intent.registeredAt) < Math.max(Date.parse(grant.issuedAt), Date.parse(attempt.authority.issuedAt))
70
+ || Date.parse(intent.registeredAt) >= expires) fail('intent entry interval differs from its original signed grant and nonce');
71
+ return { ...verified, input, fence, attempt, authority: { ...verified, root: authority.root, home: intent.home,
72
+ repositoryDigest: authority.repositoryDigest, epoch: attempt.authority.epoch, paths: grant.paths, requestedPaths: grant.paths } };
73
+ }
74
+ async function actualCompletion(verified, proof) {
75
+ if (verified.attempt.status !== 'used') return null;
76
+ try {
77
+ return await revalidateSwarmCompletion(proof, verified.authority, verified.input);
78
+ } catch { return null; }
79
+ }
80
+ function reservationFor(index, intent) {
81
+ return index.records.find(value => value.grantDigest === intent.intentId);
82
+ }
83
+ async function currentStatus(index, intent, verified, proof) {
84
+ const { fence, attempt, grant } = verified;
85
+ const record = reservationFor(index, intent);
86
+ if (fence.status !== 'active' || fence.epoch !== attempt.authority.epoch || attempt.status === 'revoked'
87
+ || fence.revokedLeaseIds.includes(grant.leaseId)) return 'revoked';
88
+ if (record) {
89
+ if (record.flowId !== intent.flowId || record.workerId !== intent.workerId || record.leaseId !== intent.leaseId
90
+ || record.home !== intent.home || canonical(record.paths) !== canonical(intent.paths)
91
+ || canonical(record.delegationGrant) !== canonical(intent.delegationGrant) || canonical(record.acceptedPlan) !== canonical(intent.acceptedPlan)
92
+ || canonical(record.lease) !== canonical(intent.lease) || canonical(record.executionAuthority) !== canonical(intent.executionAuthority)) {
93
+ fail('active ownership differs from its original exact queued cloud materials');
94
+ }
95
+ return 'active';
96
+ }
97
+ if (intent.status === 'completed' || intent.status === 'active' || attempt.status === 'used') {
98
+ if (await actualCompletion(verified, proof)) return 'completed';
99
+ fail('an admitted or used intent lost its active reservation without a real completed process', 'ENTROROVE_SWARM_RECONCILE_REQUIRED');
100
+ }
101
+ return Date.parse(intent.expiresAt) <= Date.now() ? 'expired' : 'waiting';
102
+ }
103
+ function checkpointPaths(intent, record) {
104
+ return intent.paths.filter(target => record.paths.some(scope => swarmPathsOverlap(scope, target)));
105
+ }
106
+ function checkpointId(intentId, targetGrantDigest) { return sha256(`entrorove.swarm-checkpoint/1.0\0${intentId}\0${targetGrantDigest}`); }
107
+ function validateRequests(index) {
108
+ const identities = new Set(), requests = [];
109
+ for (const request of index.checkpointRequests) {
110
+ exact(request, REQUEST_FIELDS);
111
+ const intent = index.intents.find(value => value.intentId === request.intentId);
112
+ if (!hash(request.requestId) || !hash(request.targetGrantDigest) || !intent || intent.priority !== 'urgent'
113
+ || request.reason !== 'urgent-overlap' || request.requestId !== checkpointId(intent.intentId, request.targetGrantDigest)
114
+ || identities.has(request.requestId) || !instant(request.requestedAt) || Date.parse(request.requestedAt) < Date.parse(intent.registeredAt)
115
+ || Date.parse(request.requestedAt) > Date.now() || canonical(canonicalSwarmPaths(request.paths)) !== canonical(request.paths)) {
116
+ fail('checkpoint request lacks its actual signed urgent waiting intent');
117
+ }
118
+ identities.add(request.requestId);
119
+ const target = index.records.find(value => value.grantDigest === request.targetGrantDigest);
120
+ if (!target || intent.status !== 'waiting') continue;
121
+ if (canonical(checkpointPaths(intent, target)) !== canonical(request.paths)) fail('checkpoint request changed the actual overlapping owner paths');
122
+ requests.push(request);
123
+ }
124
+ return requests;
125
+ }
126
+
127
+ // The native core owns the existing repository ownershipFile lock and publishes
128
+ // this returned index atomically with actual reservations. No caller flag grants
129
+ // authority, and an intent never revokes or clears an existing active record.
130
+ export async function validateSwarmWriteIntents(value, authority, completionProofs = new Map()) {
131
+ if (!(completionProofs instanceof Map)) fail('completion proofs must be original internal opaque tokens');
132
+ const index = controls(value, authority), identities = new Set(), intents = [];
133
+ let changed = false;
134
+ for (let sequence = 0; sequence < index.intents.length; sequence++) {
135
+ const intent = index.intents[sequence];
136
+ if (intent?.sequence !== sequence || identities.has(intent?.intentId)) fail('arrival history changed its unique durable FIFO sequence');
137
+ const verified = await storedAuthority(intent, authority), status = await currentStatus(index, intent, verified, completionProofs.get(intent.intentId));
138
+ identities.add(intent.intentId); changed ||= status !== intent.status;
139
+ intents.push(status === intent.status ? intent : { ...intent, status });
140
+ }
141
+ const checkpointRequests = validateRequests({ ...index, intents });
142
+ changed ||= canonical(checkpointRequests) !== canonical(index.checkpointRequests);
143
+ return changed ? advance(index, { intents, checkpointRequests }) : { ...index, intents, checkpointRequests };
144
+ }
145
+ async function freshCaller(authority, input, purpose) {
146
+ const fresh = await readSwarmCloudAuthority(input, purpose);
147
+ if (fresh.root !== authority.root || fresh.home !== authority.home || fresh.repositoryDigest !== authority.repositoryDigest
148
+ || fresh.epoch !== authority.epoch || canonical(fresh.grant) !== canonical(authority.grant)
149
+ || canonical(fresh.plan) !== canonical(authority.plan) || canonical(fresh.lease) !== canonical(authority.lease)
150
+ || canonical(fresh.paths) !== canonical(authority.paths)) fail('caller changed its original validated authority while acquiring the ownership lock');
151
+ if (input.priority !== undefined && input.priority !== fresh.grant.priority) fail('caller priority cannot replace the original signed priority', 'ENTROROVE_SWARM_PRIORITY_INVALID');
152
+ return fresh;
153
+ }
154
+ export async function registerSwarmWriteIntent(value, authority, input, completionProofs = new Map()) {
155
+ const fresh = await freshCaller(authority, input, 'issue'), index = await validateSwarmWriteIntents(value, fresh, completionProofs);
156
+ const intentId = sha256(canonical(input.delegationGrant)), existing = index.intents.find(intent => intent.intentId === intentId);
157
+ if (existing) {
158
+ if (canonical(existing.executionAuthority) !== canonical(input.executionAuthority) || existing.executionAttemptNonce !== input.executionAttemptNonce
159
+ || existing.home !== fresh.home) fail('durable intent cannot replace its original execution nonce or home');
160
+ return { index, intent: existing };
161
+ }
162
+ if (index.intents.length >= 1024 || !Number.isSafeInteger(index.nextIntentSequence + 1)) fail('durable intent history requires explicit bounded reconciliation');
163
+ const expires = Math.min(Date.parse(fresh.grant.expiresAt), Date.parse(fresh.lease.expiresAt), Date.parse(input.executionAuthority.expiresAt));
164
+ const intent = { intentId, sequence: index.nextIntentSequence, status: 'waiting', priority: fresh.grant.priority,
165
+ flowId: fresh.plan.flowId, stepId: fresh.step.stepId, workerId: fresh.grant.workerId, leaseId: fresh.lease.leaseId,
166
+ paths: fresh.paths, expiresAt: new Date(expires).toISOString(), registeredAt: new Date().toISOString(), home: fresh.home,
167
+ acceptedPlan: input.acceptedPlan, lease: input.lease, delegationGrant: input.delegationGrant,
168
+ executionAuthority: input.executionAuthority, executionAttemptNonce: input.executionAttemptNonce };
169
+ const next = advance(index, { nextIntentSequence: index.nextIntentSequence + 1, intents: [...index.intents, intent] });
170
+ return { index: await validateSwarmWriteIntents(next, fresh, completionProofs), intent };
171
+ }
172
+ function earlier(left, right) {
173
+ const rank = SWARM_PRIORITIES.indexOf(left.priority) - SWARM_PRIORITIES.indexOf(right.priority);
174
+ return rank > 0 || (rank === 0 && left.sequence < right.sequence);
175
+ }
176
+ export async function claimSwarmWriteIntent(value, authority, input, completionProofs = new Map()) {
177
+ const fresh = await freshCaller(authority, input, 'issue'), index = await validateSwarmWriteIntents(value, fresh, completionProofs);
178
+ const intentId = sha256(canonical(input.delegationGrant)), intent = index.intents.find(value => value.intentId === intentId);
179
+ if (!intent || canonical(intent.executionAuthority) !== canonical(input.executionAuthority)
180
+ || intent.executionAttemptNonce !== input.executionAttemptNonce || intent.home !== fresh.home) {
181
+ fail('the exact original cloud grant and registered nonce must enter the durable queue first', 'ENTROROVE_SWARM_CLOUD_AUTHORITY_REQUIRED');
182
+ }
183
+ if (intent.status !== 'waiting') fail('only a live pending original intent may acquire new paths', 'ENTROROVE_SWARM_AUTHORITY_EXPIRED');
184
+ const active = index.records.filter(record => checkpointPaths(intent, record).length);
185
+ const ahead = index.intents.filter(other => other.status === 'waiting' && other.intentId !== intentId && earlier(other, intent)
186
+ && other.paths.some(scope => intent.paths.some(target => swarmPathsOverlap(scope, target))));
187
+ const blockedPaths = intent.paths.filter(target => active.some(record => record.paths.some(scope => swarmPathsOverlap(scope, target)))
188
+ || ahead.some(other => other.paths.some(scope => swarmPathsOverlap(scope, target))));
189
+ const checkpointRequests = [...index.checkpointRequests];
190
+ if (intent.priority === 'urgent') for (const target of active) {
191
+ const requestId = checkpointId(intentId, target.grantDigest);
192
+ if (!checkpointRequests.some(value => value.requestId === requestId)) checkpointRequests.push({ requestId, intentId,
193
+ targetGrantDigest: target.grantDigest, requestedAt: new Date().toISOString(), paths: checkpointPaths(intent, target), reason: 'urgent-overlap' });
194
+ }
195
+ if (checkpointRequests.length > 1024) fail('durable checkpoint requests exceed the bounded ownership queue');
196
+ const updated = checkpointRequests.length === index.checkpointRequests.length ? index : advance(index, { checkpointRequests });
197
+ return { index: updated, intentId, readyPaths: intent.paths.filter(target => !blockedPaths.includes(target)), blockedPaths,
198
+ safeCheckpointRequested: intent.priority === 'urgent' && active.length > 0, mayAcquire: blockedPaths.length === 0,
199
+ blockedBy: [...active.map(value => ({ kind: 'active', grantDigest: value.grantDigest, reconcileRequired: value.reconcileRequired === true })),
200
+ ...ahead.map(value => ({ kind: 'priority', intentId: value.intentId, priority: value.priority, sequence: value.sequence }))] };
201
+ }
202
+ export async function readSwarmWriteIntentHistory(value, authority) {
203
+ const index = controls(value, authority), verified = new Map();
204
+ for (let sequence = 0; sequence < index.intents.length; sequence++) {
205
+ const intent = index.intents[sequence];
206
+ if (intent?.sequence !== sequence || verified.has(intent?.intentId)) fail('readonly checkpoint history lost its durable FIFO identities');
207
+ verified.set(intent.intentId, await storedAuthority(intent, authority));
208
+ }
209
+ return { index, verified, requests: validateRequests(index) };
210
+ }
211
+ export async function readSwarmCheckpointRequests(value, authority, input) {
212
+ const fresh = await freshCaller(authority, input, 'verify');
213
+ const { index, verified, requests: checkedRequests } = await readSwarmWriteIntentHistory(value, fresh);
214
+ const requests = checkedRequests.filter(request => {
215
+ const intent = index.intents.find(value => value.intentId === request.intentId), original = verified.get(request.intentId);
216
+ return original.fence.status === 'active' && original.fence.epoch === original.attempt.authority.epoch
217
+ && original.attempt.status === 'ready' && !original.fence.revokedLeaseIds.includes(intent.leaseId)
218
+ && Date.parse(intent.expiresAt) > Date.now();
219
+ });
220
+ const targetGrantDigest = sha256(canonical(input.delegationGrant));
221
+ return requests.filter(value => value.targetGrantDigest === targetGrantDigest)
222
+ .map(value => ({ requestId: value.requestId, requestedAt: value.requestedAt, paths: value.paths,
223
+ reason: value.reason, safeCheckpointRequested: true }));
224
+ }
225
+ export async function closeSwarmWriteIntent(value, authority, input, completionProof, completionProofs = new Map()) {
226
+ const fresh = await freshCaller(authority, input, 'report'), intentId = sha256(canonical(input.delegationGrant));
227
+ if (!(completionProofs instanceof Map)) fail('completion proofs must be original internal opaque tokens');
228
+ const proofs = new Map(completionProofs); proofs.set(intentId, completionProof);
229
+ const index = await validateSwarmWriteIntents(value, fresh, proofs), intent = index.intents.find(value => value.intentId === intentId);
230
+ if (!intent || canonical(intent.executionAuthority) !== canonical(input.executionAuthority) || intent.executionAttemptNonce !== input.executionAttemptNonce) {
231
+ fail('completion cannot close another original signed queued intent');
232
+ }
233
+ if (!await actualCompletion(await storedAuthority(intent, fresh), completionProof)) fail('completion has no actual signed finished business process', 'ENTROROVE_SWARM_EXECUTION_UNKNOWN');
234
+ if (intent.status === 'completed') return index;
235
+ return advance(index, { intents: index.intents.map(value => value.intentId === intentId ? { ...value, status: 'completed' } : value),
236
+ checkpointRequests: index.checkpointRequests.filter(value => value.intentId !== intentId && value.targetGrantDigest !== intentId) });
237
+ }
@@ -0,0 +1,102 @@
1
+ import path from 'node:path';
2
+ import { canonical } from './local-integrity.mjs';
3
+
4
+ export const SWARM_PRIORITIES = Object.freeze(['background', 'normal', 'high', 'urgent']);
5
+ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
6
+ const STATUSES = ['active', 'reported', 'failed', 'blocked', 'revoked'];
7
+ function fail(code, message) { throw Object.assign(new Error(message), { code }); }
8
+ function identifier(value) { return typeof value === 'string' && ID.test(value); }
9
+ export function canonicalSwarmPaths(values) {
10
+ if (!Array.isArray(values) || !values.length || values.length > 512) fail('ENTROROVE_SWARM_PATH_INVALID', 'swarm paths must be nonempty and bounded');
11
+ for (const value of values) {
12
+ if (typeof value !== 'string' || !value || value.length > 500 || value !== value.normalize('NFC') || path.isAbsolute(value)
13
+ || /^[A-Za-z]:/.test(value) || /[\\\u0000-\u001f\u007f*?\[\]{}]/.test(value)
14
+ || value.split('/').some(part => !part || part === '.' || part === '..')) fail('ENTROROVE_SWARM_PATH_INVALID', 'swarm path is unsafe');
15
+ }
16
+ if (new Set(values).size !== values.length) fail('ENTROROVE_SWARM_PATH_INVALID', 'swarm paths must be unique');
17
+ return [...values].sort();
18
+ }
19
+ export function swarmPathsOverlap(left, right) {
20
+ return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
21
+ }
22
+ function validWorker(worker, flowId) {
23
+ if (!worker || !identifier(worker.workerId) || !identifier(worker.leaseId) || worker.flowId !== flowId
24
+ || !Number.isSafeInteger(worker.handoffEpoch) || worker.handoffEpoch < 0 || !STATUSES.includes(worker.status)
25
+ || !SWARM_PRIORITIES.includes(worker.priority)) return false;
26
+ const created = Date.parse(worker.createdAt), expires = Date.parse(worker.expiresAt);
27
+ if (typeof worker.createdAt !== 'string' || typeof worker.expiresAt !== 'string'
28
+ || !Number.isFinite(created) || !Number.isFinite(expires) || expires <= created) return false;
29
+ try { return canonical(canonicalSwarmPaths(worker.paths)) === canonical(worker.paths); } catch { return false; }
30
+ }
31
+ export function validateSwarmState(value, expected) {
32
+ if (!value || value.schemaVersion !== 'entrorove.coordination/1.0' || !identifier(value.flowId)
33
+ || !expected || value.flowId !== expected.flowId || value.repositoryDigest !== expected.repositoryDigest
34
+ || !/^[0-9a-f]{32}$/.test(value.repositoryDigest ?? '') || !Array.isArray(value.workers)
35
+ || !Array.isArray(value.leases) || !Array.isArray(value.events) || value.workers.length > 50
36
+ || !Number.isSafeInteger(value.maxWorkers) || value.maxWorkers < 0 || value.maxWorkers > 50
37
+ || (value.priority !== undefined && !SWARM_PRIORITIES.includes(value.priority))) {
38
+ fail('ENTROROVE_SWARM_STATE_INVALID', 'coordination state lost its actual repository/flow binding');
39
+ }
40
+ const workers = new Set(), leases = new Set();
41
+ for (const worker of value.workers) {
42
+ if (!validWorker(worker, value.flowId) || workers.has(worker.workerId) || leases.has(worker.leaseId)) {
43
+ fail('ENTROROVE_SWARM_STATE_INVALID', 'coordination worker identities, paths or lifetime are invalid');
44
+ }
45
+ workers.add(worker.workerId); leases.add(worker.leaseId);
46
+ }
47
+ if (value.leases.length !== value.workers.length || value.leases.some((lease, index) => canonical(lease) !== canonical(value.workers[index]))) {
48
+ fail('ENTROROVE_SWARM_STATE_INVALID', 'coordination lease table drifted from its exact worker records');
49
+ }
50
+ return value;
51
+ }
52
+ function checked(state) { return validateSwarmState(state, { flowId: state?.flowId, repositoryDigest: state?.repositoryDigest }); }
53
+ export function appendSwarmWorker(state, worker) {
54
+ checked(state);
55
+ if (state.workers.some(item => item.workerId === worker?.workerId)) fail('ENTROROVE_SWARM_WORKER_DUPLICATE', 'workerId is already bound and cannot be reused');
56
+ if (state.leases.some(item => item.leaseId === worker?.leaseId)) fail('ENTROROVE_SWARM_LEASE_INVALID', 'leaseId is already bound');
57
+ if (!validWorker(worker, state.flowId) || worker.status !== 'active') fail('ENTROROVE_SWARM_STATE_INVALID', 'new worker record is invalid');
58
+ const active = state.workers.filter(item => item.status === 'active');
59
+ if (active.length >= state.maxWorkers) fail('ENTROROVE_SWARM_WORKER_LIMIT', 'the registered worker limit is exhausted');
60
+ if (active.some(item => item.paths.some(scope => worker.paths.some(target => swarmPathsOverlap(scope, target))))) {
61
+ fail('ENTROROVE_SWARM_PATH_CONFLICT', 'an active worker already owns an overlapping path');
62
+ }
63
+ return { ...state, workers: [...state.workers, { ...worker }], leases: [...state.leases, { ...worker }] };
64
+ }
65
+ export function reportSwarmWorker(state, input, actualCompletionConfirmed = false) {
66
+ checked(state);
67
+ const worker = state.workers.find(item => item.workerId === input.workerId && item.leaseId === input.leaseId && item.status === 'active');
68
+ if (!worker || (!actualCompletionConfirmed && Date.parse(worker.expiresAt) <= Date.now())) fail('ENTROROVE_SWARM_LEASE_INVALID', 'the exact active worker lease is missing or expired');
69
+ const outcome = input.outcome ?? 'completed';
70
+ if (!['completed', 'failed', 'blocked'].includes(outcome)) fail('ENTROROVE_SWARM_REPORT_INVALID', 'worker report outcome is invalid');
71
+ const status = outcome === 'completed' ? 'reported' : outcome;
72
+ const updated = { ...worker, status, outcome, reportedAt: new Date().toISOString() };
73
+ const replace = item => item.workerId === worker.workerId && item.leaseId === worker.leaseId ? updated : item;
74
+ return { state: { ...state, workers: state.workers.map(replace), leases: state.leases.map(replace) },
75
+ result: { status, trafficLight: status === 'reported' ? 'yellow' : 'red', workerId: worker.workerId, leaseId: worker.leaseId } };
76
+ }
77
+
78
+ // This is a pure queue decision, never a file-lock grant. The production caller
79
+ // must establish occupiedPaths from actual signed locks and recheck on acquire.
80
+ export function selectSwarmWriteIntent(intents, overlapPaths, occupiedPaths = []) {
81
+ const paths = canonicalSwarmPaths(overlapPaths);
82
+ const occupied = occupiedPaths.length ? canonicalSwarmPaths(occupiedPaths) : [];
83
+ if (!Array.isArray(intents) || intents.length > 1024) fail('ENTROROVE_SWARM_STATE_INVALID', 'write-intent queue is invalid');
84
+ const ids = new Set(), sequences = new Set();
85
+ for (const intent of intents) {
86
+ if (!SWARM_PRIORITIES.includes(intent?.priority)) fail('ENTROROVE_SWARM_PRIORITY_INVALID', 'priority must be background, normal, high or urgent');
87
+ if (!identifier(intent.intentId) || ids.has(intent.intentId) || !Number.isSafeInteger(intent.sequence) || intent.sequence < 0
88
+ || sequences.has(intent.sequence) || !['waiting', 'ready', 'completed', 'expired'].includes(intent.status)) {
89
+ fail('ENTROROVE_SWARM_STATE_INVALID', 'write-intent identity or durable queue order is invalid');
90
+ }
91
+ canonicalSwarmPaths(intent.paths); ids.add(intent.intentId); sequences.add(intent.sequence);
92
+ }
93
+ const candidates = intents.filter(intent => ['waiting', 'ready'].includes(intent.status)
94
+ && intent.paths.some(target => paths.some(scope => swarmPathsOverlap(target, scope))));
95
+ candidates.sort((left, right) => SWARM_PRIORITIES.indexOf(right.priority) - SWARM_PRIORITIES.indexOf(left.priority) || left.sequence - right.sequence);
96
+ const selected = candidates[0];
97
+ if (!selected) return { intentId: null, readyPaths: [], blockedPaths: [], safeCheckpointRequested: false };
98
+ const selectedPaths = canonicalSwarmPaths(selected.paths.filter(target => paths.some(scope => swarmPathsOverlap(target, scope))));
99
+ const blockedPaths = selectedPaths.filter(target => occupied.some(scope => swarmPathsOverlap(target, scope)));
100
+ return { intentId: selected.intentId, readyPaths: selectedPaths.filter(target => !blockedPaths.includes(target)), blockedPaths,
101
+ safeCheckpointRequested: selected.priority === 'urgent' && blockedPaths.length > 0, freshAimlockSnapshotRequired: true };
102
+ }
@@ -0,0 +1,135 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { constants, promises as fs } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { canonical, sha256 } from './local-integrity.mjs';
6
+ import { readSignedSwarmMaterial, verifyStoredSwarmGrant } from './local-swarm-authority.mjs';
7
+ import { readTaskHandoffFence } from './local-task-handoff-fence.mjs';
8
+ import { revalidateSwarmCompletion, swarmOwnershipCompletionInput } from './local-swarm-completion.mjs';
9
+
10
+ function fail(message) { throw Object.assign(new Error(message), { code: 'ENTROROVE_SWARM_STATE_INVALID' }); }
11
+ async function hierarchy(home, parts, mode) {
12
+ if (typeof home !== 'string' || !path.isAbsolute(home)) fail('coordination home must be absolute');
13
+ const stat = await fs.lstat(home), actual = await fs.realpath(home);
14
+ if (!stat.isDirectory() || stat.isSymbolicLink() || actual !== path.resolve(home)
15
+ || (typeof process.getuid === 'function' && stat.uid !== process.getuid())) fail('coordination home must be actual and owned');
16
+ let directory = actual;
17
+ for (const part of parts) {
18
+ directory = path.join(directory, part);
19
+ if (mode === 'write') { try { await fs.mkdir(directory, { mode: 0o700 }); } catch (error) { if (error.code !== 'EEXIST') throw error; } }
20
+ const value = await fs.lstat(directory);
21
+ if (!value.isDirectory() || value.isSymbolicLink() || (process.platform !== 'win32' && (value.mode & 0o077))
22
+ || (typeof process.getuid === 'function' && value.uid !== process.getuid())) fail('coordination state hierarchy must be private, real and owned');
23
+ }
24
+ return directory;
25
+ }
26
+ export async function swarmRepositoryStorage(authority, mode = 'write') {
27
+ if (!['read', 'write'].includes(mode)) fail('coordination storage mode is invalid');
28
+ const sharedHome = process.env.ENTROROVE_SWARM_COORDINATION_HOME ?? os.homedir();
29
+ const shared = await hierarchy(sharedHome, ['.entrorove', 'coordination', authority.repositoryDigest], mode);
30
+ return { ownershipFile: path.join(shared, 'ownership.json'), markerFile: path.join(shared, 'ownership.initialized.json') };
31
+ }
32
+ export async function swarmFlowStorage(authority, mode = 'write') {
33
+ if (!['read', 'write'].includes(mode)) fail('coordination storage mode is invalid');
34
+ const local = await hierarchy(authority.home, ['.entrorove', 'coordination', authority.repositoryDigest, authority.plan.flowId], mode);
35
+ return { stateFile: path.join(local, 'state.json') };
36
+ }
37
+ export async function swarmStorage(authority, mode = 'write') {
38
+ return { ...await swarmRepositoryStorage(authority, mode), ...await swarmFlowStorage(authority, mode) };
39
+ }
40
+ export async function readSwarmJson(file, fallback) {
41
+ let before;
42
+ try { before = await fs.lstat(file); } catch (error) { if (error.code === 'ENOENT') return fallback; throw error; }
43
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 8 * 1024 * 1024
44
+ || (process.platform !== 'win32' && (before.mode & 0o077))
45
+ || (typeof process.getuid === 'function' && before.uid !== process.getuid())) fail('coordination state file must be bounded, private, real and owned');
46
+ const handle = await fs.open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
47
+ try {
48
+ const opened = await handle.stat();
49
+ if (opened.dev !== before.dev || opened.ino !== before.ino) fail('coordination state changed before open');
50
+ const bytes = await handle.readFile(), after = await handle.stat();
51
+ if (after.size !== opened.size || after.mtimeMs !== opened.mtimeMs || bytes.length > 8 * 1024 * 1024) fail('coordination state changed while reading');
52
+ return JSON.parse(bytes.toString('utf8'));
53
+ } finally { await handle.close(); }
54
+ }
55
+ export async function writeSwarmJson(file, value) {
56
+ const temporary = `${file}.${randomUUID()}.tmp`, handle = await fs.open(temporary, 'wx', 0o600);
57
+ try { await handle.writeFile(`${canonical(value)}\n`); await handle.sync(); } finally { await handle.close(); }
58
+ try { await fs.rename(temporary, file); } finally { await fs.rm(temporary, { force: true }); }
59
+ const directory = await fs.open(path.dirname(file), 'r'); try { await directory.sync(); } finally { await directory.close(); }
60
+ }
61
+ export function newSwarmOwnership(authority) {
62
+ return { schemaVersion: 'entrorove.swarm-ownership/1.0', repositoryDigest: authority.repositoryDigest,
63
+ repositoryRoot: authority.root, records: [], intentRevision: 0, nextIntentSequence: 0, intents: [], checkpointRequests: [] };
64
+ }
65
+ export async function readSwarmOwnership(storage, authority, mode = 'write') {
66
+ if (!['read', 'write'].includes(mode)) fail('coordination ownership mode is invalid');
67
+ const marker = await readSwarmJson(storage.markerFile, null), value = await readSwarmJson(storage.ownershipFile, null);
68
+ if (!marker && !value) {
69
+ if (mode === 'read') fail('the original repository ownership history has not been initialized');
70
+ const initialized = { schemaVersion: 'entrorove.swarm-ownership-initialization/1.0', initializationId: randomUUID(),
71
+ repositoryRoot: authority.root, repositoryDigest: authority.repositoryDigest, initializedAt: new Date().toISOString() };
72
+ const index = { ...newSwarmOwnership(authority), markerDigest: sha256(canonical(initialized)) };
73
+ // An interrupted first publication leaves a marker without an index and
74
+ // requires explicit recovery; it can never silently recreate an empty map.
75
+ await writeSwarmJson(storage.markerFile, initialized); await writeSwarmJson(storage.ownershipFile, index);
76
+ return index;
77
+ }
78
+ if (!marker || !value || marker.schemaVersion !== 'entrorove.swarm-ownership-initialization/1.0'
79
+ || marker.repositoryRoot !== authority.root || marker.repositoryDigest !== authority.repositoryDigest
80
+ || !/^[0-9a-f-]{36}$/.test(marker.initializationId ?? '') || !Number.isFinite(Date.parse(marker.initializedAt))
81
+ || value.markerDigest !== sha256(canonical(marker))) fail('initialized repository ownership history is missing or changed; reconcile before new grants');
82
+ return value;
83
+ }
84
+ export async function validateSwarmOwnership(value, authority, completionProofs = new Map()) {
85
+ if (!value || value.schemaVersion !== 'entrorove.swarm-ownership/1.0' || value.repositoryDigest !== authority.repositoryDigest
86
+ || value.repositoryRoot !== authority.root || !Array.isArray(value.records) || value.records.length > 1024) fail('repository ownership index lost its physical repository binding');
87
+ const identities = new Set(), records = [];
88
+ for (const record of value.records) {
89
+ const grant = verifyStoredSwarmGrant(record?.delegationGrant, true), digest = sha256(canonical(record.delegationGrant));
90
+ const identity = `${grant.flowId}\0${grant.workerId}\0${grant.leaseId}`;
91
+ if (record.grantDigest !== digest || record.flowId !== grant.flowId || record.workerId !== grant.workerId
92
+ || record.leaseId !== grant.leaseId || record.expiresAt !== grant.expiresAt || canonical(record.paths) !== canonical(grant.paths)
93
+ || grant.localRepositoryDigest !== authority.repositoryDigest || identities.has(identity)
94
+ || typeof record.home !== 'string' || !path.isAbsolute(record.home)) fail('ownership record changed its actual signed cloud grant');
95
+ identities.add(identity);
96
+ const material = readSignedSwarmMaterial({ flowId: grant.flowId, stepId: grant.stepId, workerId: grant.workerId,
97
+ acceptedPlan: record.acceptedPlan, lease: record.lease, delegationGrant: record.delegationGrant }, 'report');
98
+ const fence = await readTaskHandoffFence({ repositoryRoot: authority.root, home: record.home, flowId: grant.flowId });
99
+ const attempt = fence.executionAttempts.find(item => canonical(item.authority) === canonical(record.executionAuthority));
100
+ if (!attempt || attempt.executionHome !== record.home || attempt.authority.taskId !== grant.taskId
101
+ || attempt.authority.leaseId !== grant.leaseId || attempt.authority.generation !== grant.generation
102
+ || attempt.authority.flowId !== grant.flowId || attempt.authority.runnerKeyId !== grant.runnerKeyId) fail('ownership record lost its original actual registered execution authority');
103
+ if (fence.status !== 'active' || fence.epoch !== attempt.authority.epoch || attempt.status === 'revoked'
104
+ || fence.revokedLeaseIds.includes(grant.leaseId)) continue;
105
+ let retained = record;
106
+ if (Date.parse(grant.expiresAt) <= Date.now()) {
107
+ const active = fence.activities.some(value => value.leaseId === grant.leaseId);
108
+ if (!active && !(attempt.status === 'ready' && Date.parse(attempt.authority.expiresAt) > Date.now())) {
109
+ try {
110
+ const completed = await revalidateSwarmCompletion(completionProofs.get(record.grantDigest),
111
+ { ...material, root: authority.root, home: record.home, repositoryDigest: authority.repositoryDigest, epoch: fence.epoch },
112
+ swarmOwnershipCompletionInput(record, authority.root));
113
+ const started = Date.parse(completed?.process?.startedAt), finished = Date.parse(completed?.process?.finishedAt);
114
+ if (completed && ['reported', 'failed'].includes(completed.status) && completed.receipt
115
+ && Number.isInteger(completed.process?.exitCode) && Number.isFinite(started) && Number.isFinite(finished)
116
+ && started >= Date.parse(record.lease.issuedAt) && started >= Date.parse(grant.issuedAt)
117
+ && started < Date.parse(grant.expiresAt) && finished >= started && finished <= Date.parse(record.lease.expiresAt)) continue;
118
+ retained = { ...record, reconcileRequired: true };
119
+ } catch {
120
+ // Unknown/unfinished actual outcome retains its original signed path
121
+ // reservation. TTL never preempts a process or clears unknown work.
122
+ retained = { ...record, reconcileRequired: true };
123
+ }
124
+ }
125
+ }
126
+ records.push(retained);
127
+ }
128
+ return { ...value, records };
129
+ }
130
+ export function swarmOwnershipRecord(authority, input) {
131
+ return { grantDigest: sha256(canonical(input.delegationGrant)), delegationGrant: input.delegationGrant,
132
+ acceptedPlan: input.acceptedPlan, lease: input.lease, executionAuthority: input.executionAuthority,
133
+ home: authority.home, flowId: authority.plan.flowId,
134
+ workerId: authority.grant.workerId, leaseId: authority.lease.leaseId, paths: authority.paths, expiresAt: authority.grant.expiresAt };
135
+ }