@snappedly-tools/shipyard 0.8.0 → 0.9.1

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 (45) hide show
  1. package/README.md +24 -14
  2. package/dist/MountConfig-K5ILnfht.d.ts +26 -0
  3. package/dist/{MountConfig-BHnKnA4h.d.ts → SandboxProvider-oUAwYlWm.d.ts} +1 -26
  4. package/dist/{chunk-L6PX5QTU.js → chunk-57EEKW3R.js} +57 -55
  5. package/dist/chunk-57EEKW3R.js.map +1 -0
  6. package/dist/{chunk-JI3HDDMS.js → chunk-HXSZM52J.js} +3 -3
  7. package/dist/{chunk-JI3HDDMS.js.map → chunk-HXSZM52J.js.map} +1 -1
  8. package/dist/{chunk-44I2BL6E.js → chunk-JZUBT4WG.js} +20 -4
  9. package/dist/chunk-JZUBT4WG.js.map +1 -0
  10. package/dist/chunk-NQRFVKCU.js +1840 -0
  11. package/dist/chunk-NQRFVKCU.js.map +1 -0
  12. package/dist/chunk-Z5C4LHVP.js +137 -0
  13. package/dist/chunk-Z5C4LHVP.js.map +1 -0
  14. package/dist/createSandbox-DmbnWAZv.d.ts +739 -0
  15. package/dist/index.d.ts +7 -2534
  16. package/dist/index.js +216 -7269
  17. package/dist/index.js.map +1 -1
  18. package/dist/integrations/github.d.ts +52 -0
  19. package/dist/integrations/github.js +1049 -0
  20. package/dist/integrations/github.js.map +1 -0
  21. package/dist/integrations/releases.d.ts +136 -0
  22. package/dist/integrations/releases.js +500 -0
  23. package/dist/integrations/releases.js.map +1 -0
  24. package/dist/main.js +641 -383
  25. package/dist/main.js.map +1 -1
  26. package/dist/publication-BPoy_M9M.d.ts +1200 -0
  27. package/dist/sandboxes/docker.d.ts +2 -1
  28. package/dist/sandboxes/docker.js +2 -2
  29. package/dist/templates/parallel-planner/main.mts +11 -7
  30. package/dist/templates/parallel-planner/setup.sh +1 -0
  31. package/dist/templates/parallel-planner-with-review/main.mts +11 -7
  32. package/dist/templates/parallel-planner-with-review/setup.sh +1 -0
  33. package/dist/templates/sequential-reviewer/main.mts +7 -3
  34. package/dist/templates/sequential-reviewer/setup.sh +1 -0
  35. package/dist/templates/shared/setup.sh +1 -0
  36. package/dist/templates/simple-loop/main.mts +7 -3
  37. package/dist/templates/simple-loop/setup.sh +1 -0
  38. package/dist/workflow/coordinator/migrations/002_workflow_phase_records.sql +11 -0
  39. package/dist/workflow/coordinator/migrations/003_phase_record_schema_version.sql +6 -0
  40. package/dist/workflow.d.ts +472 -0
  41. package/dist/workflow.js +4345 -0
  42. package/dist/workflow.js.map +1 -0
  43. package/package.json +13 -1
  44. package/dist/chunk-44I2BL6E.js.map +0 -1
  45. package/dist/chunk-L6PX5QTU.js.map +0 -1
