@kungfu-tech/buildchain 4.0.2-alpha.9 → 4.0.2

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 (157) hide show
  1. package/AGENTS.md +10 -1
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/actions/v4-release-candidate-promote/README.md +28 -0
  6. package/architecture/agent-change-map.md +64 -7
  7. package/architecture/ci-lane-change-budget.json +366 -0
  8. package/architecture/internal-capabilities.json +53 -8
  9. package/architecture/maintainability-debt.json +147 -93
  10. package/architecture/maintainability-policy.json +32 -28
  11. package/architecture/release-tail-contract-inventory.json +6 -6
  12. package/architecture/v4-architecture-constitution.md +23 -20
  13. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  14. package/architecture/v4-delivery-authority-parity.json +2 -2
  15. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  16. package/architecture/v4-floating-consumer-policy.json +2 -2
  17. package/architecture/v4-next-development-parity.json +5 -5
  18. package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
  19. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  20. package/architecture/v4-provider-operation-journal-contract.json +5 -2
  21. package/architecture/v4-publication-facade-patches.json +64 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +74 -39
  24. package/architecture/v4-rust-wasm-production-authority.json +58 -0
  25. package/architecture/v4-stable-publication-fence.json +5 -2
  26. package/architecture/v4-stage-capsule-qualification.json +2 -2
  27. package/architecture/v4-tail-reseal-parity.json +8 -6
  28. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  29. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  30. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  31. package/architecture/workflow-taxonomy.json +1451 -0
  32. package/bin/buildchain.mjs +7 -7
  33. package/contracts/buildchain-v2-residuals-v1.json +57 -48
  34. package/contracts/dev-delivery-authority-v2.schema.json +16 -2
  35. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  36. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  37. package/dist/site/buildchain-contract.json +93 -48
  38. package/dist/site/buildchain-site.json +222 -69
  39. package/dist/site/capability-registry.json +4 -4
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2585 -1000
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +13 -13
  44. package/dist/site/node-api-registry.json +120 -115
  45. package/dist/site/page-registry.json +204 -51
  46. package/dist/site/public-surface-audit.json +3205 -1219
  47. package/dist/site/publication-authority-registry.json +1124 -362
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  50. package/dist/site/site-manifest.json +17 -17
  51. package/dist/site/workflow-registry.json +5061 -1659
  52. package/docs/MAP.md +1 -0
  53. package/docs/auditable-demo.md +1 -1
  54. package/docs/cli.md +21 -8
  55. package/docs/dev-delivery-warrant.md +1 -1
  56. package/docs/engineering-housekeeper.md +3 -3
  57. package/docs/kfd-support.md +26 -2
  58. package/docs/next-development-transition.md +2 -2
  59. package/docs/node-api-reference.md +190 -190
  60. package/docs/publication-authority.md +2 -2
  61. package/docs/release-candidate.md +3 -3
  62. package/docs/release-flow.md +67 -19
  63. package/docs/release-governance.md +8 -8
  64. package/docs/release-passport.md +27 -2
  65. package/docs/reusable-build-surface.md +16 -0
  66. package/docs/v4-adopter-delivery.md +2 -2
  67. package/docs/v4-production-release.md +71 -8
  68. package/docs/v4-rust-wasm-production-authority.md +58 -0
  69. package/docs/v4-stage-capsule.md +2 -2
  70. package/docs/v4-tail-reseal.md +1 -1
  71. package/docs/workflow-catalog.md +154 -0
  72. package/docs/workflow-path-migration.md +109 -0
  73. package/package.json +10 -6
  74. package/packages/core/artifact-verification-envelope.js +5 -5
  75. package/packages/core/buildchain-contract.js +1 -1
  76. package/packages/core/buildchain-kfd-claims.js +2 -2
  77. package/packages/core/buildchain-publication-authority.js +42 -26
  78. package/packages/core/buildchain-v4-domain.wasm +0 -0
  79. package/packages/core/ci-lane-change-budget.js +14 -1
  80. package/packages/core/controller-evidence.js +4 -1
  81. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  82. package/packages/core/dev-delivery-warrant-state.js +3 -4
  83. package/packages/core/dev-delivery-warrant.js +12 -2
  84. package/packages/core/github-governance-authority.js +4 -4
  85. package/packages/core/kfd-adopter-manifest.js +46 -47
  86. package/packages/core/next-development-projection.js +2 -2
  87. package/packages/core/paper-agent-entry.js +30 -4
  88. package/packages/core/paper-fleet.js +1 -1
  89. package/packages/core/paper-runtime-channels.js +348 -0
  90. package/packages/core/paper-scaffold-content.js +10 -0
  91. package/packages/core/paper.js +37 -71
  92. package/packages/core/release-line-dry-run.js +1 -1
  93. package/packages/core/release-passport-contract.js +5 -5
  94. package/packages/core/release-passport.js +5 -4
  95. package/packages/core/release-tail-provider-plane.js +68 -1048
  96. package/packages/core/v4-canonical-contracts.js +10 -75
  97. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  98. package/packages/core/v4-domain-wasm.js +216 -0
  99. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  100. package/packages/core/v4-floating-consumer-policy.js +1 -1
  101. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  102. package/packages/core/v4-product-publication.js +19 -349
  103. package/packages/core/v4-protected-publication-source.js +19 -21
  104. package/packages/core/v4-provider-operation-journal.js +18 -487
  105. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  106. package/packages/core/v4-release-activation-shadow.js +13 -500
  107. package/packages/core/v4-release-invocation.js +28 -389
  108. package/packages/core/v4-stable-publication-fence.js +13 -342
  109. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  110. package/scripts/binary-publication-evidence.mjs +135 -0
  111. package/scripts/build-contract-core.mjs +1 -14
  112. package/scripts/build-v4-domain-wasm.mjs +189 -0
  113. package/scripts/check-action-bundles.mjs +12 -2
  114. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  115. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  116. package/scripts/check-inventory.mjs +35 -44
  117. package/scripts/check-maintainability.mjs +16 -113
  118. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  119. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  120. package/scripts/check-v4-release-topology.mjs +90 -33
  121. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  122. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  123. package/scripts/check-workflow-taxonomy.mjs +6 -0
  124. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  125. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  126. package/scripts/dev-delivery-authority.mjs +8 -8
  127. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  128. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  129. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  130. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  131. package/scripts/dev-delivery-warrant.mjs +76 -99
  132. package/scripts/dev-pr-auto-merge.mjs +15 -15
  133. package/scripts/generate-channel-build-workflow.mjs +7 -2
  134. package/scripts/generate-channel-promotion-workflow.mjs +7 -5
  135. package/scripts/generate-site-bundle.mjs +11 -11
  136. package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
  137. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  138. package/scripts/github-output.mjs +31 -0
  139. package/scripts/maintainability-metrics.mjs +16 -5
  140. package/scripts/maintainability-public-surface.mjs +115 -0
  141. package/scripts/next-development-review.mjs +307 -0
  142. package/scripts/paper-work-fleet-cli.mjs +2 -0
  143. package/scripts/release-asset-client.mjs +91 -0
  144. package/scripts/release-candidate-resolver.mjs +9 -9
  145. package/scripts/source-verification-evidence.mjs +344 -0
  146. package/scripts/stable-candidate-qualification.mjs +1 -1
  147. package/scripts/universal-facade-maintainability.mjs +82 -0
  148. package/scripts/v4-product-publication-intent.mjs +13 -6
  149. package/scripts/v4-publication-settlement.mjs +211 -0
  150. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  151. package/scripts/v4-universal-workflow-engine.mjs +618 -0
  152. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  153. package/scripts/verify-version-state-delta.mjs +173 -0
  154. package/scripts/workflow-taxonomy.mjs +507 -0
  155. package/templates/native-dev-delivery.yml +4 -7
  156. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  157. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -1,7 +1,9 @@
