@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55

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 (159) hide show
  1. package/README.md +9 -6
  2. package/actions/release-tail/README.md +8 -0
  3. package/actions/v4-release-candidate-promote/README.md +3 -0
  4. package/architecture/agent-change-map.md +1015 -0
  5. package/architecture/ci-lane-change-budget.json +256 -0
  6. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  7. package/architecture/internal-capabilities.json +220 -15
  8. package/architecture/maintainability-debt.json +3229 -0
  9. package/architecture/maintainability-policy.json +489 -132
  10. package/architecture/release-tail-contract-inventory.json +5 -2
  11. package/architecture/v3-core-mechanism-inventory.json +4 -3
  12. package/architecture/v3-v4-capability-closure.md +59 -0
  13. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  14. package/architecture/v4-adopter-delivery-parity.json +32 -5
  15. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  16. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  17. package/architecture/v4-floating-consumer-policy.json +6 -0
  18. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  19. package/architecture/v4-runtime-semantic-closure.json +710 -0
  20. package/bin/buildchain.mjs +7 -1
  21. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  22. package/bin/internal/command-registry.mjs +1 -0
  23. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  24. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  25. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  26. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  29. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  30. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  31. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  32. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  33. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  38. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  39. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  40. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  41. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  42. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  43. package/dist/site/agent-index.json +4 -0
  44. package/dist/site/artifact-schemas.json +8 -0
  45. package/dist/site/buildchain-contract.json +125 -34
  46. package/dist/site/buildchain-site.json +308 -33
  47. package/dist/site/capability-registry.json +13 -12
  48. package/dist/site/cli-registry.json +57 -2
  49. package/dist/site/controller-registry.json +41 -5
  50. package/dist/site/kfd-claims.json +438 -24
  51. package/dist/site/kfd-upstream-aggregate.json +1 -1
  52. package/dist/site/manual-registry.json +22 -7
  53. package/dist/site/node-api-registry.json +12843 -9314
  54. package/dist/site/page-registry.json +288 -21
  55. package/dist/site/public-surface-audit.json +281 -21
  56. package/dist/site/publication-authority-registry.json +66 -8
  57. package/dist/site/publication-registry.json +4 -4
  58. package/dist/site/release-provenance.json +20 -0
  59. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  60. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  61. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  62. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  63. package/dist/site/site-manifest.json +19 -11
  64. package/dist/site/workflow-registry.json +195 -17
  65. package/docs/MAP.md +4 -2
  66. package/docs/cli-reference.md +70 -2
  67. package/docs/cli.md +8 -0
  68. package/docs/dev-delivery-warrant.md +34 -10
  69. package/docs/node-api-reference.md +524 -279
  70. package/docs/publish-transaction.md +11 -4
  71. package/docs/release-candidate.md +7 -0
  72. package/docs/release-tail-provider-plane.md +30 -0
  73. package/docs/v4-adopter-delivery.md +152 -0
  74. package/docs/v4-declarative-release-tail-migration.md +43 -0
  75. package/docs/v4-publication-rehearsal.md +130 -0
  76. package/package.json +24 -4
  77. package/packages/core/adopter-delivery-passport.js +295 -0
  78. package/packages/core/adopter-delivery-vectors.js +158 -0
  79. package/packages/core/adopter-delivery-vectors.json +77 -0
  80. package/packages/core/buildchain-agent-manuals.js +1 -0
  81. package/packages/core/buildchain-config.js +84 -0
  82. package/packages/core/buildchain-contract.js +31 -1
  83. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  84. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  85. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  86. package/packages/core/buildchain-publication-authority.js +2 -0
  87. package/packages/core/ci-lane-change-budget.js +247 -0
  88. package/packages/core/dev-delivery-native-proof.js +43 -27
  89. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  90. package/packages/core/dev-delivery-warrant-state.js +46 -10
  91. package/packages/core/dev-delivery-warrant.js +29 -7
  92. package/packages/core/github-governance-authority.js +5 -4
  93. package/packages/core/index.js +6 -0
  94. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  95. package/packages/core/paper-scaffold-content.js +401 -0
  96. package/packages/core/paper.js +12 -378
  97. package/packages/core/publication-control-plane-audit.js +135 -111
  98. package/packages/core/publication-rehearsal-projection.js +104 -0
  99. package/packages/core/publication-rehearsal-runtime.js +190 -0
  100. package/packages/core/published-delivery-authority.js +266 -0
  101. package/packages/core/release-candidate-recovery.js +71 -15
  102. package/packages/core/release-candidate.js +2 -2
  103. package/packages/core/release-passport.js +95 -170
  104. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  105. package/packages/core/v4-adopter-delivery.js +359 -0
  106. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  107. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  108. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  109. package/packages/core/v4-floating-consumer-policy.js +139 -35
  110. package/packages/core/v4-floating-consumer-release-passport.js +154 -8
  111. package/packages/core/v4-publication-qualification.js +222 -0
  112. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  113. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  114. package/packages/core/v4-publication-rehearsal.js +481 -0
  115. package/packages/core/workflow-yaml-contract.js +24 -2
  116. package/scripts/artifact-signing-controller-core.mjs +1 -1
  117. package/scripts/audit-publication-control-plane.mjs +17 -17
  118. package/scripts/buildchain-cli-help.mjs +13 -0
  119. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  120. package/scripts/check-internal-architecture.mjs +23 -3
  121. package/scripts/check-inventory.mjs +24 -6
  122. package/scripts/check-maintainability.mjs +145 -4
  123. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  124. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  125. package/scripts/check-v4-floating-consumer-policy-contract.mjs +30 -4
  126. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  127. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  128. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  129. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  130. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  131. package/scripts/dev-delivery-warrant.mjs +50 -6
  132. package/scripts/dev-pr-auto-merge.mjs +25 -11
  133. package/scripts/dev-pr-delivery-warrant.mjs +45 -0
  134. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  135. package/scripts/dev-qualification-patrol.mjs +9 -9
  136. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  137. package/scripts/generate-agent-change-map.mjs +118 -0
  138. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  139. package/scripts/generate-release-candidate-passport.mjs +177 -0
  140. package/scripts/generate-site-bundle.mjs +36 -0
  141. package/scripts/maintainability-domain-metrics.mjs +237 -0
  142. package/scripts/maintainability-governance.mjs +473 -0
  143. package/scripts/maintainability-metrics.mjs +28 -6
  144. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  145. package/scripts/promotion-channel-router.mjs +4 -4
  146. package/scripts/promotion-routing-evidence.mjs +35 -6
  147. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  148. package/scripts/release-candidate-resolver.mjs +44 -0
  149. package/scripts/release-line-policy.mjs +25 -15
  150. package/scripts/release-tail.mjs +68 -3
  151. package/scripts/resume-from-candidate-run.mjs +595 -65
  152. package/scripts/run-lifecycle-core.mjs +341 -376
  153. package/scripts/site-capability-metadata.mjs +21 -0
  154. package/scripts/stable-candidate-qualification.mjs +9 -10
  155. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  156. package/scripts/v4-consumer-policy.mjs +4 -0
  157. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  158. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
  159. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