@@ -0,0 +1,500 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ // src/integrations/releases/index.ts
4
+ var InMemoryReleaseStore = class {
5
+ states = /* @__PURE__ */ new Map();
6
+ get(key) {
7
+ const state = this.states.get(key);
8
+ return state === void 0 ? void 0 : structuredClone(state);
9
+ }
10
+ save(state) {
11
+ this.states.set(state.key, structuredClone(state));
12
+ }
13
+ };
14
+ var canonicalCandidate = (candidate) => JSON.stringify({
15
+ artifactDigest: candidate.artifactDigest,
16
+ artifactRef: candidate.artifactRef,
17
+ briefHash: candidate.briefHash,
18
+ reviewedHeadSha: candidate.reviewedHeadSha,
19
+ sourceSha: candidate.sourceSha,
20
+ version: candidate.version ?? ""
21
+ });
22
+ var releaseCandidateKey = (candidate) => createHash("sha256").update(canonicalCandidate(candidate)).digest("hex");
23
+ var sameCandidate = (left, right) => releaseCandidateKey(left) === releaseCandidateKey(right);
24
+ var defaultNow = () => (/* @__PURE__ */ new Date()).toISOString();
25
+ var isFreshTimestamp = (value, now, maxAgeSeconds) => {
26
+ const timestamp = Date.parse(value);
27
+ const current = Date.parse(now);
28
+ return Number.isFinite(timestamp) && Number.isFinite(current) && timestamp <= current && current - timestamp <= maxAgeSeconds * 1e3;
29
+ };
30
+ var approvalRoles = (policy) => policy.approvalRoles ?? ["owner", "maintainer"];
31
+ var validCandidate = (candidate, validateArtifactRef) => {
32
+ const fields = [
33
+ ["sourceSha", candidate.sourceSha],
34
+ ["reviewedHeadSha", candidate.reviewedHeadSha],
35
+ ["briefHash", candidate.briefHash],
36
+ ["artifactDigest", candidate.artifactDigest],
37
+ ["artifactRef", candidate.artifactRef]
38
+ ];
39
+ const missing = fields.find(
40
+ ([, value]) => typeof value !== "string" || value.trim().length === 0
41
+ );
42
+ if (missing !== void 0) return `${missing[0]} is required`;
43
+ const gitObject = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;
44
+ if (!gitObject.test(candidate.sourceSha)) {
45
+ return "sourceSha must be a full Git object id";
46
+ }
47
+ if (!gitObject.test(candidate.reviewedHeadSha)) {
48
+ return "reviewedHeadSha must be a full Git object id";
49
+ }
50
+ if (!/^[0-9a-f]{64}$/i.test(candidate.briefHash)) {
51
+ return "briefHash must be a SHA-256 hash";
52
+ }
53
+ if (!/^sha256:[0-9a-f]{64}$/i.test(candidate.artifactDigest)) {
54
+ return "artifactDigest must be a SHA-256 digest";
55
+ }
56
+ if (/[\s?#]/.test(candidate.artifactRef)) {
57
+ return "artifactRef must not contain whitespace, a query, or a fragment";
58
+ }
59
+ const digestReference = candidate.artifactRef.match(
60
+ /@sha256:([0-9a-f]{64})$/i
61
+ );
62
+ if (digestReference !== null) {
63
+ return digestReference[1].toLowerCase() === candidate.artifactDigest.slice("sha256:".length).toLowerCase() ? void 0 : "digest-qualified artifactRef must match artifactDigest";
64
+ }
65
+ const immutableNpmReference = /^npm:(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*@v?\d+\.\d+\.\d+(?:-[0-9a-z.-]+)?(?:\+[0-9a-z.-]+)?$/i;
66
+ const immutableGitReference = /^(?:git|github):[^\s/@]+\/[^\s@]+@[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;
67
+ if (immutableNpmReference.test(candidate.artifactRef) || immutableGitReference.test(candidate.artifactRef)) {
68
+ return void 0;
69
+ }
70
+ if (validateArtifactRef !== void 0) {
71
+ return validateArtifactRef(candidate);
72
+ }
73
+ return "artifactRef format is not proven immutable";
74
+ };
75
+ var isIntentDeploymentId = (deploymentId) => deploymentId.startsWith("intent:");
76
+ var requiredChecksPassed = (policy, checks) => {
77
+ const reasons = [];
78
+ for (const required of policy.requiredStagingChecks) {
79
+ const check = checks.find((candidate) => candidate.name === required);
80
+ if (check === void 0)
81
+ reasons.push(`Required staging check is missing: ${required}`);
82
+ else if (check.status !== "passed") {
83
+ reasons.push(`Required staging check ${required} is ${check.status}`);
84
+ }
85
+ }
86
+ return reasons;
87
+ };
88
+ var verificationReasons = (policy, verification) => [
89
+ ...requiredChecksPassed(policy, verification.checks),
90
+ ...verification.smoke === "passed" ? [] : [`Staging smoke is ${verification.smoke}`],
91
+ ...verification.health === "passed" ? [] : [`Staging health is ${verification.health}`],
92
+ ...verification.failureReason ? [verification.failureReason] : []
93
+ ];
94
+ var blocked = (reason, state) => ({
95
+ outcome: "blocked",
96
+ reason,
97
+ state
98
+ });
99
+ var ReleaseIntegration = class {
100
+ policy;
101
+ store;
102
+ transport;
103
+ actor;
104
+ validateArtifactRef;
105
+ now;
106
+ constructor(options) {
107
+ if (options.policy.repository.trim().length === 0) {
108
+ throw new Error("Release policy repository is required");
109
+ }
110
+ if (options.policy.stagingName.trim().length === 0) {
111
+ throw new Error("Release policy staging environment is required");
112
+ }
113
+ if (options.policy.productionName.trim().length === 0) {
114
+ throw new Error("Release policy production environment is required");
115
+ }
116
+ if (options.actor.id.trim().length === 0) {
117
+ throw new Error("Release actor is required");
118
+ }
119
+ if (options.policy.approvalRoles?.some(
120
+ (role) => role !== "owner" && role !== "maintainer"
121
+ )) {
122
+ throw new Error("Release approval roles are invalid");
123
+ }
124
+ this.policy = structuredClone(options.policy);
125
+ this.store = options.store;
126
+ this.transport = options.transport;
127
+ this.actor = options.actor;
128
+ this.validateArtifactRef = options.validateArtifactRef;
129
+ this.now = options.now ?? defaultNow;
130
+ }
131
+ // Bind evidence and remote idempotency to the deployment target and gate.
132
+ // Candidate-only records from older versions deliberately do not authorize releases.
133
+ stateKey(candidate) {
134
+ return createHash("sha256").update(
135
+ JSON.stringify({
136
+ candidate: releaseCandidateKey(candidate),
137
+ repository: this.policy.repository,
138
+ staging: this.policy.stagingName,
139
+ production: this.policy.productionName,
140
+ checks: [...this.policy.requiredStagingChecks].sort(),
141
+ humanApproval: this.policy.requireHumanApproval,
142
+ roles: [...approvalRoles(this.policy)].sort(),
143
+ freshness: this.policy.approvalFreshnessSeconds ?? 300,
144
+ recovery: this.policy.recoveryMode
145
+ })
146
+ ).digest("hex");
147
+ }
148
+ async requestStaging(candidate) {
149
+ const invalid = validCandidate(candidate, this.validateArtifactRef);
150
+ if (invalid !== void 0)
151
+ return blocked(`Invalid release candidate: ${invalid}`);
152
+ const key = this.stateKey(candidate);
153
+ const existing = this.store.get(key);
154
+ const idempotencyKey = `shipyard:${key}:staging`;
155
+ const intentId = `intent:${idempotencyKey}`;
156
+ if (existing !== void 0 && (existing.status !== "staging-requested" || existing.staging?.deploymentId !== intentId)) {
157
+ return { outcome: "duplicate", state: existing };
158
+ }
159
+ const timestamp = this.now();
160
+ const intent = existing ?? {
161
+ key,
162
+ candidate,
163
+ status: "staging-requested",
164
+ staging: {
165
+ environment: "staging",
166
+ candidate,
167
+ deploymentId: intentId,
168
+ checks: [],
169
+ smoke: "unknown",
170
+ health: "unknown",
171
+ recordedAt: timestamp
172
+ },
173
+ updatedAt: timestamp
174
+ };
175
+ this.store.save(intent);
176
+ const deployment = await this.requestDeployment(
177
+ "staging",
178
+ candidate,
179
+ idempotencyKey
180
+ );
181
+ if (deployment === void 0) {
182
+ return {
183
+ outcome: "failed",
184
+ reason: "Staging deployment could not be requested",
185
+ state: this.store.get(key)
186
+ };
187
+ }
188
+ const updated = {
189
+ ...intent,
190
+ staging: {
191
+ ...intent.staging,
192
+ deploymentId: deployment.deploymentId,
193
+ recordedAt: this.now()
194
+ },
195
+ updatedAt: this.now()
196
+ };
197
+ this.store.save(updated);
198
+ return { outcome: "requested", state: updated };
199
+ }
200
+ recordStagingVerification(verification) {
201
+ const invalid = validCandidate(
202
+ verification.candidate,
203
+ this.validateArtifactRef
204
+ );
205
+ if (invalid !== void 0)
206
+ return blocked(`Invalid release candidate: ${invalid}`);
207
+ if (verification.environment !== "staging")
208
+ return blocked("Verification is not for staging");
209
+ const key = this.stateKey(verification.candidate);
210
+ const state = this.store.get(key);
211
+ if (state === void 0)
212
+ return blocked("Staging deployment was not requested");
213
+ if (state.staging === void 0 || isIntentDeploymentId(state.staging.deploymentId)) {
214
+ return blocked(
215
+ "Staging deployment request has not been confirmed",
216
+ state
217
+ );
218
+ }
219
+ if (state.staging?.deploymentId !== verification.deploymentId) {
220
+ return blocked(
221
+ "Staging verification references a different deployment",
222
+ state
223
+ );
224
+ }
225
+ if (state.status === "production-failed") {
226
+ return blocked(
227
+ "Production verification failed; recovery is required before staging can be recorded",
228
+ state
229
+ );
230
+ }
231
+ if (!sameCandidate(state.candidate, verification.candidate)) {
232
+ return blocked(
233
+ "Staging verification references a different candidate",
234
+ state
235
+ );
236
+ }
237
+ const reasons = verificationReasons(this.policy, verification);
238
+ const updated = {
239
+ ...state,
240
+ status: reasons.length === 0 ? "staging-verified" : "staging-failed",
241
+ staging: structuredClone(verification),
242
+ updatedAt: this.now()
243
+ };
244
+ this.store.save(updated);
245
+ return reasons.length === 0 ? { outcome: "verified", state: updated } : { outcome: "failed", reason: reasons.join("; "), state: updated };
246
+ }
247
+ approveProduction(input) {
248
+ const invalid = validCandidate(input.candidate, this.validateArtifactRef);
249
+ if (invalid !== void 0)
250
+ return blocked(`Invalid release candidate: ${invalid}`);
251
+ const key = this.stateKey(input.candidate);
252
+ const state = this.store.get(key);
253
+ if (state === void 0)
254
+ return blocked("Candidate has no staging release state");
255
+ if (state.status !== "staging-verified") {
256
+ if (state.status === "production-approved") {
257
+ return { outcome: "duplicate", state };
258
+ }
259
+ return blocked("Production approval requires verified staging", state);
260
+ }
261
+ if (!this.policy.requireHumanApproval) {
262
+ return blocked(
263
+ "Production approval policy is not configured for a human gate",
264
+ state
265
+ );
266
+ }
267
+ if (!sameCandidate(input.candidate, input.approval.candidate)) {
268
+ return blocked(
269
+ "Production approval does not match the exact candidate",
270
+ state
271
+ );
272
+ }
273
+ if (input.approval.actor.id.trim().length === 0) {
274
+ return blocked("Production approval actor is missing", state);
275
+ }
276
+ const actorRole = input.approval.actor.role;
277
+ if (actorRole === "release-coordinator") {
278
+ return blocked(
279
+ "Production approval requires an owner or maintainer",
280
+ state
281
+ );
282
+ }
283
+ if (!approvalRoles(this.policy).includes(actorRole)) {
284
+ return blocked(
285
+ "Production approval role is not allowed by release policy",
286
+ state
287
+ );
288
+ }
289
+ if (input.approval.approvedAt.trim().length === 0) {
290
+ return blocked("Production approval timestamp is missing", state);
291
+ }
292
+ const approvalFreshnessSeconds = this.policy.approvalFreshnessSeconds ?? 300;
293
+ if (!Number.isFinite(approvalFreshnessSeconds) || approvalFreshnessSeconds <= 0) {
294
+ return blocked("Production approval freshness policy is invalid", state);
295
+ }
296
+ if (!isFreshTimestamp(
297
+ input.approval.approvedAt,
298
+ this.now(),
299
+ approvalFreshnessSeconds
300
+ )) {
301
+ return blocked("Production approval evidence is not fresh", state);
302
+ }
303
+ const updated = {
304
+ ...state,
305
+ status: "production-approved",
306
+ approval: structuredClone(input.approval),
307
+ updatedAt: this.now()
308
+ };
309
+ this.store.save(updated);
310
+ return { outcome: "approved", state: updated };
311
+ }
312
+ async promoteProduction(candidate) {
313
+ const invalid = validCandidate(candidate, this.validateArtifactRef);
314
+ if (invalid !== void 0)
315
+ return blocked(`Invalid release candidate: ${invalid}`);
316
+ const key = this.stateKey(candidate);
317
+ const state = this.store.get(key);
318
+ if (state === void 0) return blocked("Candidate has no release state");
319
+ const idempotencyKey = `shipyard:${key}:production`;
320
+ const intentId = `intent:${idempotencyKey}`;
321
+ if (state.status === "production-verified") {
322
+ return { outcome: "duplicate", state };
323
+ }
324
+ const pendingIntent = state.status === "production-requested" && state.production?.deploymentId === intentId;
325
+ if (state.status === "production-requested" && !pendingIntent) {
326
+ return { outcome: "duplicate", state };
327
+ }
328
+ if (state.status === "production-failed") {
329
+ return blocked(
330
+ "Production verification failed; recovery decision is required",
331
+ state
332
+ );
333
+ }
334
+ if (!pendingIntent && (state.status !== "production-approved" || state.approval === void 0)) {
335
+ return blocked(
336
+ "Production promotion requires exact human approval after staging",
337
+ state
338
+ );
339
+ }
340
+ if (state.approval === void 0 || !sameCandidate(candidate, state.approval.candidate)) {
341
+ return blocked("Production approval is stale for this candidate", state);
342
+ }
343
+ const approvalFreshnessSeconds = this.policy.approvalFreshnessSeconds ?? 300;
344
+ if (!Number.isFinite(approvalFreshnessSeconds) || approvalFreshnessSeconds <= 0) {
345
+ return blocked("Production approval freshness policy is invalid", state);
346
+ }
347
+ if (!isFreshTimestamp(
348
+ state.approval.approvedAt,
349
+ this.now(),
350
+ approvalFreshnessSeconds
351
+ )) {
352
+ return blocked("Production approval evidence is not fresh", state);
353
+ }
354
+ const intent = pendingIntent ? state : {
355
+ ...state,
356
+ status: "production-requested",
357
+ production: {
358
+ environment: "production",
359
+ candidate,
360
+ deploymentId: intentId,
361
+ checks: [],
362
+ smoke: "unknown",
363
+ health: "unknown",
364
+ recordedAt: this.now()
365
+ },
366
+ updatedAt: this.now()
367
+ };
368
+ if (!pendingIntent) this.store.save(intent);
369
+ const deployment = await this.requestDeployment(
370
+ "production",
371
+ candidate,
372
+ idempotencyKey
373
+ );
374
+ if (deployment === void 0) {
375
+ return {
376
+ outcome: "failed",
377
+ reason: "Production deployment could not be requested",
378
+ state: this.store.get(key)
379
+ };
380
+ }
381
+ const updated = {
382
+ ...intent,
383
+ production: {
384
+ ...intent.production,
385
+ deploymentId: deployment.deploymentId,
386
+ recordedAt: this.now()
387
+ },
388
+ updatedAt: this.now()
389
+ };
390
+ this.store.save(updated);
391
+ return { outcome: "requested", state: updated };
392
+ }
393
+ async requestDeployment(environment, candidate, idempotencyKey) {
394
+ const environmentName = environment === "staging" ? this.policy.stagingName : this.policy.productionName;
395
+ let reconciled;
396
+ try {
397
+ reconciled = this.transport.reconcileDeployment ? await this.transport.reconcileDeployment({
398
+ environment,
399
+ environmentName,
400
+ candidate,
401
+ idempotencyKey
402
+ }) : void 0;
403
+ } catch {
404
+ reconciled = void 0;
405
+ }
406
+ if (reconciled !== void 0) {
407
+ return reconciled.deploymentId.trim().length > 0 ? reconciled : void 0;
408
+ }
409
+ try {
410
+ const deployment = await this.transport.requestDeployment({
411
+ environment,
412
+ environmentName,
413
+ candidate,
414
+ actor: this.actor,
415
+ idempotencyKey
416
+ });
417
+ if (deployment.deploymentId.trim().length === 0) {
418
+ return void 0;
419
+ }
420
+ return deployment;
421
+ } catch {
422
+ return void 0;
423
+ }
424
+ }
425
+ recordProductionVerification(verification) {
426
+ const invalid = validCandidate(
427
+ verification.candidate,
428
+ this.validateArtifactRef
429
+ );
430
+ if (invalid !== void 0)
431
+ return blocked(`Invalid release candidate: ${invalid}`);
432
+ if (verification.environment !== "production")
433
+ return blocked("Verification is not for production");
434
+ const key = this.stateKey(verification.candidate);
435
+ const state = this.store.get(key);
436
+ if (state === void 0 || state.production === void 0) {
437
+ return blocked("Production deployment was not requested");
438
+ }
439
+ if (isIntentDeploymentId(state.production.deploymentId)) {
440
+ return blocked(
441
+ "Production deployment request has not been confirmed",
442
+ state
443
+ );
444
+ }
445
+ if (state.status === "production-failed") {
446
+ return blocked(
447
+ "Production verification already failed; record recovery before any new candidate",
448
+ state
449
+ );
450
+ }
451
+ if (state.production.deploymentId !== verification.deploymentId || !sameCandidate(state.candidate, verification.candidate)) {
452
+ return blocked(
453
+ "Production verification references a different deployment or candidate",
454
+ state
455
+ );
456
+ }
457
+ const failed = requiredChecksPassed(this.policy, verification.checks).length > 0 || verification.smoke !== "passed" || verification.health !== "passed" || verification.checks.some((check) => check.status !== "passed");
458
+ const updated = {
459
+ ...state,
460
+ status: failed ? "production-failed" : "production-verified",
461
+ production: structuredClone(verification),
462
+ updatedAt: this.now()
463
+ };
464
+ this.store.save(updated);
465
+ return failed ? {
466
+ outcome: "failed",
467
+ reason: verification.failureReason ?? "Production verification failed",
468
+ state: updated
469
+ } : { outcome: "verified", state: updated };
470
+ }
471
+ recordRecovery(input) {
472
+ const key = this.stateKey(input.candidate);
473
+ const state = this.store.get(key);
474
+ if (state === void 0 || state.status !== "production-failed") {
475
+ throw new Error(
476
+ "Recovery can only be recorded for a failed production candidate"
477
+ );
478
+ }
479
+ if (input.reason.trim().length === 0) {
480
+ throw new Error("Recovery reason is required");
481
+ }
482
+ if (input.authorizedBy.id.trim().length === 0 || input.authorizedBy.role === "release-coordinator" || !approvalRoles(this.policy).includes(input.authorizedBy.role)) {
483
+ throw new Error("Recovery requires an authorized owner or maintainer");
484
+ }
485
+ const recovery = {
486
+ candidate: structuredClone(input.candidate),
487
+ action: this.policy.recoveryMode,
488
+ reason: input.reason,
489
+ authorizedBy: input.authorizedBy,
490
+ recordedAt: this.now()
491
+ };
492
+ this.store.save({ ...state, recovery, updatedAt: this.now() });
493
+ return recovery;
494
+ }
495
+ };
496
+ var candidateMatchesApproval = (candidate, approval) => sameCandidate(candidate, approval.candidate);
497
+
498
+ export { InMemoryReleaseStore, ReleaseIntegration, candidateMatchesApproval, releaseCandidateKey };
499
+ //# sourceMappingURL=releases.js.map
500
+ //# sourceMappingURL=releases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/integrations/releases/index.ts"],"names":[],"mappings":"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","file":"releases.js","sourcesContent":["import { createHash } from \"node:crypto\";\n\nexport type ReleaseEnvironment = \"staging\" | \"production\";\nexport type ReleaseCheckStatus = \"passed\" | \"failed\" | \"missing\" | \"unknown\";\n\nexport interface ReleaseCandidate {\n readonly sourceSha: string;\n readonly reviewedHeadSha: string;\n readonly briefHash: string;\n readonly artifactDigest: string;\n /** Provider-specific immutable locator; integrity is bound separately by artifactDigest. */\n readonly artifactRef: string;\n readonly version?: string;\n}\n\nexport interface ReleasePolicy {\n readonly repository: string;\n readonly stagingName: string;\n readonly productionName: string;\n readonly requiredStagingChecks: readonly string[];\n readonly requireHumanApproval: boolean;\n /** Maximum age for a production approval before it must be renewed. */\n readonly approvalFreshnessSeconds?: number;\n /** Roles allowed to approve production; release coordinators may request but not approve. */\n readonly approvalRoles?: readonly (\"owner\" | \"maintainer\")[];\n /** Recovery is recorded as a requested action; it is never inferred or executed by default. */\n readonly recoveryMode: \"roll-forward\" | \"rollback\" | \"owner-decision\";\n}\n\nexport interface ReleaseActor {\n readonly id: string;\n readonly role: \"release-coordinator\" | \"owner\" | \"maintainer\";\n}\n\nexport interface ReleaseCheck {\n readonly name: string;\n readonly status: ReleaseCheckStatus;\n readonly summary: string;\n}\n\nexport interface ReleaseVerification {\n readonly environment: ReleaseEnvironment;\n readonly candidate: ReleaseCandidate;\n readonly deploymentId: string;\n readonly checks: readonly ReleaseCheck[];\n readonly smoke: \"passed\" | \"failed\" | \"missing\" | \"unknown\";\n readonly health: \"passed\" | \"failed\" | \"missing\" | \"unknown\";\n readonly recordedAt: string;\n readonly failureReason?: string;\n}\n\nexport interface ProductionApproval {\n readonly actor: ReleaseActor;\n readonly approvedAt: string;\n readonly candidate: ReleaseCandidate;\n}\n\nexport type ReleaseStateStatus =\n | \"staging-requested\"\n | \"staging-failed\"\n | \"staging-verified\"\n | \"production-approved\"\n | \"production-requested\"\n | \"production-failed\"\n | \"production-verified\";\n\nexport interface ReleaseState {\n readonly key: string;\n readonly candidate: ReleaseCandidate;\n readonly status: ReleaseStateStatus;\n readonly staging?: ReleaseVerification;\n readonly approval?: ProductionApproval;\n readonly recovery?: RecoveryRecord;\n readonly production?: ReleaseVerification;\n readonly updatedAt: string;\n}\n\nexport interface ReleaseStore {\n get(key: string): ReleaseState | undefined;\n save(state: ReleaseState): void;\n}\n\nexport class InMemoryReleaseStore implements ReleaseStore {\n private readonly states = new Map<string, ReleaseState>();\n\n get(key: string): ReleaseState | undefined {\n const state = this.states.get(key);\n return state === undefined ? undefined : structuredClone(state);\n }\n\n save(state: ReleaseState): void {\n this.states.set(state.key, structuredClone(state));\n }\n}\n\nexport interface ReleaseTransport {\n requestDeployment(input: {\n readonly environment: ReleaseEnvironment;\n readonly environmentName: string;\n readonly candidate: ReleaseCandidate;\n readonly actor: ReleaseActor;\n readonly idempotencyKey: string;\n }): Promise<{ readonly deploymentId: string }>;\n reconcileDeployment?(input: {\n readonly environment: ReleaseEnvironment;\n readonly environmentName: string;\n readonly candidate: ReleaseCandidate;\n readonly idempotencyKey: string;\n }): Promise<{ readonly deploymentId: string } | undefined>;\n}\n\nexport interface ReleaseIntegrationOptions {\n readonly policy: ReleasePolicy;\n readonly store: ReleaseStore;\n readonly transport: ReleaseTransport;\n readonly actor: ReleaseActor;\n /** Validate a provider-specific immutable locator not covered by built-in formats. */\n readonly validateArtifactRef?: (\n candidate: ReleaseCandidate,\n ) => string | undefined;\n readonly now?: () => string;\n}\n\nexport interface ReleaseOperationResult {\n readonly outcome:\n | \"requested\"\n | \"duplicate\"\n | \"verified\"\n | \"approved\"\n | \"blocked\"\n | \"failed\";\n readonly reason?: string;\n readonly state?: ReleaseState;\n}\n\nexport interface RecoveryRecord {\n readonly candidate: ReleaseCandidate;\n readonly action: ReleasePolicy[\"recoveryMode\"];\n readonly reason: string;\n readonly authorizedBy: ReleaseActor;\n readonly recordedAt: string;\n}\n\nconst canonicalCandidate = (candidate: ReleaseCandidate): string =>\n JSON.stringify({\n artifactDigest: candidate.artifactDigest,\n artifactRef: candidate.artifactRef,\n briefHash: candidate.briefHash,\n reviewedHeadSha: candidate.reviewedHeadSha,\n sourceSha: candidate.sourceSha,\n version: candidate.version ?? \"\",\n });\n\nexport const releaseCandidateKey = (candidate: ReleaseCandidate): string =>\n createHash(\"sha256\").update(canonicalCandidate(candidate)).digest(\"hex\");\n\nconst sameCandidate = (\n left: ReleaseCandidate,\n right: ReleaseCandidate,\n): boolean => releaseCandidateKey(left) === releaseCandidateKey(right);\n\nconst defaultNow = (): string => new Date().toISOString();\n\nconst isFreshTimestamp = (\n value: string,\n now: string,\n maxAgeSeconds: number,\n): boolean => {\n const timestamp = Date.parse(value);\n const current = Date.parse(now);\n return (\n Number.isFinite(timestamp) &&\n Number.isFinite(current) &&\n timestamp <= current &&\n current - timestamp <= maxAgeSeconds * 1000\n );\n};\n\nconst approvalRoles = (\n policy: ReleasePolicy,\n): readonly (\"owner\" | \"maintainer\")[] =>\n policy.approvalRoles ?? [\"owner\", \"maintainer\"];\n\nconst validCandidate = (\n candidate: ReleaseCandidate,\n validateArtifactRef?: ReleaseIntegrationOptions[\"validateArtifactRef\"],\n): string | undefined => {\n const fields: readonly [string, unknown][] = [\n [\"sourceSha\", candidate.sourceSha],\n [\"reviewedHeadSha\", candidate.reviewedHeadSha],\n [\"briefHash\", candidate.briefHash],\n [\"artifactDigest\", candidate.artifactDigest],\n [\"artifactRef\", candidate.artifactRef],\n ];\n const missing = fields.find(\n ([, value]) => typeof value !== \"string\" || value.trim().length === 0,\n );\n if (missing !== undefined) return `${missing[0]} is required`;\n const gitObject = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;\n if (!gitObject.test(candidate.sourceSha)) {\n return \"sourceSha must be a full Git object id\";\n }\n if (!gitObject.test(candidate.reviewedHeadSha)) {\n return \"reviewedHeadSha must be a full Git object id\";\n }\n if (!/^[0-9a-f]{64}$/i.test(candidate.briefHash)) {\n return \"briefHash must be a SHA-256 hash\";\n }\n if (!/^sha256:[0-9a-f]{64}$/i.test(candidate.artifactDigest)) {\n return \"artifactDigest must be a SHA-256 digest\";\n }\n if (/[\\s?#]/.test(candidate.artifactRef)) {\n return \"artifactRef must not contain whitespace, a query, or a fragment\";\n }\n const digestReference = candidate.artifactRef.match(\n /@sha256:([0-9a-f]{64})$/i,\n );\n if (digestReference !== null) {\n return digestReference[1]!.toLowerCase() ===\n candidate.artifactDigest.slice(\"sha256:\".length).toLowerCase()\n ? undefined\n : \"digest-qualified artifactRef must match artifactDigest\";\n }\n const immutableNpmReference =\n /^npm:(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*@v?\\d+\\.\\d+\\.\\d+(?:-[0-9a-z.-]+)?(?:\\+[0-9a-z.-]+)?$/i;\n const immutableGitReference =\n /^(?:git|github):[^\\s/@]+\\/[^\\s@]+@[0-9a-f]{40}(?:[0-9a-f]{24})?$/i;\n if (\n immutableNpmReference.test(candidate.artifactRef) ||\n immutableGitReference.test(candidate.artifactRef)\n ) {\n return undefined;\n }\n if (validateArtifactRef !== undefined) {\n return validateArtifactRef(candidate);\n }\n return \"artifactRef format is not proven immutable\";\n};\n\nconst isIntentDeploymentId = (deploymentId: string): boolean =>\n deploymentId.startsWith(\"intent:\");\n\nconst requiredChecksPassed = (\n policy: ReleasePolicy,\n checks: readonly ReleaseCheck[],\n): string[] => {\n const reasons: string[] = [];\n for (const required of policy.requiredStagingChecks) {\n const check = checks.find((candidate) => candidate.name === required);\n if (check === undefined)\n reasons.push(`Required staging check is missing: ${required}`);\n else if (check.status !== \"passed\") {\n reasons.push(`Required staging check ${required} is ${check.status}`);\n }\n }\n return reasons;\n};\n\nconst verificationReasons = (\n policy: ReleasePolicy,\n verification: ReleaseVerification,\n): string[] => [\n ...requiredChecksPassed(policy, verification.checks),\n ...(verification.smoke === \"passed\"\n ? []\n : [`Staging smoke is ${verification.smoke}`]),\n ...(verification.health === \"passed\"\n ? []\n : [`Staging health is ${verification.health}`]),\n ...(verification.failureReason ? [verification.failureReason] : []),\n];\n\nconst blocked = (\n reason: string,\n state?: ReleaseState,\n): ReleaseOperationResult => ({\n outcome: \"blocked\",\n reason,\n state,\n});\n\nexport class ReleaseIntegration {\n private readonly policy: ReleasePolicy;\n private readonly store: ReleaseStore;\n private readonly transport: ReleaseTransport;\n private readonly actor: ReleaseActor;\n private readonly validateArtifactRef?: ReleaseIntegrationOptions[\"validateArtifactRef\"];\n private readonly now: () => string;\n\n constructor(options: ReleaseIntegrationOptions) {\n if (options.policy.repository.trim().length === 0) {\n throw new Error(\"Release policy repository is required\");\n }\n if (options.policy.stagingName.trim().length === 0) {\n throw new Error(\"Release policy staging environment is required\");\n }\n if (options.policy.productionName.trim().length === 0) {\n throw new Error(\"Release policy production environment is required\");\n }\n if (options.actor.id.trim().length === 0) {\n throw new Error(\"Release actor is required\");\n }\n if (\n options.policy.approvalRoles?.some(\n (role) => role !== \"owner\" && role !== \"maintainer\",\n )\n ) {\n throw new Error(\"Release approval roles are invalid\");\n }\n this.policy = structuredClone(options.policy);\n this.store = options.store;\n this.transport = options.transport;\n this.actor = options.actor;\n this.validateArtifactRef = options.validateArtifactRef;\n this.now = options.now ?? defaultNow;\n }\n\n // Bind evidence and remote idempotency to the deployment target and gate.\n // Candidate-only records from older versions deliberately do not authorize releases.\n private stateKey(candidate: ReleaseCandidate): string {\n return createHash(\"sha256\")\n .update(\n JSON.stringify({\n candidate: releaseCandidateKey(candidate),\n repository: this.policy.repository,\n staging: this.policy.stagingName,\n production: this.policy.productionName,\n checks: [...this.policy.requiredStagingChecks].sort(),\n humanApproval: this.policy.requireHumanApproval,\n roles: [...approvalRoles(this.policy)].sort(),\n freshness: this.policy.approvalFreshnessSeconds ?? 300,\n recovery: this.policy.recoveryMode,\n }),\n )\n .digest(\"hex\");\n }\n\n async requestStaging(\n candidate: ReleaseCandidate,\n ): Promise<ReleaseOperationResult> {\n const invalid = validCandidate(candidate, this.validateArtifactRef);\n if (invalid !== undefined)\n return blocked(`Invalid release candidate: ${invalid}`);\n const key = this.stateKey(candidate);\n const existing = this.store.get(key);\n const idempotencyKey = `shipyard:${key}:staging`;\n const intentId = `intent:${idempotencyKey}`;\n if (\n existing !== undefined &&\n (existing.status !== \"staging-requested\" ||\n existing.staging?.deploymentId !== intentId)\n ) {\n return { outcome: \"duplicate\", state: existing };\n }\n const timestamp = this.now();\n const intent: ReleaseState = existing ?? {\n key,\n candidate,\n status: \"staging-requested\",\n staging: {\n environment: \"staging\",\n candidate,\n deploymentId: intentId,\n checks: [],\n smoke: \"unknown\",\n health: \"unknown\",\n recordedAt: timestamp,\n },\n updatedAt: timestamp,\n };\n this.store.save(intent);\n const deployment = await this.requestDeployment(\n \"staging\",\n candidate,\n idempotencyKey,\n );\n if (deployment === undefined) {\n return {\n outcome: \"failed\",\n reason: \"Staging deployment could not be requested\",\n state: this.store.get(key),\n };\n }\n const updated: ReleaseState = {\n ...intent,\n staging: {\n ...intent.staging!,\n deploymentId: deployment.deploymentId,\n recordedAt: this.now(),\n },\n updatedAt: this.now(),\n };\n this.store.save(updated);\n return { outcome: \"requested\", state: updated };\n }\n\n recordStagingVerification(\n verification: ReleaseVerification,\n ): ReleaseOperationResult {\n const invalid = validCandidate(\n verification.candidate,\n this.validateArtifactRef,\n );\n if (invalid !== undefined)\n return blocked(`Invalid release candidate: ${invalid}`);\n if (verification.environment !== \"staging\")\n return blocked(\"Verification is not for staging\");\n const key = this.stateKey(verification.candidate);\n const state = this.store.get(key);\n if (state === undefined)\n return blocked(\"Staging deployment was not requested\");\n if (\n state.staging === undefined ||\n isIntentDeploymentId(state.staging.deploymentId)\n ) {\n return blocked(\n \"Staging deployment request has not been confirmed\",\n state,\n );\n }\n if (state.staging?.deploymentId !== verification.deploymentId) {\n return blocked(\n \"Staging verification references a different deployment\",\n state,\n );\n }\n if (state.status === \"production-failed\") {\n return blocked(\n \"Production verification failed; recovery is required before staging can be recorded\",\n state,\n );\n }\n if (!sameCandidate(state.candidate, verification.candidate)) {\n return blocked(\n \"Staging verification references a different candidate\",\n state,\n );\n }\n const reasons = verificationReasons(this.policy, verification);\n const updated: ReleaseState = {\n ...state,\n status: reasons.length === 0 ? \"staging-verified\" : \"staging-failed\",\n staging: structuredClone(verification),\n updatedAt: this.now(),\n };\n this.store.save(updated);\n return reasons.length === 0\n ? { outcome: \"verified\", state: updated }\n : { outcome: \"failed\", reason: reasons.join(\"; \"), state: updated };\n }\n\n approveProduction(input: {\n readonly candidate: ReleaseCandidate;\n readonly approval: ProductionApproval;\n }): ReleaseOperationResult {\n const invalid = validCandidate(input.candidate, this.validateArtifactRef);\n if (invalid !== undefined)\n return blocked(`Invalid release candidate: ${invalid}`);\n const key = this.stateKey(input.candidate);\n const state = this.store.get(key);\n if (state === undefined)\n return blocked(\"Candidate has no staging release state\");\n if (state.status !== \"staging-verified\") {\n if (state.status === \"production-approved\") {\n return { outcome: \"duplicate\", state };\n }\n return blocked(\"Production approval requires verified staging\", state);\n }\n if (!this.policy.requireHumanApproval) {\n return blocked(\n \"Production approval policy is not configured for a human gate\",\n state,\n );\n }\n if (!sameCandidate(input.candidate, input.approval.candidate)) {\n return blocked(\n \"Production approval does not match the exact candidate\",\n state,\n );\n }\n if (input.approval.actor.id.trim().length === 0) {\n return blocked(\"Production approval actor is missing\", state);\n }\n const actorRole = input.approval.actor.role;\n if (actorRole === \"release-coordinator\") {\n return blocked(\n \"Production approval requires an owner or maintainer\",\n state,\n );\n }\n if (!approvalRoles(this.policy).includes(actorRole)) {\n return blocked(\n \"Production approval role is not allowed by release policy\",\n state,\n );\n }\n if (input.approval.approvedAt.trim().length === 0) {\n return blocked(\"Production approval timestamp is missing\", state);\n }\n const approvalFreshnessSeconds =\n this.policy.approvalFreshnessSeconds ?? 300;\n if (\n !Number.isFinite(approvalFreshnessSeconds) ||\n approvalFreshnessSeconds <= 0\n ) {\n return blocked(\"Production approval freshness policy is invalid\", state);\n }\n if (\n !isFreshTimestamp(\n input.approval.approvedAt,\n this.now(),\n approvalFreshnessSeconds,\n )\n ) {\n return blocked(\"Production approval evidence is not fresh\", state);\n }\n const updated: ReleaseState = {\n ...state,\n status: \"production-approved\",\n approval: structuredClone(input.approval),\n updatedAt: this.now(),\n };\n this.store.save(updated);\n return { outcome: \"approved\", state: updated };\n }\n\n async promoteProduction(\n candidate: ReleaseCandidate,\n ): Promise<ReleaseOperationResult> {\n const invalid = validCandidate(candidate, this.validateArtifactRef);\n if (invalid !== undefined)\n return blocked(`Invalid release candidate: ${invalid}`);\n const key = this.stateKey(candidate);\n const state = this.store.get(key);\n if (state === undefined) return blocked(\"Candidate has no release state\");\n const idempotencyKey = `shipyard:${key}:production`;\n const intentId = `intent:${idempotencyKey}`;\n if (state.status === \"production-verified\") {\n return { outcome: \"duplicate\", state };\n }\n const pendingIntent =\n state.status === \"production-requested\" &&\n state.production?.deploymentId === intentId;\n if (state.status === \"production-requested\" && !pendingIntent) {\n return { outcome: \"duplicate\", state };\n }\n if (state.status === \"production-failed\") {\n return blocked(\n \"Production verification failed; recovery decision is required\",\n state,\n );\n }\n if (\n !pendingIntent &&\n (state.status !== \"production-approved\" || state.approval === undefined)\n ) {\n return blocked(\n \"Production promotion requires exact human approval after staging\",\n state,\n );\n }\n if (\n state.approval === undefined ||\n !sameCandidate(candidate, state.approval.candidate)\n ) {\n return blocked(\"Production approval is stale for this candidate\", state);\n }\n const approvalFreshnessSeconds =\n this.policy.approvalFreshnessSeconds ?? 300;\n if (\n !Number.isFinite(approvalFreshnessSeconds) ||\n approvalFreshnessSeconds <= 0\n ) {\n return blocked(\"Production approval freshness policy is invalid\", state);\n }\n if (\n !isFreshTimestamp(\n state.approval.approvedAt,\n this.now(),\n approvalFreshnessSeconds,\n )\n ) {\n return blocked(\"Production approval evidence is not fresh\", state);\n }\n const intent = pendingIntent\n ? state\n : {\n ...state,\n status: \"production-requested\" as const,\n production: {\n environment: \"production\" as const,\n candidate,\n deploymentId: intentId,\n checks: [],\n smoke: \"unknown\" as const,\n health: \"unknown\" as const,\n recordedAt: this.now(),\n },\n updatedAt: this.now(),\n };\n if (!pendingIntent) this.store.save(intent);\n const deployment = await this.requestDeployment(\n \"production\",\n candidate,\n idempotencyKey,\n );\n if (deployment === undefined) {\n return {\n outcome: \"failed\",\n reason: \"Production deployment could not be requested\",\n state: this.store.get(key),\n };\n }\n const updated: ReleaseState = {\n ...intent,\n production: {\n ...intent.production!,\n deploymentId: deployment.deploymentId,\n recordedAt: this.now(),\n },\n updatedAt: this.now(),\n };\n this.store.save(updated);\n return { outcome: \"requested\", state: updated };\n }\n\n private async requestDeployment(\n environment: ReleaseEnvironment,\n candidate: ReleaseCandidate,\n idempotencyKey: string,\n ): Promise<{ readonly deploymentId: string } | undefined> {\n const environmentName =\n environment === \"staging\"\n ? this.policy.stagingName\n : this.policy.productionName;\n let reconciled: { readonly deploymentId: string } | undefined;\n try {\n reconciled = this.transport.reconcileDeployment\n ? await this.transport.reconcileDeployment({\n environment,\n environmentName,\n candidate,\n idempotencyKey,\n })\n : undefined;\n } catch {\n reconciled = undefined;\n }\n if (reconciled !== undefined) {\n return reconciled.deploymentId.trim().length > 0 ? reconciled : undefined;\n }\n try {\n const deployment = await this.transport.requestDeployment({\n environment,\n environmentName,\n candidate,\n actor: this.actor,\n idempotencyKey,\n });\n if (deployment.deploymentId.trim().length === 0) {\n return undefined;\n }\n return deployment;\n } catch {\n return undefined;\n }\n }\n\n recordProductionVerification(\n verification: ReleaseVerification,\n ): ReleaseOperationResult {\n const invalid = validCandidate(\n verification.candidate,\n this.validateArtifactRef,\n );\n if (invalid !== undefined)\n return blocked(`Invalid release candidate: ${invalid}`);\n if (verification.environment !== \"production\")\n return blocked(\"Verification is not for production\");\n const key = this.stateKey(verification.candidate);\n const state = this.store.get(key);\n if (state === undefined || state.production === undefined) {\n return blocked(\"Production deployment was not requested\");\n }\n if (isIntentDeploymentId(state.production.deploymentId)) {\n return blocked(\n \"Production deployment request has not been confirmed\",\n state,\n );\n }\n if (state.status === \"production-failed\") {\n return blocked(\n \"Production verification already failed; record recovery before any new candidate\",\n state,\n );\n }\n if (\n state.production.deploymentId !== verification.deploymentId ||\n !sameCandidate(state.candidate, verification.candidate)\n ) {\n return blocked(\n \"Production verification references a different deployment or candidate\",\n state,\n );\n }\n const failed =\n requiredChecksPassed(this.policy, verification.checks).length > 0 ||\n verification.smoke !== \"passed\" ||\n verification.health !== \"passed\" ||\n verification.checks.some((check) => check.status !== \"passed\");\n const updated: ReleaseState = {\n ...state,\n status: failed ? \"production-failed\" : \"production-verified\",\n production: structuredClone(verification),\n updatedAt: this.now(),\n };\n this.store.save(updated);\n return failed\n ? {\n outcome: \"failed\",\n reason:\n verification.failureReason ?? \"Production verification failed\",\n state: updated,\n }\n : { outcome: \"verified\", state: updated };\n }\n\n recordRecovery(input: {\n readonly candidate: ReleaseCandidate;\n readonly reason: string;\n readonly authorizedBy: ReleaseActor;\n }): RecoveryRecord {\n const key = this.stateKey(input.candidate);\n const state = this.store.get(key);\n if (state === undefined || state.status !== \"production-failed\") {\n throw new Error(\n \"Recovery can only be recorded for a failed production candidate\",\n );\n }\n if (input.reason.trim().length === 0) {\n throw new Error(\"Recovery reason is required\");\n }\n if (\n input.authorizedBy.id.trim().length === 0 ||\n input.authorizedBy.role === \"release-coordinator\" ||\n !approvalRoles(this.policy).includes(input.authorizedBy.role)\n ) {\n throw new Error(\"Recovery requires an authorized owner or maintainer\");\n }\n const recovery: RecoveryRecord = {\n candidate: structuredClone(input.candidate),\n action: this.policy.recoveryMode,\n reason: input.reason,\n authorizedBy: input.authorizedBy,\n recordedAt: this.now(),\n };\n this.store.save({ ...state, recovery, updatedAt: this.now() });\n return recovery;\n }\n}\n\nexport const candidateMatchesApproval = (\n candidate: ReleaseCandidate,\n approval: ProductionApproval,\n): boolean => sameCandidate(candidate, approval.candidate);\n"]}