1
- import crypto from "node:crypto";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
3
+
4
+ import { invokeV4DomainWasm } from "./v4-domain-wasm.js";
4
5
  import { RELEASE_TAIL_PRODUCT_CAPABILITIES } from "./release-tail-product-capabilities.js";
6
+
5
7
  export const RELEASE_TAIL_DECLARATION_CONTRACT =
6
8
  "kungfu-buildchain-release-tail-capabilities";
7
9
  export const RELEASE_TAIL_TRANSACTION_POLICY = "buildchain.release-tail/v1";
@@ -27,6 +29,7 @@ export const RELEASE_TAIL_STATES = Object.freeze([
27
29
  "repair-required",
28
30
  "terminal-failure",
29
31
  ]);
32
+
30
33
  export const RELEASE_TAIL_CAPABILITY_REGISTRY = Object.freeze([
31
34
  ...RELEASE_TAIL_PRODUCT_CAPABILITIES,
32
35
  Object.freeze({
@@ -67,425 +70,13 @@ export const RELEASE_TAIL_CAPABILITY_REGISTRY = Object.freeze([
67
70
  }),
68
71
  ]);
69
72
 
70
- const CAPABILITY_BY_ID = new Map(
71
- RELEASE_TAIL_CAPABILITY_REGISTRY.map((entry) => [entry.id, entry]),
72
- );
73
- const ROOT = /^sha256:[0-9a-f]{64}$/u;
74
- const SHA = /^[0-9a-f]{40}$/u;
75
- const FORBIDDEN_KEYS = new Set([
76
- "cmd",
77
- "command",
78
- "eval",
79
- "executable",
80
- "javascript",
81
- "run",
82
- "script",
83
- "shell",
84
- ]);
85
- const TOP_LEVEL_FIELDS = [
86
- "contract",
87
- "schemaVersion",
88
- "transactionPolicy",
89
- "subject",
90
- "capabilities",
91
- ];
92
- const SUBJECT_FIELDS = ["repository", "sourceSha", "version", "tag", "channel"];
93
- const CAPABILITY_FIELDS = [
94
- "id",
95
- "executor",
96
- "adapter",
97
- "artifactRoles",
98
- "destination",
99
- "channelPolicy",
100
- "activationPolicy",
101
- "readbackPredicates",
102
- "effect",
103
- "observation",
104
- "receipt",
105
- "operationIdentity",
106
- "idempotency",
107
- "retry",
108
- "evidenceRequirements",
109
- ];
110
- const EFFECT_PLAN_FIELDS = [
111
- "schema",
112
- "transactionPolicy",
113
- "transactionRoot",
114
- "declarationRoot",
115
- "subject",
116
- "operationOrder",
117
- "effects",
118
- "planRoot",
119
- ];
120
- const EFFECT_FIELDS = [
121
- "schema",
122
- "kind",
123
- "transactionRoot",
124
- "operationId",
125
- "capabilityId",
126
- "executor",
127
- "adapter",
128
- "subjectRoot",
129
- "targetRoot",
130
- "attemptKey",
131
- "subject",
132
- "artifactRoles",
133
- "destination",
134
- "channelPolicy",
135
- "activationPolicy",
136
- "readbackPredicates",
137
- "idempotency",
138
- "retry",
139
- "evidenceRequirements",
140
- "effectRoot",
141
- ];
142
- const TERMINAL_STATES = new Set([
143
- "complete",
144
- "blocked",
145
- "repair-required",
146
- "terminal-failure",
147
- ]);
148
-
149
73
  export function releaseTailStableJson(value) {
150
- if (Array.isArray(value)) {
151
- return `[${value.map(releaseTailStableJson).join(",")}]`;
152
- }
153
- if (value && typeof value === "object") {
154
- return `{${Object.keys(value)
155
- .sort()
156
- .map(
157
- (key) => `${JSON.stringify(key)}:${releaseTailStableJson(value[key])}`,
158
- )
159
- .join(",")}}`;
160
- }
161
- return JSON.stringify(value);
74
+ const canonical = invokeV4DomainWasm("canonical-json", value).canonicalUtf8;
75
+ return canonical.endsWith("\n") ? canonical.slice(0, -1) : canonical;
162
76
  }
163
77
 
164
78
  export function releaseTailRoot(value) {
165
- return `sha256:${crypto
166
- .createHash("sha256")
167
- .update(releaseTailStableJson(value))
168
- .digest("hex")}`;
169
- }
170
-
171
- function fail(message) {
172
- throw new Error(`invalid release-tail declaration: ${message}`);
173
- }
174
-
175
- function plainObject(value, label) {
176
- if (!value || typeof value !== "object" || Array.isArray(value)) {
177
- fail(`${label} must be an object`);
178
- }
179
- return value;
180
- }
181
-
182
- function exactFields(value, fields, label) {
183
- plainObject(value, label);
184
- const actual = Object.keys(value).sort();
185
- const expected = [...fields].sort();
186
- if (actual.join("\n") !== expected.join("\n")) {
187
- fail(`${label} fields must be exactly: ${fields.join(", ")}`);
188
- }
189
- }
190
-
191
- function nonEmpty(value, label) {
192
- if (typeof value !== "string" || value.trim() === "") {
193
- fail(`${label} must be a non-empty string`);
194
- }
195
- return value;
196
- }
197
-
198
- function exactRoot(value, label) {
199
- const normalized = nonEmpty(value, label).toLowerCase();
200
- if (!ROOT.test(normalized)) fail(`${label} must be a sha256 content root`);
201
- return normalized;
202
- }
203
-
204
- function oneOf(value, choices, label) {
205
- if (!choices.includes(value)) {
206
- fail(`${label} must be one of ${choices.join(", ")}`);
207
- }
208
- return value;
209
- }
210
-
211
- function rejectExecutableKeys(value, pointer = "$") {
212
- if (Array.isArray(value)) {
213
- value.forEach((entry, index) =>
214
- rejectExecutableKeys(entry, `${pointer}/${index}`),
215
- );
216
- return;
217
- }
218
- if (!value || typeof value !== "object") return;
219
- for (const [key, entry] of Object.entries(value)) {
220
- if (FORBIDDEN_KEYS.has(key.toLowerCase())) {
221
- fail(`executable key '${key}' is forbidden at ${pointer}`);
222
- }
223
- rejectExecutableKeys(entry, `${pointer}/${key}`);
224
- }
225
- }
226
-
227
- function normalizeMessage(value, expectedSchema, expectedKind, label) {
228
- exactFields(value, ["kind", "schema"], label);
229
- if (value.schema !== expectedSchema) fail(`${label}.schema is not supported`);
230
- if (value.kind !== expectedKind)
231
- fail(`${label}.kind must be ${expectedKind}`);
232
- return { kind: value.kind, schema: value.schema };
233
- }
234
-
235
- function normalizeCapability(value, subject) {
236
- exactFields(
237
- value,
238
- CAPABILITY_FIELDS,
239
- `capability ${value?.id || "<unknown>"}`,
240
- );
241
- const registry = CAPABILITY_BY_ID.get(value.id);
242
- if (!registry) fail(`unsupported capability id: ${value.id || "<empty>"}`);
243
- if (value.executor !== registry.executor) {
244
- fail(`${value.id}.executor must be ${registry.executor}`);
245
- }
246
- if (value.adapter !== registry.adapter) {
247
- fail(`${value.id}.adapter must be ${registry.adapter}`);
248
- }
249
- if (!Array.isArray(value.artifactRoles)) {
250
- fail(`${value.id}.artifactRoles must be an array`);
251
- }
252
- const artifactRoles = value.artifactRoles.map((entry, index) => {
253
- exactFields(entry, ["role", "root"], `${value.id}.artifactRoles[${index}]`);
254
- return {
255
- role: nonEmpty(entry.role, `${value.id}.artifactRoles[${index}].role`),
256
- root: exactRoot(entry.root, `${value.id}.artifactRoles[${index}].root`),
257
- };
258
- });
259
- if (
260
- new Set(artifactRoles.map((entry) => entry.role)).size !==
261
- artifactRoles.length
262
- ) {
263
- fail(`${value.id}.artifactRoles contains duplicate roles`);
264
- }
265
-
266
- exactFields(
267
- value.destination,
268
- ["kind", "locator"],
269
- `${value.id}.destination`,
270
- );
271
- const destination = {
272
- kind: nonEmpty(value.destination.kind, `${value.id}.destination.kind`),
273
- locator: nonEmpty(
274
- value.destination.locator,
275
- `${value.id}.destination.locator`,
276
- ),
277
- };
278
-
279
- exactFields(
280
- value.channelPolicy,
281
- ["channel", "tagPattern", "authorityMove"],
282
- `${value.id}.channelPolicy`,
283
- );
284
- const channel = oneOf(
285
- value.channelPolicy.channel,
286
- ["alpha", "release", "stable"],
287
- `${value.id}.channelPolicy.channel`,
288
- );
289
- if (channel !== subject.channel)
290
- fail(`${value.id} channel does not match subject`);
291
- const tagPattern = nonEmpty(
292
- value.channelPolicy.tagPattern,
293
- `${value.id}.channelPolicy.tagPattern`,
294
- );
295
- let tagExpression;
296
- try {
297
- tagExpression = new RegExp(tagPattern, "u");
298
- } catch {
299
- fail(`${value.id}.channelPolicy.tagPattern is not a valid expression`);
300
- }
301
- if (!tagExpression.test(subject.tag)) {
302
- fail(`${value.id}.channelPolicy.tagPattern does not match subject.tag`);
303
- }
304
- const channelPolicy = {
305
- channel,
306
- tagPattern,
307
- authorityMove: oneOf(
308
- value.channelPolicy.authorityMove,
309
- ["none", "signed-cas", "verified-ref"],
310
- `${value.id}.channelPolicy.authorityMove`,
311
- ),
312
- };
313
-
314
- exactFields(
315
- value.activationPolicy,
316
- ["mode", "environment"],
317
- `${value.id}.activationPolicy`,
318
- );
319
- const activationPolicy = {
320
- mode: oneOf(
321
- value.activationPolicy.mode,
322
- ["none", "receipt-set", "receipt-only"],
323
- `${value.id}.activationPolicy.mode`,
324
- ),
325
- environment: oneOf(
326
- value.activationPolicy.environment,
327
- ["none", "shadow", "production"],
328
- `${value.id}.activationPolicy.environment`,
329
- ),
330
- };
331
-
332
- if (
333
- !Array.isArray(value.readbackPredicates) ||
334
- value.readbackPredicates.length === 0
335
- ) {
336
- fail(`${value.id} must declare at least one readback predicate`);
337
- }
338
- const readbackPredicates = value.readbackPredicates.map((entry, index) => {
339
- exactFields(
340
- entry,
341
- ["id", "kind", "expected"],
342
- `${value.id}.readbackPredicates[${index}]`,
343
- );
344
- return {
345
- id: nonEmpty(entry.id, `${value.id}.readbackPredicates[${index}].id`),
346
- kind: nonEmpty(
347
- entry.kind,
348
- `${value.id}.readbackPredicates[${index}].kind`,
349
- ),
350
- expected: nonEmpty(
351
- entry.expected,
352
- `${value.id}.readbackPredicates[${index}].expected`,
353
- ),
354
- };
355
- });
356
- if (
357
- new Set(readbackPredicates.map((entry) => entry.id)).size !==
358
- readbackPredicates.length
359
- ) {
360
- fail(`${value.id}.readbackPredicates contains duplicate ids`);
361
- }
362
-
363
- exactFields(
364
- value.operationIdentity,
365
- [
366
- "transactionRoot",
367
- "capabilityId",
368
- "subjectRoot",
369
- "targetRoot",
370
- "attemptKey",
371
- ],
372
- `${value.id}.operationIdentity`,
373
- );
374
- if (value.operationIdentity.capabilityId !== value.id) {
375
- fail(
376
- `${value.id}.operationIdentity.capabilityId must match the capability id`,
377
- );
378
- }
379
- const operationIdentity = {
380
- transactionRoot: exactRoot(
381
- value.operationIdentity.transactionRoot,
382
- `${value.id}.operationIdentity.transactionRoot`,
383
- ),
384
- capabilityId: value.id,
385
- subjectRoot: exactRoot(
386
- value.operationIdentity.subjectRoot,
387
- `${value.id}.operationIdentity.subjectRoot`,
388
- ),
389
- targetRoot: exactRoot(
390
- value.operationIdentity.targetRoot,
391
- `${value.id}.operationIdentity.targetRoot`,
392
- ),
393
- attemptKey: nonEmpty(
394
- value.operationIdentity.attemptKey,
395
- `${value.id}.operationIdentity.attemptKey`,
396
- ),
397
- };
398
-
399
- exactFields(
400
- value.idempotency,
401
- ["scope", "duplicate"],
402
- `${value.id}.idempotency`,
403
- );
404
- const idempotency = {
405
- scope: oneOf(
406
- value.idempotency.scope,
407
- ["operation", "subject-target"],
408
- `${value.id}.idempotency.scope`,
409
- ),
410
- duplicate: oneOf(
411
- value.idempotency.duplicate,
412
- ["return-observed-receipt", "readback-before-retry"],
413
- `${value.id}.idempotency.duplicate`,
414
- ),
415
- };
416
-
417
- exactFields(
418
- value.retry,
419
- ["class", "localAttempts", "exhausted"],
420
- `${value.id}.retry`,
421
- );
422
- const localAttempts = value.retry.localAttempts;
423
- if (
424
- !Number.isSafeInteger(localAttempts) ||
425
- localAttempts < 0 ||
426
- localAttempts > 3
427
- ) {
428
- fail(`${value.id}.retry.localAttempts must be between zero and three`);
429
- }
430
- const retry = {
431
- class: oneOf(
432
- value.retry.class,
433
- ["never", "readback", "provider-transient"],
434
- `${value.id}.retry.class`,
435
- ),
436
- localAttempts,
437
- exhausted: oneOf(
438
- value.retry.exhausted,
439
- ["blocked", "repair-required", "terminal-failure"],
440
- `${value.id}.retry.exhausted`,
441
- ),
442
- };
443
- if (retry.class === "never" && retry.localAttempts !== 0) {
444
- fail(`${value.id}.retry.localAttempts must be zero for retry class never`);
445
- }
446
-
447
- if (
448
- !Array.isArray(value.evidenceRequirements) ||
449
- value.evidenceRequirements.length === 0
450
- ) {
451
- fail(`${value.id}.evidenceRequirements must not be empty`);
452
- }
453
- const evidenceRequirements = value.evidenceRequirements.map((entry, index) =>
454
- nonEmpty(entry, `${value.id}.evidenceRequirements[${index}]`),
455
- );
456
-
457
- return {
458
- id: value.id,
459
- executor: value.executor,
460
- adapter: value.adapter,
461
- artifactRoles,
462
- destination,
463
- channelPolicy,
464
- activationPolicy,
465
- readbackPredicates,
466
- effect: normalizeMessage(
467
- value.effect,
468
- RELEASE_TAIL_EFFECT_SCHEMA,
469
- registry.effectKind,
470
- `${value.id}.effect`,
471
- ),
472
- observation: normalizeMessage(
473
- value.observation,
474
- RELEASE_TAIL_OBSERVATION_SCHEMA,
475
- registry.observationKind,
476
- `${value.id}.observation`,
477
- ),
478
- receipt: normalizeMessage(
479
- value.receipt,
480
- RELEASE_TAIL_RECEIPT_SCHEMA,
481
- registry.receiptKind,
482
- `${value.id}.receipt`,
483
- ),
484
- operationIdentity,
485
- idempotency,
486
- retry,
487
- evidenceRequirements,
488
- };
79
+ return invokeV4DomainWasm("release-tail-root", value).root;
489
80
  }
490
81
 
491
82
  export function parseReleaseTailDeclaration(input) {
@@ -494,337 +85,31 @@ export function parseReleaseTailDeclaration(input) {
494
85
  try {
495
86
  value = JSON.parse(input);
496
87
  } catch (error) {
497
- fail(`input is not valid JSON: ${error.message}`);
88
+ throw new Error(
89
+ `invalid release-tail declaration: input is not valid JSON: ${error.message}`,
90
+ );
498
91
  }
499
92
  }
500
- value = structuredClone(plainObject(value, "declaration"));
501
- rejectExecutableKeys(value);
502
- exactFields(value, TOP_LEVEL_FIELDS, "declaration");
503
- if (value.contract !== RELEASE_TAIL_DECLARATION_CONTRACT) {
504
- fail(`contract must be ${RELEASE_TAIL_DECLARATION_CONTRACT}`);
505
- }
506
- if (value.schemaVersion !== 1) fail("schemaVersion must be 1");
507
- if (value.transactionPolicy !== RELEASE_TAIL_TRANSACTION_POLICY) {
508
- fail(`transactionPolicy must be ${RELEASE_TAIL_TRANSACTION_POLICY}`);
509
- }
510
- exactFields(value.subject, SUBJECT_FIELDS, "subject");
511
- const subject = {
512
- repository: nonEmpty(value.subject.repository, "subject.repository"),
513
- sourceSha: nonEmpty(
514
- value.subject.sourceSha,
515
- "subject.sourceSha",
516
- ).toLowerCase(),
517
- version: nonEmpty(value.subject.version, "subject.version"),
518
- tag: nonEmpty(value.subject.tag, "subject.tag"),
519
- channel: oneOf(
520
- value.subject.channel,
521
- ["alpha", "release", "stable"],
522
- "subject.channel",
523
- ),
524
- };
525
- if (!SHA.test(subject.sourceSha)) {
526
- fail("subject.sourceSha must be an exact 40-character Git SHA");
527
- }
528
- if (!Array.isArray(value.capabilities) || value.capabilities.length === 0) {
529
- fail("capabilities must not be empty");
530
- }
531
- const capabilities = value.capabilities.map((entry) =>
532
- normalizeCapability(entry, subject),
533
- );
534
- if (
535
- new Set(capabilities.map((entry) => entry.id)).size !== capabilities.length
536
- ) {
537
- fail("capability ids must be unique");
538
- }
539
- const order = new Map(
540
- RELEASE_TAIL_CAPABILITY_REGISTRY.map((entry, index) => [entry.id, index]),
541
- );
542
- capabilities.sort((left, right) => order.get(left.id) - order.get(right.id));
543
- const transactionRoots = new Set(
544
- capabilities.map((entry) => entry.operationIdentity.transactionRoot),
545
- );
546
- if (transactionRoots.size !== 1) {
547
- fail("all operation identities must bind one transaction root");
548
- }
549
- return {
550
- contract: RELEASE_TAIL_DECLARATION_CONTRACT,
551
- schemaVersion: 1,
552
- transactionPolicy: RELEASE_TAIL_TRANSACTION_POLICY,
553
- subject,
554
- capabilities,
555
- };
556
- }
557
-
558
- function createEffect(declaration, capability) {
559
- const identity = capability.operationIdentity;
560
- const envelope = {
561
- schema: RELEASE_TAIL_EFFECT_SCHEMA,
562
- kind: capability.effect.kind,
563
- transactionRoot: identity.transactionRoot,
564
- operationId: releaseTailRoot(identity),
565
- capabilityId: capability.id,
566
- executor: capability.executor,
567
- adapter: capability.adapter,
568
- subjectRoot: identity.subjectRoot,
569
- targetRoot: identity.targetRoot,
570
- attemptKey: identity.attemptKey,
571
- subject: declaration.subject,
572
- artifactRoles: capability.artifactRoles,
573
- destination: capability.destination,
574
- channelPolicy: capability.channelPolicy,
575
- activationPolicy: capability.activationPolicy,
576
- readbackPredicates: capability.readbackPredicates,
577
- idempotency: capability.idempotency,
578
- retry: capability.retry,
579
- evidenceRequirements: capability.evidenceRequirements,
580
- };
581
- return { ...envelope, effectRoot: releaseTailRoot(envelope) };
93
+ return invokeV4DomainWasm("release-tail-parse", value);
582
94
  }
583
95
 
584
96
  export function compileReleaseTailDeclaration(input) {
585
- const declaration = parseReleaseTailDeclaration(input);
586
- const effects = declaration.capabilities.map((capability) =>
587
- createEffect(declaration, capability),
97
+ return invokeV4DomainWasm(
98
+ "release-tail-compile",
99
+ typeof input === "string" ? JSON.parse(input) : input,
588
100
  );
589
- const plan = {
590
- schema: "kungfu.buildchain.release-tail.effect-plan/v1",
591
- transactionPolicy: RELEASE_TAIL_TRANSACTION_POLICY,
592
- transactionRoot:
593
- declaration.capabilities[0].operationIdentity.transactionRoot,
594
- declarationRoot: releaseTailRoot(declaration),
595
- subject: declaration.subject,
596
- operationOrder: effects.map((effect) => effect.operationId),
597
- effects,
598
- };
599
- return { ...plan, planRoot: releaseTailRoot(plan) };
600
101
  }
601
102
 
602
103
  export function validateReleaseTailEffectPlan(plan) {
603
- const issues = [];
604
- try {
605
- exactFields(plan, EFFECT_PLAN_FIELDS, "effect plan");
606
- rejectExecutableKeys(plan);
607
- exactFields(plan.subject, SUBJECT_FIELDS, "effect plan subject");
608
- } catch (error) {
609
- issues.push(error.message);
610
- }
611
- if (plan?.schema !== "kungfu.buildchain.release-tail.effect-plan/v1") {
612
- issues.push("effect plan schema is invalid");
613
- }
614
- if (plan?.transactionPolicy !== RELEASE_TAIL_TRANSACTION_POLICY) {
615
- issues.push("effect plan transactionPolicy is invalid");
616
- }
617
- for (const field of ["transactionRoot", "declarationRoot", "planRoot"]) {
618
- if (!ROOT.test(plan?.[field] || "")) issues.push(`${field} is invalid`);
619
- }
620
- if (!Array.isArray(plan?.effects) || plan.effects.length === 0) {
621
- issues.push("effects must be a non-empty array");
622
- } else {
623
- const operationIds = [];
624
- for (const effect of plan.effects) {
625
- try {
626
- exactFields(effect, EFFECT_FIELDS, `effect ${effect?.capabilityId}`);
627
- } catch (error) {
628
- issues.push(error.message);
629
- }
630
- const descriptor = CAPABILITY_BY_ID.get(effect?.capabilityId);
631
- if (!descriptor) {
632
- issues.push(
633
- `unsupported effect capability: ${effect?.capabilityId || "<empty>"}`,
634
- );
635
- continue;
636
- }
637
- if (
638
- effect.schema !== RELEASE_TAIL_EFFECT_SCHEMA ||
639
- effect.kind !== descriptor.effectKind ||
640
- effect.executor !== descriptor.executor ||
641
- effect.adapter !== descriptor.adapter
642
- ) {
643
- issues.push(
644
- `effect ${effect.capabilityId} does not match the capability registry`,
645
- );
646
- }
647
- if (effect.transactionRoot !== plan.transactionRoot) {
648
- issues.push(`effect ${effect.capabilityId} transactionRoot mismatch`);
649
- }
650
- if (
651
- releaseTailStableJson(effect.subject) !==
652
- releaseTailStableJson(plan.subject)
653
- ) {
654
- issues.push(`effect ${effect.capabilityId} subject mismatch`);
655
- }
656
- if (
657
- !ROOT.test(effect.subjectRoot || "") ||
658
- !ROOT.test(effect.targetRoot || "")
659
- ) {
660
- issues.push(
661
- `effect ${effect.capabilityId} has an invalid subject or target root`,
662
- );
663
- }
664
- if (
665
- effect.operationId !==
666
- releaseTailRoot({
667
- transactionRoot: effect.transactionRoot,
668
- capabilityId: effect.capabilityId,
669
- subjectRoot: effect.subjectRoot,
670
- targetRoot: effect.targetRoot,
671
- attemptKey: effect.attemptKey,
672
- })
673
- ) {
674
- issues.push(`effect ${effect.capabilityId} operationId mismatch`);
675
- }
676
- const effectBody = structuredClone(effect);
677
- delete effectBody.effectRoot;
678
- if (effect.effectRoot !== releaseTailRoot(effectBody)) {
679
- issues.push(`effect ${effect.capabilityId} effectRoot mismatch`);
680
- }
681
- operationIds.push(effect.operationId);
682
- }
683
- if (new Set(operationIds).size !== operationIds.length) {
684
- issues.push("effect plan operation ids must be unique");
685
- }
686
- if (operationIds.join("\n") !== (plan.operationOrder || []).join("\n")) {
687
- issues.push("effect plan operationOrder mismatch");
688
- }
689
- }
690
- const body = structuredClone(plan || {});
691
- delete body.planRoot;
692
- if (plan?.planRoot !== releaseTailRoot(body))
693
- issues.push("planRoot mismatch");
694
- return { valid: issues.length === 0, issues };
104
+ return invokeV4DomainWasm("release-tail-validate-plan", plan);
695
105
  }
696
106
 
697
107
  export function createReleaseTailTransaction(input) {
698
- const plan = input?.schema?.endsWith("effect-plan/v1")
699
- ? structuredClone(input)
700
- : compileReleaseTailDeclaration(input);
701
- const planValidation = validateReleaseTailEffectPlan(plan);
702
- if (!planValidation.valid) {
703
- throw new Error(
704
- `invalid release-tail effect plan: ${planValidation.issues.join("; ")}`,
705
- );
706
- }
707
- const transaction = {
708
- schema: RELEASE_TAIL_TRANSACTION_SCHEMA,
709
- transactionPolicy: RELEASE_TAIL_TRANSACTION_POLICY,
710
- transactionRoot: plan.transactionRoot,
711
- declarationRoot: plan.declarationRoot,
712
- planRoot: plan.planRoot,
713
- subject: plan.subject,
714
- state: "prepared",
715
- operationOrder: plan.operationOrder,
716
- operations: plan.effects.map((effect) => ({
717
- operationId: effect.operationId,
718
- capabilityId: effect.capabilityId,
719
- effect,
720
- status: "pending",
721
- effectAttempts: 0,
722
- readbackAttempts: 0,
723
- observationRoots: [],
724
- receipt: null,
725
- })),
726
- observations: [],
727
- receipts: [],
728
- failure: null,
729
- };
730
- return refreshTransaction(transaction);
731
- }
732
-
733
- function transactionBody(transaction) {
734
- const copy = structuredClone(transaction);
735
- delete copy.stateRoot;
736
- return copy;
737
- }
738
-
739
- function refreshTransaction(transaction) {
740
- transaction.stateRoot = releaseTailRoot(transactionBody(transaction));
741
- return transaction;
108
+ return invokeV4DomainWasm("release-tail-create", input);
742
109
  }
743
110
 
744
111
  export function validateReleaseTailTransaction(transaction) {
745
- const issues = [];
746
- if (transaction?.schema !== RELEASE_TAIL_TRANSACTION_SCHEMA) {
747
- issues.push(`schema must be ${RELEASE_TAIL_TRANSACTION_SCHEMA}`);
748
- }
749
- if (!RELEASE_TAIL_STATES.includes(transaction?.state))
750
- issues.push("state is invalid");
751
- if (!ROOT.test(transaction?.transactionRoot || "")) {
752
- issues.push("transactionRoot is invalid");
753
- }
754
- if (!ROOT.test(transaction?.declarationRoot || "")) {
755
- issues.push("declarationRoot is invalid");
756
- }
757
- if (!ROOT.test(transaction?.planRoot || ""))
758
- issues.push("planRoot is invalid");
759
- if (!Array.isArray(transaction?.operations)) {
760
- issues.push("operations must be an array");
761
- } else {
762
- const ids = transaction.operations.map((entry) => entry.operationId);
763
- if (ids.join("\n") !== (transaction.operationOrder || []).join("\n")) {
764
- issues.push("operations must preserve operationOrder");
765
- }
766
- for (const operation of transaction.operations) {
767
- if (!["pending", "complete", "failed"].includes(operation.status)) {
768
- issues.push(
769
- `operation ${operation.operationId || "<unknown>"} status is invalid`,
770
- );
771
- }
772
- if (
773
- !Number.isSafeInteger(operation.effectAttempts) ||
774
- operation.effectAttempts < 0
775
- ) {
776
- issues.push(
777
- `operation ${operation.operationId || "<unknown>"} effectAttempts is invalid`,
778
- );
779
- }
780
- if (
781
- !Number.isSafeInteger(operation.readbackAttempts) ||
782
- operation.readbackAttempts < 0
783
- ) {
784
- issues.push(
785
- `operation ${operation.operationId || "<unknown>"} readbackAttempts is invalid`,
786
- );
787
- }
788
- if (!Array.isArray(operation.observationRoots)) {
789
- issues.push(
790
- `operation ${operation.operationId || "<unknown>"} observationRoots is invalid`,
791
- );
792
- }
793
- }
794
- }
795
- if (!Array.isArray(transaction?.observations)) {
796
- issues.push("observations must be an array");
797
- } else {
798
- for (const observation of transaction.observations) {
799
- const body = structuredClone(observation);
800
- delete body.observationRoot;
801
- if (observation.observationRoot !== releaseTailRoot(body)) {
802
- issues.push(
803
- `observation ${observation.operationId || "<unknown>"} root mismatch`,
804
- );
805
- }
806
- }
807
- }
808
- if (!Array.isArray(transaction?.receipts)) {
809
- issues.push("receipts must be an array");
810
- } else {
811
- for (const receipt of transaction.receipts) {
812
- const body = structuredClone(receipt);
813
- delete body.receiptRoot;
814
- if (receipt.receiptRoot !== releaseTailRoot(body)) {
815
- issues.push(
816
- `receipt ${receipt.operationId || "<unknown>"} root mismatch`,
817
- );
818
- }
819
- }
820
- }
821
- if (
822
- transaction?.stateRoot !==
823
- releaseTailRoot(transactionBody(transaction || {}))
824
- ) {
825
- issues.push("stateRoot mismatch");
826
- }
827
- return { valid: issues.length === 0, issues };
112
+ return invokeV4DomainWasm("release-tail-validate-transaction", transaction);
828
113
  }
829
114
 
830
115
  export function readReleaseTailTransaction(filePath) {
@@ -855,353 +140,88 @@ export function writeReleaseTailTransaction(filePath, transaction) {
855
140
  return resolved;
856
141
  }
857
142
 
858
- function normalizeAdapterObservation(effect, raw, phase) {
859
- const value = raw && typeof raw === "object" ? raw : {};
860
- const outcome = oneOf(
861
- value.outcome || "transient",
862
- ["observed", "absent", "transient", "conflict"],
863
- `${effect.capabilityId} adapter observation outcome`,
864
- );
865
- const subjectRoot = value.subjectRoot
866
- ? exactRoot(
867
- value.subjectRoot,
868
- `${effect.capabilityId} observation subjectRoot`,
869
- )
870
- : "";
871
- const targetRoot = value.targetRoot
872
- ? exactRoot(
873
- value.targetRoot,
874
- `${effect.capabilityId} observation targetRoot`,
875
- )
876
- : "";
877
- let status = outcome;
878
- if (outcome === "observed") {
879
- status =
880
- subjectRoot === effect.subjectRoot && targetRoot === effect.targetRoot
881
- ? "matched"
882
- : "stale";
143
+ async function emitCheckpoints(callback, checkpoints) {
144
+ if (!callback) return;
145
+ for (const checkpoint of checkpoints) {
146
+ await callback(structuredClone(checkpoint));
883
147
  }
884
- const evidenceRoots = Array.isArray(value.evidenceRoots)
885
- ? [
886
- ...new Set(
887
- value.evidenceRoots.map((root, index) =>
888
- exactRoot(
889
- root,
890
- `${effect.capabilityId} observation evidenceRoots[${index}]`,
891
- ),
892
- ),
893
- ),
894
- ].sort()
895
- : [];
896
- const observation = {
897
- schema: RELEASE_TAIL_OBSERVATION_SCHEMA,
898
- kind: CAPABILITY_BY_ID.get(effect.capabilityId).observationKind,
899
- transactionRoot: effect.transactionRoot,
900
- operationId: effect.operationId,
901
- capabilityId: effect.capabilityId,
902
- phase,
903
- status,
904
- subjectRoot,
905
- targetRoot,
906
- providerCode: /^[a-z0-9][a-z0-9._-]{0,159}$/u.test(
907
- String(value.providerCode || status),
908
- )
909
- ? String(value.providerCode || status)
910
- : "provider-observation",
911
- evidenceRoots,
912
- };
913
- return { ...observation, observationRoot: releaseTailRoot(observation) };
914
- }
915
-
916
- function createReceipt(operation, action, observation) {
917
- const effect = operation.effect;
918
- const body = {
919
- schema: RELEASE_TAIL_RECEIPT_SCHEMA,
920
- kind: CAPABILITY_BY_ID.get(effect.capabilityId).receiptKind,
921
- transactionRoot: effect.transactionRoot,
922
- operationId: effect.operationId,
923
- capabilityId: effect.capabilityId,
924
- subjectRoot: effect.subjectRoot,
925
- targetRoot: effect.targetRoot,
926
- effectRoot: effect.effectRoot,
927
- action,
928
- effectAttempts: operation.effectAttempts,
929
- readbackAttempts: operation.readbackAttempts,
930
- observationRoots: [...operation.observationRoots],
931
- evidenceRoots: observation.evidenceRoots,
932
- };
933
- return { ...body, receiptRoot: releaseTailRoot(body) };
934
- }
935
-
936
- function capabilityForOperation(operation) {
937
- return CAPABILITY_BY_ID.get(operation.capabilityId);
938
148
  }
939
149
 
940
- function nextPendingOperation(transaction) {
941
- return (
942
- transaction.operations.find((entry) => entry.status === "pending") || null
943
- );
944
- }
945
-
946
- function adapterFor(adapters, operation) {
947
- const descriptor = capabilityForOperation(operation);
948
- const adapter = adapters?.[descriptor.adapter];
150
+ function adapterFor(adapters, effect) {
151
+ const adapter = adapters?.[effect.adapter];
949
152
  if (
950
153
  !adapter ||
951
154
  typeof adapter.readback !== "function" ||
952
155
  typeof adapter.apply !== "function"
953
156
  ) {
954
157
  throw new Error(
955
- `release-tail adapter '${descriptor.adapter}' must provide readback and apply`,
158
+ `release-tail adapter '${effect.adapter}' must provide readback and apply`,
956
159
  );
957
160
  }
958
161
  return adapter;
959
162
  }
960
163
 
961
- function recordObservation(transaction, operationId, observation) {
962
- const next = structuredClone(transaction);
963
- const operation = next.operations.find(
964
- (entry) => entry.operationId === operationId,
965
- );
966
- if (!operation)
967
- throw new Error(`unknown release-tail operation: ${operationId}`);
968
- operation.observationRoots.push(observation.observationRoot);
969
- next.observations.push(observation);
970
- return refreshTransaction(next);
971
- }
972
-
973
- function completeOperation(transaction, operationId, action, observation) {
974
- const next = structuredClone(transaction);
975
- const operation = next.operations.find(
976
- (entry) => entry.operationId === operationId,
977
- );
978
- operation.status = "complete";
979
- operation.receipt = createReceipt(operation, action, observation);
980
- next.receipts = next.operations
981
- .filter((entry) => entry.receipt)
982
- .map((entry) => entry.receipt);
983
- next.failure = null;
984
- next.state = nextPendingOperation(next)
985
- ? capabilityForOperation(nextPendingOperation(next)).transactionState
986
- : "complete";
987
- return refreshTransaction(next);
988
- }
989
-
990
- function failOperation(
991
- transaction,
992
- operationId,
993
- state,
994
- code,
995
- observation = null,
996
- ) {
997
- const next = structuredClone(transaction);
998
- const operation = next.operations.find(
999
- (entry) => entry.operationId === operationId,
1000
- );
1001
- operation.status = "failed";
1002
- next.state = state;
1003
- next.failure = {
1004
- operationId,
1005
- capabilityId: operation.capabilityId,
1006
- code,
1007
- observationRoot: observation?.observationRoot || "",
164
+ function providerFailure(error, fallback) {
165
+ return {
166
+ classification:
167
+ error?.releaseTailClass === "conflict" ? "conflict" : "transient",
168
+ code: error?.releaseTailCode || fallback,
1008
169
  };
1009
- return refreshTransaction(next);
1010
- }
1011
-
1012
- async function checkpoint(callback, transaction) {
1013
- if (callback) await callback(structuredClone(transaction));
1014
170
  }
1015
171
 
1016
172
  export async function executeReleaseTailTransaction(
1017
173
  transaction,
1018
- { adapters, checkpoint: checkpointCallback } = {},
174
+ { adapters, checkpoint } = {},
1019
175
  ) {
1020
- const validation = validateReleaseTailTransaction(transaction);
1021
- if (!validation.valid) {
1022
- throw new Error(
1023
- `invalid release-tail transaction: ${validation.issues.join("; ")}`,
1024
- );
1025
- }
1026
- if (TERMINAL_STATES.has(transaction.state))
1027
- return structuredClone(transaction);
1028
- let current = structuredClone(transaction);
1029
- while (nextPendingOperation(current)) {
1030
- const pending = nextPendingOperation(current);
1031
- const capability = CAPABILITY_BY_ID.get(pending.capabilityId);
1032
- const adapter = adapterFor(adapters, pending);
1033
- current.state = capability.transactionState;
1034
- current = refreshTransaction(current);
1035
- await checkpoint(checkpointCallback, current);
1036
-
1037
- const policy = pending.effect.retry;
1038
- const maxCycles = 1 + policy.localAttempts;
1039
- let applied = pending.effectAttempts > 0;
1040
- let completed = false;
1041
-
1042
- for (let cycle = 0; cycle < maxCycles; cycle += 1) {
1043
- current.state = "reading-back";
1044
- current = refreshTransaction(current);
1045
- await checkpoint(checkpointCallback, current);
1046
- let rawReadback;
176
+ let exchange = invokeV4DomainWasm(
177
+ "release-tail-execution-start",
178
+ transaction,
179
+ );
180
+ await emitCheckpoints(checkpoint, exchange.checkpoints);
181
+ while (exchange.instruction.action !== "done") {
182
+ const { instruction } = exchange;
183
+ const adapter = adapterFor(adapters, instruction.effect);
184
+ let signal;
185
+ if (instruction.action === "readback") {
186
+ let raw;
1047
187
  try {
1048
- rawReadback = await adapter.readback(structuredClone(pending.effect));
188
+ raw = await adapter.readback(structuredClone(instruction.effect));
1049
189
  } catch (error) {
1050
- rawReadback = {
190
+ const failure = providerFailure(error, "readback-error");
191
+ raw = {
1051
192
  outcome:
1052
- error?.releaseTailClass === "conflict" ? "conflict" : "transient",
1053
- providerCode: error?.releaseTailCode || "readback-error",
193
+ failure.classification === "conflict" ? "conflict" : "transient",
194
+ providerCode: failure.code,
1054
195
  };
1055
196
  }
1056
- let operation = current.operations.find(
1057
- (entry) => entry.operationId === pending.operationId,
1058
- );
1059
- operation.readbackAttempts += 1;
1060
- current = refreshTransaction(current);
1061
- const preObservation = normalizeAdapterObservation(
1062
- pending.effect,
1063
- rawReadback,
1064
- applied ? "retry-readback" : "pre-effect-readback",
1065
- );
1066
- current = recordObservation(current, pending.operationId, preObservation);
1067
- await checkpoint(checkpointCallback, current);
1068
- if (preObservation.status === "matched") {
1069
- current = completeOperation(
1070
- current,
1071
- pending.operationId,
1072
- applied ? "applied-and-observed" : "observed-existing",
1073
- preObservation,
1074
- );
1075
- await checkpoint(checkpointCallback, current);
1076
- completed = true;
1077
- break;
1078
- }
1079
- if (preObservation.status === "conflict") {
1080
- current = failOperation(
1081
- current,
1082
- pending.operationId,
1083
- "terminal-failure",
1084
- "provider-conflict",
1085
- preObservation,
1086
- );
1087
- await checkpoint(checkpointCallback, current);
1088
- return current;
1089
- }
1090
- if (preObservation.status === "stale") {
1091
- current = failOperation(
1092
- current,
1093
- pending.operationId,
1094
- "repair-required",
1095
- "stale-readback",
1096
- preObservation,
1097
- );
1098
- await checkpoint(checkpointCallback, current);
1099
- return current;
1100
- }
1101
- if (preObservation.status === "transient") {
1102
- if (cycle + 1 < maxCycles) continue;
1103
- break;
1104
- }
1105
- if (applied && policy.class !== "provider-transient") continue;
1106
-
1107
- operation = current.operations.find(
1108
- (entry) => entry.operationId === pending.operationId,
1109
- );
1110
- operation.effectAttempts += 1;
1111
- current = refreshTransaction(current);
1112
- await checkpoint(checkpointCallback, current);
1113
- try {
1114
- await adapter.apply(structuredClone(pending.effect));
1115
- } catch (error) {
1116
- if (error?.releaseTailClass === "conflict") {
1117
- current = failOperation(
1118
- current,
1119
- pending.operationId,
1120
- "terminal-failure",
1121
- error?.releaseTailCode || "provider-conflict",
1122
- );
1123
- await checkpoint(checkpointCallback, current);
1124
- return current;
1125
- }
1126
- }
1127
- applied = true;
1128
-
1129
- current.state = "reading-back";
1130
- current = refreshTransaction(current);
1131
- await checkpoint(checkpointCallback, current);
1132
- let postRaw;
197
+ signal = {
198
+ kind: "readback",
199
+ phase: instruction.phase,
200
+ raw,
201
+ };
202
+ } else if (instruction.action === "apply") {
1133
203
  try {
1134
- postRaw = await adapter.readback(structuredClone(pending.effect));
204
+ await adapter.apply(structuredClone(instruction.effect));
205
+ signal = { kind: "apply", classification: "success", code: "" };
1135
206
  } catch (error) {
1136
- postRaw = {
1137
- outcome:
1138
- error?.releaseTailClass === "conflict" ? "conflict" : "transient",
1139
- providerCode: error?.releaseTailCode || "post-effect-readback-error",
207
+ signal = {
208
+ kind: "apply",
209
+ ...providerFailure(error, "provider-apply-error"),
1140
210
  };
1141
211
  }
1142
- operation = current.operations.find(
1143
- (entry) => entry.operationId === pending.operationId,
212
+ } else {
213
+ throw new Error(
214
+ `unsupported Rust/WASM release-tail instruction '${instruction.action}'`,
1144
215
  );
1145
- operation.readbackAttempts += 1;
1146
- current = refreshTransaction(current);
1147
- const postObservation = normalizeAdapterObservation(
1148
- pending.effect,
1149
- postRaw,
1150
- "post-effect-readback",
1151
- );
1152
- current = recordObservation(
1153
- current,
1154
- pending.operationId,
1155
- postObservation,
1156
- );
1157
- await checkpoint(checkpointCallback, current);
1158
- if (postObservation.status === "matched") {
1159
- current = completeOperation(
1160
- current,
1161
- pending.operationId,
1162
- "applied-and-observed",
1163
- postObservation,
1164
- );
1165
- await checkpoint(checkpointCallback, current);
1166
- completed = true;
1167
- break;
1168
- }
1169
- if (postObservation.status === "conflict") {
1170
- current = failOperation(
1171
- current,
1172
- pending.operationId,
1173
- "terminal-failure",
1174
- "provider-conflict",
1175
- postObservation,
1176
- );
1177
- await checkpoint(checkpointCallback, current);
1178
- return current;
1179
- }
1180
- if (postObservation.status === "stale") {
1181
- current = failOperation(
1182
- current,
1183
- pending.operationId,
1184
- "repair-required",
1185
- "stale-readback",
1186
- postObservation,
1187
- );
1188
- await checkpoint(checkpointCallback, current);
1189
- return current;
1190
- }
1191
- }
1192
-
1193
- if (!completed) {
1194
- current = failOperation(
1195
- current,
1196
- pending.operationId,
1197
- policy.exhausted,
1198
- "local-retry-exhausted",
1199
- );
1200
- await checkpoint(checkpointCallback, current);
1201
- return current;
1202
216
  }
1203
- }
1204
- return current;
217
+ exchange = invokeV4DomainWasm("release-tail-execution-advance", {
218
+ transaction: exchange.transaction,
219
+ cursor: exchange.cursor,
220
+ signal,
221
+ });
222
+ await emitCheckpoints(checkpoint, exchange.checkpoints);
223
+ }
224
+ return exchange.transaction;
1205
225
  }
1206
226
 
1207
227
  export function releaseTailRetryPolicyFromDeclaration(input) {