@@ -72,77 +72,72 @@ function fact(id, pass, observed) {
72
72
  };
73
73
  }
74
74
 
75
- export function evaluatePublicationControlPlaneSnapshot({
75
+ function evaluatePublisherPolicy({
76
+ publisher,
77
+ publisherMode,
78
+ packageName,
76
79
  repository,
77
80
  workflowPath,
78
- publisherWorkflowPath = workflowPath,
81
+ publisherWorkflowPath,
82
+ workflowFilename,
79
83
  environment,
80
- branch,
81
- packageName,
82
- publisherMode = "npm-trusted-publisher",
83
- requiredStatusCheck = "check",
84
- snapshot,
85
- observedAt,
86
- expiresAt,
87
- } = {}) {
88
- const workflowFilename = String(publisherWorkflowPath || "").split("/").pop();
89
- const providerEnvironment = environment === "none" ? "" : environment;
90
- const actions = snapshot?.actions || {};
91
- const branchPolicy = snapshot?.branch || {};
92
- const environmentPolicy = snapshot?.environment || {};
93
- const oidc = snapshot?.oidc || {};
94
- const publisher = snapshot?.publisher || {};
95
- const runner = snapshot?.runner || {};
96
- const npmIdentityPass = publisher.packageName === packageName &&
97
- publisher.provider === "github" &&
98
- publisher.repository === repository &&
99
- publisher.workflowFilename === workflowFilename &&
100
- publisher.environment === providerEnvironment &&
101
- publisher.longLivedWorkflowCredentialPresent === false;
102
- const publisherPass = publisherMode === "npm-trusted-publisher"
103
- ? npmIdentityPass && (
84
+ providerEnvironment,
85
+ }) {
86
+ if (publisherMode === "npm-trusted-publisher") {
87
+ const identityPass = publisher.packageName === packageName &&
88
+ publisher.provider === "github" &&
89
+ publisher.repository === repository &&
90
+ publisher.workflowFilename === workflowFilename &&
91
+ publisher.environment === providerEnvironment &&
92
+ publisher.longLivedWorkflowCredentialPresent === false;
93
+ return identityPass && (
104
94
  (publisher.enforcement === "audited-control-plane" && publisher.allowPublish === true) ||
105
95
  (publisher.enforcement === "provider-at-transaction" &&
106
96
  publisher.authorizationDeferred === true &&
107
97
  publisher.configurationRead === false)
108
- )
109
- : publisherMode === "github-token"
110
- ? publisher.provider === "github-token" &&
111
- publisher.repository === repository &&
112
- publisher.workflowPath === workflowPath &&
113
- publisher.permissionScoped === true &&
114
- publisher.longLivedWorkflowCredentialPresent === false
115
- : publisherMode === "oidc-role"
116
- ? publisher.provider !== "" &&
117
- publisher.repository === repository &&
118
- publisher.workflowPath === publisherWorkflowPath &&
119
- publisher.environment === environment &&
120
- publisher.trustQualifying === true &&
121
- /^[0-9a-f]{64}$/i.test(String(publisher.roleDigest || "").replace(/^sha256:/, "")) &&
122
- publisher.longLivedWorkflowCredentialPresent === false
123
- : false;
124
- const credentialIsolationPass = publisherMode === "github-token"
98
+ );
99
+ }
100
+ if (publisherMode === "github-token") {
101
+ return publisher.provider === "github-token" &&
102
+ publisher.repository === repository &&
103
+ publisher.workflowPath === workflowPath &&
104
+ publisher.permissionScoped === true &&
105
+ publisher.longLivedWorkflowCredentialPresent === false;
106
+ }
107
+ if (publisherMode !== "oidc-role") return false;
108
+ return publisher.provider !== "" &&
109
+ publisher.repository === repository &&
110
+ publisher.workflowPath === publisherWorkflowPath &&
111
+ publisher.environment === environment &&
112
+ publisher.trustQualifying === true &&
113
+ /^[0-9a-f]{64}$/i.test(String(publisher.roleDigest || "").replace(/^sha256:/, "")) &&
114
+ publisher.longLivedWorkflowCredentialPresent === false;
115
+ }
116
+
117
+ function evaluateCredentialIsolation({ oidc, publisherMode, publisherWorkflowPath, providerEnvironment }) {
118
+ return publisherMode === "github-token"
125
119
  ? oidc.githubTokenJobScoped === true && oidc.longLivedCredentialPresent === false
126
120
  : oidc.workflowPath === publisherWorkflowPath &&
127
121
  oidc.environment === providerEnvironment &&
128
122
  oidc.idTokenJobScoped === true &&
129
123
  oidc.longLivedCredentialPresent === false;
130
- const configuredBranchPolicyPass = branchPolicy.ref === branch &&
131
- branchPolicy.strict === true &&
132
- Number(branchPolicy.requiredApprovals || 0) >= 1 &&
133
- branchPolicy.requireConversationResolution === true &&
134
- branchPolicy.enforceAdmins === true;
135
- const declaredRequiredStatusCheck = branchPolicy.declaredRequiredStatusCheck || branchPolicy.requiredStatusCheck;
136
- const resolvedRequiredStatusCheck = branchPolicy.requiredStatusCheck;
137
- const requiredStatusCheckBindingPass = declaredRequiredStatusCheck === requiredStatusCheck && (
138
- resolvedRequiredStatusCheck === declaredRequiredStatusCheck ||
139
- (
140
- resolvedRequiredStatusCheck.startsWith(`${declaredRequiredStatusCheck} / `) &&
141
- branchPolicy.requiredStatusCheckMatchCount === 1
142
- )
143
- );
124
+ }
125
+
126
+ function requiredStatusCheckBinding(branchPolicy, requiredStatusCheck) {
127
+ const declared = branchPolicy.declaredRequiredStatusCheck || branchPolicy.requiredStatusCheck;
128
+ const resolved = branchPolicy.requiredStatusCheck;
129
+ return {
130
+ resolved,
131
+ pass: declared === requiredStatusCheck && (
132
+ resolved === declared ||
133
+ (resolved.startsWith(`${declared} / `) && branchPolicy.requiredStatusCheckMatchCount === 1)
134
+ ),
135
+ };
136
+ }
137
+
138
+ function sourceAuthorizationPass(branchPolicy) {
144
139
  const authorizationSha = branchPolicy.authorizationSha || branchPolicy.sourceSha;
145
- const sourceAuthorizationPass = branchPolicy.sourceSha === authorizationSha || (
140
+ return branchPolicy.sourceSha === authorizationSha || (
146
141
  branchPolicy.releaseReconciliation?.qualifying === true &&
147
142
  branchPolicy.releaseReconciliation.parentSha === authorizationSha &&
148
143
  /^\d+\.\d+\.\d+$/.test(String(branchPolicy.releaseReconciliation.version || "")) &&
@@ -150,74 +145,103 @@ export function evaluatePublicationControlPlaneSnapshot({
150
145
  Array.isArray(branchPolicy.releaseReconciliation.changedPaths) &&
151
146
  branchPolicy.releaseReconciliation.changedPaths.length > 0
152
147
  );
153
- const sourceBranchBindingPass = branchPolicy.sourceSha === branchPolicy.headSha ||
154
- branchPolicy.sourceContainedInBranch === true;
155
- const providerTransactionBranchPass = branchPolicy.ref === branch &&
148
+ }
149
+
150
+ function evaluateConfiguredBranchPolicy(branchPolicy, branch) {
151
+ return branchPolicy.ref === branch &&
152
+ branchPolicy.strict === true &&
153
+ Number(branchPolicy.requiredApprovals || 0) >= 1 &&
154
+ branchPolicy.requireConversationResolution === true &&
155
+ branchPolicy.enforceAdmins === true;
156
+ }
157
+
158
+ function evaluateProviderBranchPolicy(branchPolicy, branch, requiredCheck) {
159
+ return branchPolicy.ref === branch &&
156
160
  branchPolicy.policyMode === "provider-enforced-transaction" &&
157
161
  branchPolicy.protected === true &&
158
162
  branchPolicy.enforcementLevel === "everyone" &&
159
163
  Array.isArray(branchPolicy.requiredStatusChecks) &&
160
- requiredStatusCheckBindingPass &&
161
- branchPolicy.requiredStatusChecks.includes(resolvedRequiredStatusCheck) &&
164
+ requiredCheck.pass &&
165
+ branchPolicy.requiredStatusChecks.includes(requiredCheck.resolved) &&
162
166
  branchPolicy.requiredCheckPassed === true &&
163
167
  /^[0-9a-f]{40}$/i.test(String(branchPolicy.requiredCheckSha || "")) &&
164
- branchPolicy.requiredCheckSha === branchPolicy.pullRequestHeadSha &&
165
- sourceBranchBindingPass &&
166
- sourceAuthorizationPass &&
168
+ branchPolicy.requiredCheckSha === branchPolicy.pullRequestHeadSha;
169
+ }
170
+
171
+ function evaluateProviderPullRequest(branchPolicy, branch, repository) {
172
+ return (branchPolicy.sourceSha === branchPolicy.headSha || branchPolicy.sourceContainedInBranch === true) &&
173
+ sourceAuthorizationPass(branchPolicy) &&
167
174
  branchPolicy.mergedPullRequest === true &&
168
175
  branchPolicy.baseRef === branch &&
169
176
  branchPolicy.headRepository === repository &&
170
177
  Number(branchPolicy.approvalCount || 0) >= 1 &&
171
178
  branchPolicy.independentApproval === true;
172
- const facts = [
173
- fact(
174
- "actions-policy",
175
- actions.defaultWorkflowPermissions === "read" && actions.canApprovePullRequestReviews === false,
176
- actions,
177
- ),
178
- fact(
179
- "branch-policy",
180
- configuredBranchPolicyPass || providerTransactionBranchPass,
181
- branchPolicy,
182
- ),
183
- fact(
184
- "environment-policy",
185
- environment === "none"
186
- ? environmentPolicy.declared === false && environmentPolicy.exists === false
187
- : environmentPolicy.name === environment &&
188
- environmentPolicy.declared === true &&
189
- environmentPolicy.exists === true &&
190
- environmentPolicy.protected === true &&
191
- environmentPolicy.branchAuthorized === true &&
192
- (environmentPolicy.reviewRequired !== true || environmentPolicy.preventSelfReview === true),
193
- environmentPolicy,
194
- ),
195
- fact(
196
- "oidc-policy",
197
- credentialIsolationPass,
198
- oidc,
199
- ),
200
- fact(
201
- "publisher-policy",
202
- publisherPass,
203
- publisher,
204
- ),
205
- fact(
206
- "runner-policy",
207
- runner.class === "ephemeral" &&
208
- runner.label === "ubuntu-24.04" &&
209
- runner.githubHosted === true &&
210
- runner.selfHostedAuthorized === false,
211
- runner,
212
- ),
213
- ];
214
- return createPublicationControlPlaneAudit({
179
+ }
180
+
181
+ function evaluateBranchPolicy({ branchPolicy, branch, repository, requiredStatusCheck }) {
182
+ const requiredCheck = requiredStatusCheckBinding(branchPolicy, requiredStatusCheck);
183
+ return evaluateConfiguredBranchPolicy(branchPolicy, branch) || (
184
+ evaluateProviderBranchPolicy(branchPolicy, branch, requiredCheck) &&
185
+ evaluateProviderPullRequest(branchPolicy, branch, repository)
186
+ );
187
+ }
188
+
189
+ function evaluateEnvironmentPolicy(environmentPolicy, environment) {
190
+ return environment === "none"
191
+ ? environmentPolicy.declared === false && environmentPolicy.exists === false
192
+ : environmentPolicy.name === environment &&
193
+ environmentPolicy.declared === true &&
194
+ environmentPolicy.exists === true &&
195
+ environmentPolicy.protected === true &&
196
+ environmentPolicy.branchAuthorized === true &&
197
+ (environmentPolicy.reviewRequired !== true || environmentPolicy.preventSelfReview === true);
198
+ }
199
+
200
+ function evaluateRunnerPolicy(runner) {
201
+ return runner.class === "ephemeral" &&
202
+ runner.label === "ubuntu-24.04" &&
203
+ runner.githubHosted === true &&
204
+ runner.selfHostedAuthorized === false;
205
+ }
206
+
207
+ function evaluatePublicationControlPlaneSnapshotOptions(options) {
208
+ const {
215
209
  repository,
216
210
  workflowPath,
217
- publisherWorkflowPath,
211
+ publisherWorkflowPath = workflowPath,
218
212
  environment,
219
- facts,
213
+ branch,
214
+ packageName,
215
+ publisherMode = "npm-trusted-publisher",
216
+ requiredStatusCheck = "check",
217
+ snapshot,
220
218
  observedAt,
221
219
  expiresAt,
220
+ } = options;
221
+ const workflowFilename = String(publisherWorkflowPath || "").split("/").pop();
222
+ const providerEnvironment = environment === "none" ? "" : environment;
223
+ const actions = snapshot?.actions || {};
224
+ const branchPolicy = snapshot?.branch || {};
225
+ const environmentPolicy = snapshot?.environment || {};
226
+ const oidc = snapshot?.oidc || {};
227
+ const publisher = snapshot?.publisher || {};
228
+ const runner = snapshot?.runner || {};
229
+ const facts = [
230
+ fact("actions-policy", actions.defaultWorkflowPermissions === "read" && actions.canApprovePullRequestReviews === false, actions),
231
+ fact("branch-policy", evaluateBranchPolicy({ branchPolicy, branch, repository, requiredStatusCheck }), branchPolicy),
232
+ fact("environment-policy", evaluateEnvironmentPolicy(environmentPolicy, environment), environmentPolicy),
233
+ fact("oidc-policy", evaluateCredentialIsolation({ oidc, publisherMode, publisherWorkflowPath, providerEnvironment }), oidc),
234
+ fact("publisher-policy", evaluatePublisherPolicy({
235
+ publisher, publisherMode, packageName, repository, workflowPath,
236
+ publisherWorkflowPath, workflowFilename, environment, providerEnvironment,
237
+ }), publisher),
238
+ fact("runner-policy", evaluateRunnerPolicy(runner), runner),
239
+ ];
240
+ return createPublicationControlPlaneAudit({
241
+ repository, workflowPath, publisherWorkflowPath, environment, facts, observedAt, expiresAt,
222
242
  });
223
243
  }
244
+
245
+ export function evaluatePublicationControlPlaneSnapshot({ repository, workflowPath, publisherWorkflowPath = workflowPath, environment, branch, packageName, publisherMode = "npm-trusted-publisher", requiredStatusCheck = "check", snapshot, observedAt, expiresAt, } = {}) {
246
+ return evaluatePublicationControlPlaneSnapshotOptions({ repository, workflowPath, publisherWorkflowPath, environment, branch, packageName, publisherMode, requiredStatusCheck, snapshot, observedAt, expiresAt });
247
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
3
+ PUBLICATION_REHEARSAL_COMMAND,
4
+ RELEASE_LOCAL_CONSTRUCTIBILITY_ADR,
5
+ RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT,
6
+ } from "./publication-rehearsal-runtime.js";
7
+
8
+ export const PUBLICATION_REHEARSAL_AGENT_SECTION_START =
9
+ "<!-- buildchain:publication-rehearsal:v4:start -->";
10
+ export const PUBLICATION_REHEARSAL_AGENT_SECTION_END =
11
+ "<!-- buildchain:publication-rehearsal:v4:end -->";
12
+ export const PUBLICATION_REHEARSAL_WORKFLOW_PATH =
13
+ ".github/workflows/publication-rehearsal.yml";
14
+
15
+ export function publicationRehearsalToml() {
16
+ return `[publication_rehearsal]\ncontract = ${JSON.stringify(PUBLICATION_REHEARSAL_CAPSULE_CONTRACT)}\nadr = ${JSON.stringify(RELEASE_LOCAL_CONSTRUCTIBILITY_ADR)}\ninvariant = ${JSON.stringify(RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT)}\ncommand = ${JSON.stringify(PUBLICATION_REHEARSAL_COMMAND)}\ncapsule = ".buildchain/publication-rehearsal/capsule.json"\ncandidate_root = ".buildchain/publication-rehearsal/candidate"\nstate = ".buildchain/publication-rehearsal/state.json"\nevidence = ".buildchain/publication-rehearsal/evidence.json"\n`;
17
+ }
18
+
19
+ export function assertPublicationRehearsalConfig(config) {
20
+ const section = config?.publication_rehearsal;
21
+ if (!section || typeof section !== "object" || Array.isArray(section))
22
+ throw new Error("publication_rehearsal table is missing");
23
+ const expected = {
24
+ contract: PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
25
+ adr: RELEASE_LOCAL_CONSTRUCTIBILITY_ADR,
26
+ invariant: RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT,
27
+ command: PUBLICATION_REHEARSAL_COMMAND,
28
+ capsule: ".buildchain/publication-rehearsal/capsule.json",
29
+ candidate_root: ".buildchain/publication-rehearsal/candidate",
30
+ state: ".buildchain/publication-rehearsal/state.json",
31
+ evidence: ".buildchain/publication-rehearsal/evidence.json",
32
+ };
33
+ if (
34
+ JSON.stringify(Object.keys(section).sort()) !==
35
+ JSON.stringify(Object.keys(expected).sort())
36
+ )
37
+ throw new Error(
38
+ `publication_rehearsal fields must be exactly: ${Object.keys(expected).sort().join(", ")}`,
39
+ );
40
+ for (const [key, value] of Object.entries(expected))
41
+ if (section[key] !== value)
42
+ throw new Error(`publication_rehearsal.${key} is stale or unsupported`);
43
+ return structuredClone(section);
44
+ }
45
+
46
+ export function appendPublicationRehearsalToml(source) {
47
+ const current = String(source || "").trimEnd();
48
+ if (/^\[publication_rehearsal\]$/mu.test(current))
49
+ throw new Error(
50
+ "buildchain config already contains a publication_rehearsal table",
51
+ );
52
+ return `${current}\n\n${publicationRehearsalToml()}`;
53
+ }
54
+
55
+ export function projectPublicationRehearsalToml(source) {
56
+ const current = String(source || "");
57
+ const start = current.search(/^\[publication_rehearsal\]\s*$/mu);
58
+ if (start === -1) return appendPublicationRehearsalToml(current);
59
+ const remainder = current.slice(start);
60
+ const newline = remainder.indexOf("\n");
61
+ const nextTable = remainder.slice(newline + 1).search(/^\[[^\n]+\]\s*$/mu);
62
+ const end =
63
+ nextTable === -1 ? current.length : start + newline + 1 + nextTable;
64
+ const prefix = current.slice(0, start).trimEnd();
65
+ const suffix = current.slice(end).trimStart();
66
+ return `${prefix}\n\n${publicationRehearsalToml()}${suffix ? `\n${suffix}` : ""}`;
67
+ }
68
+
69
+ export function publicationRehearsalAgentInstructions() {
70
+ return `${PUBLICATION_REHEARSAL_AGENT_SECTION_START}\n## Publication rehearsal (required)\n\nBuildchain v4 rehearsal follows \`${RELEASE_LOCAL_CONSTRUCTIBILITY_ADR}\`.\n${RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT}\n\nRun the source-bound capsule with:\n\n\`\`\`sh\n${PUBLICATION_REHEARSAL_COMMAND}\n\`\`\`\n\nSimulation and replay evidence never claim production publication authority.\n${PUBLICATION_REHEARSAL_AGENT_SECTION_END}`;
71
+ }
72
+
73
+ export function mergePublicationRehearsalAgentInstructions(current = "") {
74
+ const source = String(current || "");
75
+ const section = publicationRehearsalAgentInstructions();
76
+ const start = source.indexOf(PUBLICATION_REHEARSAL_AGENT_SECTION_START);
77
+ const end = source.indexOf(PUBLICATION_REHEARSAL_AGENT_SECTION_END);
78
+ if ((start === -1) !== (end === -1) || end < start)
79
+ throw new Error(
80
+ "AGENTS.md has an incomplete Buildchain publication rehearsal section",
81
+ );
82
+ if (start === -1)
83
+ return source.trim()
84
+ ? `${source.trimEnd()}\n\n${section}\n`
85
+ : `# AGENTS.md\n\n${section}\n`;
86
+ if (
87
+ source.indexOf(PUBLICATION_REHEARSAL_AGENT_SECTION_START, start + 1) !==
88
+ -1 ||
89
+ source.indexOf(PUBLICATION_REHEARSAL_AGENT_SECTION_END, end + 1) !== -1
90
+ )
91
+ throw new Error(
92
+ "AGENTS.md has ambiguous Buildchain publication rehearsal sections",
93
+ );
94
+ return `${source.slice(0, start)}${section}${source.slice(
95
+ end + PUBLICATION_REHEARSAL_AGENT_SECTION_END.length,
96
+ )}`;
97
+ }
98
+
99
+ export function publicationRehearsalWorkflow(buildchainRef) {
100
+ const ref = String(buildchainRef || "").trim();
101
+ if (!ref)
102
+ throw new Error("publication rehearsal workflow requires a Buildchain ref");
103
+ return `name: Publication Rehearsal\n\non:\n workflow_dispatch:\n inputs:\n capsule-path:\n description: "Source-bound v4 publication rehearsal capsule"\n required: true\n default: ".buildchain/publication-rehearsal/capsule.json"\n candidate-root:\n description: "Repository-relative candidate root"\n required: true\n default: ".buildchain/publication-rehearsal/candidate"\n\npermissions:\n contents: read\n\njobs:\n rehearsal:\n uses: kungfu-systems/buildchain/.github/workflows/release-tail.yml@${ref}\n with:\n buildchain-ref: ${ref}\n rehearsal-capsule-path: \${{ inputs.capsule-path }}\n candidate-root: \${{ inputs.candidate-root }}\n rehearsal-mode: simulate\n state-path: ".buildchain/publication-rehearsal/state.json"\n rehearsal-evidence-path: ".buildchain/publication-rehearsal/evidence.json"\n`;
104
+ }
@@ -0,0 +1,190 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { releaseTailRoot } from "./release-tail-provider-plane.js";
5
+ import {
6
+ V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
7
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
8
+ createV4PublicationRehearsalCapsule,
9
+ executeV4PublicationRehearsal,
10
+ validateV4PublicationRehearsalCapsule,
11
+ verifyV4PublicationRehearsalCapsule,
12
+ } from "./v4-publication-rehearsal.js";
13
+
14
+ export const PUBLICATION_REHEARSAL_CAPSULE_CONTRACT =
15
+ V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT;
16
+ export const PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT =
17
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT;
18
+ export const PUBLICATION_REHEARSAL_DIAGNOSTIC_CONTRACT =
19
+ "buildchain-v4-publication-rehearsal-compatibility-diagnostic/v1";
20
+ export const RELEASE_LOCAL_CONSTRUCTIBILITY_ADR =
21
+ "docs/v4-publication-rehearsal.md";
22
+ export const RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT =
23
+ "Publication rehearsal is source-, candidate-, manifest-, config-, policy-, observation-, and shared-core-bound; rehearsal authority is effect-disabled by default and never grants production authority.";
24
+ export const PUBLICATION_REHEARSAL_COMMAND =
25
+ 'buildchain release-tail rehearse --capsule "$PWD/.buildchain/publication-rehearsal/capsule.json" --candidate-root "$PWD/.buildchain/publication-rehearsal/candidate" --mode simulate --state "$PWD/.buildchain/publication-rehearsal/state.json" --evidence "$PWD/.buildchain/publication-rehearsal/evidence.json"';
26
+
27
+ const REQUIRED_V4_INPUTS = Object.freeze([
28
+ "source.repository",
29
+ "source.revision",
30
+ "manifest.path",
31
+ "manifest.root",
32
+ "config.path",
33
+ "config.root",
34
+ ]);
35
+
36
+ function migrationReceipt(surface, reasonCode) {
37
+ const body = {
38
+ schema: "buildchain-v3-v4-public-surface-migration/v1",
39
+ surface,
40
+ status: "migration-required",
41
+ reasonCode,
42
+ replacement: {
43
+ module: "@kungfu-tech/buildchain/v4-publication-rehearsal",
44
+ capsuleContract: V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
45
+ requiredInputs: REQUIRED_V4_INPUTS,
46
+ },
47
+ productionAuthority: false,
48
+ };
49
+ return { ...body, migrationRoot: releaseTailRoot(body) };
50
+ }
51
+
52
+ export class PublicationRehearsalError extends Error {
53
+ constructor(
54
+ message,
55
+ {
56
+ code = "publication-rehearsal-v4-migration-required",
57
+ classification = "migration",
58
+ migration = null,
59
+ cause,
60
+ } = {},
61
+ ) {
62
+ super(message, cause ? { cause } : undefined);
63
+ this.name = "PublicationRehearsalError";
64
+ this.rehearsalCode = code;
65
+ this.rehearsalClass = classification;
66
+ this.migration = migration;
67
+ this.bindingRoot = "";
68
+ }
69
+ }
70
+
71
+ function hasV4CreationBindings(input) {
72
+ return Boolean(
73
+ input?.source?.repository &&
74
+ input?.source?.revision &&
75
+ input?.manifest?.path &&
76
+ input?.manifest?.root &&
77
+ input?.config?.path &&
78
+ input?.config?.root,
79
+ );
80
+ }
81
+
82
+ function requireV4CreationBindings(input, surface) {
83
+ if (hasV4CreationBindings(input)) return;
84
+ const migration = migrationReceipt(surface, "v4-source-binding-required");
85
+ throw new PublicationRehearsalError(
86
+ "v3 rehearsal capsules cannot be upgraded implicitly; provide the v4 source, manifest, and config bindings",
87
+ { migration },
88
+ );
89
+ }
90
+
91
+ export function createPublicationRehearsalCapsule(input = {}) {
92
+ requireV4CreationBindings(
93
+ input,
94
+ "@kungfu-tech/buildchain/publication-rehearsal-runtime#createPublicationRehearsalCapsule",
95
+ );
96
+ return createV4PublicationRehearsalCapsule(input);
97
+ }
98
+
99
+ export function normalizePublicationRehearsalCapsule(input) {
100
+ return validateV4PublicationRehearsalCapsule(input);
101
+ }
102
+
103
+ export function verifyPublicationRehearsalCapsule({
104
+ capsule,
105
+ capsuleRoot,
106
+ candidateRoot = capsuleRoot,
107
+ } = {}) {
108
+ return verifyV4PublicationRehearsalCapsule({ capsule, candidateRoot });
109
+ }
110
+
111
+ export function publicationRehearsalBindingRoot(capsule) {
112
+ return validateV4PublicationRehearsalCapsule(capsule).capsuleRoot;
113
+ }
114
+
115
+ export function publicationRehearsalDiagnostic(error, { capsule } = {}) {
116
+ const migration = error?.migration || null;
117
+ const body = {
118
+ contract: PUBLICATION_REHEARSAL_DIAGNOSTIC_CONTRACT,
119
+ status: "rejected",
120
+ errorClass: error?.rehearsalClass || (migration ? "migration" : "input"),
121
+ code:
122
+ error?.rehearsalCode ||
123
+ error?.code ||
124
+ "publication-rehearsal-runtime-error",
125
+ message: String(error?.message || "publication rehearsal failed"),
126
+ bindingRoot: capsule?.capsuleRoot || "",
127
+ migration,
128
+ productionAuthority: false,
129
+ };
130
+ return { ...body, diagnosticRoot: releaseTailRoot(body) };
131
+ }
132
+
133
+ export async function executePublicationRehearsal({
134
+ capsule,
135
+ capsuleRoot,
136
+ candidateRoot = capsuleRoot,
137
+ mode = "simulate",
138
+ environment = {},
139
+ adapters = {},
140
+ authority = null,
141
+ transaction = null,
142
+ checkpoint,
143
+ } = {}) {
144
+ if (
145
+ !environment ||
146
+ typeof environment !== "object" ||
147
+ Array.isArray(environment) ||
148
+ Object.keys(environment).length > 0
149
+ ) {
150
+ const migration = migrationReceipt(
151
+ "@kungfu-tech/buildchain/publication-rehearsal-runtime#executePublicationRehearsal",
152
+ "ambient-environment-forbidden",
153
+ );
154
+ throw new PublicationRehearsalError(
155
+ "v4 rehearsal accepts no ambient environment; bind every semantic input into the capsule",
156
+ { code: "undeclared-environment", classification: "input", migration },
157
+ );
158
+ }
159
+ return executeV4PublicationRehearsal({
160
+ capsule,
161
+ candidateRoot,
162
+ mode,
163
+ adapters,
164
+ authority,
165
+ transaction,
166
+ checkpoint,
167
+ });
168
+ }
169
+
170
+ export function resolvePublicationRehearsalFile(capsuleRoot, relativePath) {
171
+ if (!path.isAbsolute(capsuleRoot))
172
+ throw new PublicationRehearsalError(
173
+ "capsuleRoot must be an explicit absolute path",
174
+ { code: "implicit-workspace-forbidden", classification: "input" },
175
+ );
176
+ const base = fs.realpathSync(capsuleRoot);
177
+ const resolved = path.resolve(base, String(relativePath || ""));
178
+ if (
179
+ !resolved.startsWith(`${base}${path.sep}`) ||
180
+ !fs.existsSync(resolved) ||
181
+ !fs.statSync(resolved).isFile() ||
182
+ fs.lstatSync(resolved).isSymbolicLink() ||
183
+ !fs.realpathSync(resolved).startsWith(`${base}${path.sep}`)
184
+ )
185
+ throw new PublicationRehearsalError(
186
+ "publication rehearsal file must be a regular file inside the explicit candidate root",
187
+ { code: "capsule-path-ambiguous", classification: "input" },
188
+ );
189
+ return resolved;
190
+ }