@kungfu-tech/buildchain 3.0.6 → 3.0.7-alpha.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.
- package/README.md +4 -2
- package/actions/promote-buildchain-ref/README.md +10 -0
- package/actions/release-tail/README.md +17 -0
- package/bin/buildchain.mjs +11 -0
- package/bin/internal/command-registry.mjs +2 -0
- package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
- package/contracts/engineering-housekeeper-v1.schema.json +143 -0
- package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -0
- package/contracts/fixtures/release-tail-capabilities-v1/kungfu-alpha.json +273 -0
- package/contracts/publication-rehearsal-capsule-v1.schema.json +173 -0
- package/contracts/release-tail-capabilities-v1.schema.json +199 -0
- package/contracts/release-tail-provider-bindings-v1.schema.json +56 -0
- package/dist/site/agent-index.json +3 -0
- package/dist/site/artifact-schemas.json +6 -0
- package/dist/site/buildchain-contract.json +42 -27
- package/dist/site/buildchain-site.json +384 -33
- package/dist/site/capability-registry.json +15 -12
- package/dist/site/cli-registry.json +102 -0
- package/dist/site/controller-registry.json +19 -3
- package/dist/site/kfd-claims.json +359 -17
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +53 -7
- package/dist/site/node-api-registry.json +5882 -3605
- package/dist/site/page-registry.json +348 -21
- package/dist/site/public-surface-audit.json +396 -16
- package/dist/site/publication-authority-registry.json +106 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +7 -0
- package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +269 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +353 -0
- package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +94 -0
- package/dist/site/site-manifest.json +35 -11
- package/dist/site/workflow-registry.json +204 -10
- package/docs/MAP.md +6 -2
- package/docs/auditable-demo.md +2 -2
- package/docs/cli-reference.md +136 -0
- package/docs/dev-delivery-warrant.md +49 -4
- package/docs/engineering-housekeeper.md +138 -0
- package/docs/lifecycle-protocol.md +4 -2
- package/docs/node-api-reference.md +495 -281
- package/docs/publication-rehearsal.md +94 -0
- package/docs/release-governance.md +17 -2
- package/docs/release-tail-contract.md +160 -0
- package/docs/release-tail-provider-plane.md +120 -0
- package/docs/reusable-build-surface.md +11 -0
- package/package.json +11 -3
- package/packages/core/artifact-signing.js +61 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-config.js +66 -6
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/buildchain-publication-authority.js +5 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-shadow.js +502 -0
- package/packages/core/dev-delivery-warrant.js +15 -6
- package/packages/core/diagnostics.js +8 -3
- package/packages/core/engineering-housekeeper-github-client.js +222 -0
- package/packages/core/engineering-housekeeper-github.js +501 -0
- package/packages/core/engineering-housekeeper.js +259 -0
- package/packages/core/index.js +42 -0
- package/packages/core/kfd-gate.js +45 -15
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper-repository.js +1 -0
- package/packages/core/paper-scaffold-content.js +21 -0
- package/packages/core/paper.js +28 -2
- package/packages/core/publication-rehearsal-projection.js +173 -0
- package/packages/core/publication-rehearsal-runtime.js +921 -0
- package/packages/core/release-passport.js +130 -20
- package/packages/core/release-tail-compatibility.js +60 -0
- package/packages/core/release-tail-provider-adapters.js +461 -0
- package/packages/core/release-tail-provider-plane.js +1228 -0
- package/scripts/assemble-publication-artifact-admission.mjs +1 -1
- package/scripts/assemble-self-publication-admission.mjs +2 -2
- package/scripts/audit-publication-control-plane.mjs +1 -1
- package/scripts/auditable-demo-bundle-verification.mjs +2 -3
- package/scripts/auditable-demo-platform.mjs +2 -2
- package/scripts/auditable-demo-renditions.mjs +1 -1
- package/scripts/auditable-demo.mjs +2 -2
- package/scripts/build-contract-core.mjs +8 -3
- package/scripts/build-standalone-binary.mjs +14 -3
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-core-mechanism-inventory.mjs +347 -0
- package/scripts/check-inventory.mjs +10 -8
- package/scripts/check-maintainability.mjs +9 -2
- package/scripts/check-release-tail-contract.mjs +435 -0
- package/scripts/dev-delivery-warrant.mjs +31 -4
- package/scripts/dev-pr-auto-merge.mjs +30 -4
- package/scripts/dev-pr-delivery-warrant.mjs +50 -0
- package/scripts/engineering-housekeeper-workflow.mjs +394 -0
- package/scripts/generate-channel-promotion-workflow.mjs +10 -8
- package/scripts/generate-site-bundle.mjs +47 -4
- package/scripts/init-repo.mjs +26 -2
- package/scripts/inspect-artifact-signing-requests.mjs +6 -0
- package/scripts/materialize-self-release-candidate-version.mjs +137 -0
- package/scripts/publication-commit-evidence.mjs +69 -23
- package/scripts/release-candidate-resolver.mjs +16 -10
- package/scripts/release-tail.mjs +159 -0
- package/scripts/resume-from-candidate-run.mjs +123 -9
- package/scripts/seal-artifact-signing-requests.mjs +6 -0
- package/scripts/site-capability-metadata.mjs +13 -0
- package/scripts/v4-architecture.mjs +600 -0
- package/scripts/web-surface-core.mjs +8 -2
- package/scripts/workflow-call-contract.mjs +184 -5
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const ENGINEERING_HOUSEKEEPER_CONTRACT =
|
|
4
|
+
"kungfu-buildchain-engineering-housekeeper/v1";
|
|
5
|
+
export const ENGINEERING_HOUSEKEEPER_SCHEMA_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
export const HOUSEKEEPER_REASON_CODES = Object.freeze({
|
|
8
|
+
ELIGIBLE_MERGED_BRANCH: "eligible.merged-branch",
|
|
9
|
+
PROTECTED_BRANCH: "branch.protected",
|
|
10
|
+
RETAINED_BRANCH: "branch.retained",
|
|
11
|
+
DEFAULT_BRANCH: "branch.default",
|
|
12
|
+
TARGET_BRANCH: "branch.target",
|
|
13
|
+
OPEN_PR_HEAD: "branch.open-pr-head",
|
|
14
|
+
CROSS_REPOSITORY: "branch.cross-repository",
|
|
15
|
+
NOT_MERGED: "branch.not-merged",
|
|
16
|
+
HEAD_ADVANCED: "branch.head-advanced",
|
|
17
|
+
RENAMED: "branch.renamed",
|
|
18
|
+
TARGET_ADVANCED: "branch.target-advanced",
|
|
19
|
+
PERMISSION_DENIED: "branch.permission-denied",
|
|
20
|
+
PR_ACTIVE: "pull-request.active",
|
|
21
|
+
PR_STALE_REPORT: "pull-request.stale-report",
|
|
22
|
+
PR_LABEL_ELIGIBLE: "pull-request.label-eligible",
|
|
23
|
+
PR_AUTO_CLOSE_DISABLED: "pull-request.auto-close-disabled",
|
|
24
|
+
PR_FORKED: "pull-request.forked",
|
|
25
|
+
REPEATED_NO_OP: "replay.already-applied",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_HOUSEKEEPER_POLICY = Object.freeze({
|
|
29
|
+
protectedPatterns: ["dev/**", "alpha/**", "release/**", "publish-gate/**"],
|
|
30
|
+
retainedPatterns: ["train/**", "authority/**"],
|
|
31
|
+
pullRequests: Object.freeze({
|
|
32
|
+
reportStale: true,
|
|
33
|
+
label: "",
|
|
34
|
+
autoClose: false,
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function stableJson(value) {
|
|
39
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
return `{${Object.keys(value)
|
|
42
|
+
.sort()
|
|
43
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
|
44
|
+
.join(",")}}`;
|
|
45
|
+
}
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function engineeringHousekeeperRoot(value) {
|
|
50
|
+
return `sha256:${crypto.createHash("sha256").update(stableJson(value)).digest("hex")}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function globMatches(pattern, value) {
|
|
54
|
+
const expression = String(pattern)
|
|
55
|
+
.split("**")
|
|
56
|
+
.map((chunk) =>
|
|
57
|
+
chunk
|
|
58
|
+
.split("*")
|
|
59
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
60
|
+
.join("[^/]*"),
|
|
61
|
+
)
|
|
62
|
+
.join(".*");
|
|
63
|
+
return new RegExp(`^${expression}$`).test(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function matchesAny(patterns, value) {
|
|
67
|
+
return [...(patterns || [])].some((pattern) => globMatches(pattern, value));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizePolicy(policy = {}) {
|
|
71
|
+
return {
|
|
72
|
+
protectedPatterns: [
|
|
73
|
+
...(policy.protectedPatterns ||
|
|
74
|
+
DEFAULT_HOUSEKEEPER_POLICY.protectedPatterns),
|
|
75
|
+
].sort(),
|
|
76
|
+
retainedPatterns: [
|
|
77
|
+
...(policy.retainedPatterns ||
|
|
78
|
+
DEFAULT_HOUSEKEEPER_POLICY.retainedPatterns),
|
|
79
|
+
].sort(),
|
|
80
|
+
pullRequests: {
|
|
81
|
+
reportStale: policy.pullRequests?.reportStale !== false,
|
|
82
|
+
label: String(policy.pullRequests?.label || ""),
|
|
83
|
+
autoClose: false,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sortedUnique(values) {
|
|
89
|
+
return [...new Set(values)].sort();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function classifyHousekeeperBranch(branch, policyInput = {}) {
|
|
93
|
+
const policy = normalizePolicy(policyInput);
|
|
94
|
+
const reasons = [];
|
|
95
|
+
if (branch.isDefault) reasons.push(HOUSEKEEPER_REASON_CODES.DEFAULT_BRANCH);
|
|
96
|
+
if (branch.name === branch.target?.name)
|
|
97
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.TARGET_BRANCH);
|
|
98
|
+
if (
|
|
99
|
+
branch.isProtected === true ||
|
|
100
|
+
matchesAny(policy.protectedPatterns, branch.name)
|
|
101
|
+
)
|
|
102
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.PROTECTED_BRANCH);
|
|
103
|
+
if (matchesAny(policy.retainedPatterns, branch.name))
|
|
104
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.RETAINED_BRANCH);
|
|
105
|
+
if (branch.sourceRepository && branch.sourceRepository !== branch.repository)
|
|
106
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.CROSS_REPOSITORY);
|
|
107
|
+
if ((branch.openPullRequestNumbers || []).length > 0)
|
|
108
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.OPEN_PR_HEAD);
|
|
109
|
+
if (branch.ancestry !== "ancestor")
|
|
110
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.NOT_MERGED);
|
|
111
|
+
const eligible =
|
|
112
|
+
reasons.length === 0 &&
|
|
113
|
+
Boolean(branch.headOid) &&
|
|
114
|
+
Boolean(branch.target?.headOid);
|
|
115
|
+
return {
|
|
116
|
+
kind: "branch",
|
|
117
|
+
repository: branch.repository,
|
|
118
|
+
name: branch.name,
|
|
119
|
+
headOid: branch.headOid,
|
|
120
|
+
target: { name: branch.target?.name, headOid: branch.target?.headOid },
|
|
121
|
+
eligible,
|
|
122
|
+
decision: eligible ? "delete" : "retain",
|
|
123
|
+
reasonCodes: eligible
|
|
124
|
+
? [HOUSEKEEPER_REASON_CODES.ELIGIBLE_MERGED_BRANCH]
|
|
125
|
+
: sortedUnique(reasons),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function classifyHousekeeperPullRequest(pullRequest, policyInput = {}) {
|
|
130
|
+
const policy = normalizePolicy(policyInput);
|
|
131
|
+
const active = ["open", "draft"].includes(pullRequest.state);
|
|
132
|
+
const forked = pullRequest.headRepository !== pullRequest.repository;
|
|
133
|
+
const stale = active && pullRequest.stale === true;
|
|
134
|
+
const actions = stale && policy.pullRequests.reportStale ? ["report"] : [];
|
|
135
|
+
if (stale && policy.pullRequests.label) actions.push("label");
|
|
136
|
+
const reasons = [HOUSEKEEPER_REASON_CODES.PR_AUTO_CLOSE_DISABLED];
|
|
137
|
+
if (active) reasons.push(HOUSEKEEPER_REASON_CODES.PR_ACTIVE);
|
|
138
|
+
if (stale) reasons.push(HOUSEKEEPER_REASON_CODES.PR_STALE_REPORT);
|
|
139
|
+
if (stale && policy.pullRequests.label)
|
|
140
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.PR_LABEL_ELIGIBLE);
|
|
141
|
+
if (forked) reasons.push(HOUSEKEEPER_REASON_CODES.PR_FORKED);
|
|
142
|
+
return {
|
|
143
|
+
kind: "pull-request",
|
|
144
|
+
repository: pullRequest.repository,
|
|
145
|
+
number: pullRequest.number,
|
|
146
|
+
headRepository: pullRequest.headRepository,
|
|
147
|
+
headRef: pullRequest.headRef,
|
|
148
|
+
headOid: pullRequest.headOid,
|
|
149
|
+
state: pullRequest.state,
|
|
150
|
+
actions: actions.sort(),
|
|
151
|
+
reasonCodes: sortedUnique(reasons),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function createEngineeringHousekeeperPlan({
|
|
156
|
+
repository,
|
|
157
|
+
target,
|
|
158
|
+
branches = [],
|
|
159
|
+
pullRequests = [],
|
|
160
|
+
policy = {},
|
|
161
|
+
observedAt,
|
|
162
|
+
}) {
|
|
163
|
+
const normalizedPolicy = normalizePolicy(policy);
|
|
164
|
+
const inventory = [
|
|
165
|
+
...branches.map((branch) =>
|
|
166
|
+
classifyHousekeeperBranch(branch, normalizedPolicy),
|
|
167
|
+
),
|
|
168
|
+
...pullRequests.map((pullRequest) =>
|
|
169
|
+
classifyHousekeeperPullRequest(pullRequest, normalizedPolicy),
|
|
170
|
+
),
|
|
171
|
+
].sort((left, right) =>
|
|
172
|
+
`${left.kind}:${left.name || String(left.number).padStart(12, "0")}`.localeCompare(
|
|
173
|
+
`${right.kind}:${right.name || String(right.number).padStart(12, "0")}`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
const body = {
|
|
177
|
+
contract: ENGINEERING_HOUSEKEEPER_CONTRACT,
|
|
178
|
+
schemaVersion: ENGINEERING_HOUSEKEEPER_SCHEMA_VERSION,
|
|
179
|
+
mode: "plan",
|
|
180
|
+
repository,
|
|
181
|
+
target,
|
|
182
|
+
observedAt,
|
|
183
|
+
policy: normalizedPolicy,
|
|
184
|
+
inventory,
|
|
185
|
+
actions: inventory.flatMap((entry) =>
|
|
186
|
+
entry.kind === "branch" && entry.eligible
|
|
187
|
+
? [
|
|
188
|
+
{
|
|
189
|
+
kind: "delete-branch",
|
|
190
|
+
name: entry.name,
|
|
191
|
+
expectedHeadOid: entry.headOid,
|
|
192
|
+
targetName: entry.target.name,
|
|
193
|
+
expectedTargetHeadOid: entry.target.headOid,
|
|
194
|
+
},
|
|
195
|
+
]
|
|
196
|
+
: entry.kind === "pull-request"
|
|
197
|
+
? entry.actions.map((action) => ({
|
|
198
|
+
kind: `${action}-pull-request`,
|
|
199
|
+
number: entry.number,
|
|
200
|
+
expectedHeadOid: entry.headOid,
|
|
201
|
+
}))
|
|
202
|
+
: [],
|
|
203
|
+
),
|
|
204
|
+
};
|
|
205
|
+
return { ...body, planRoot: engineeringHousekeeperRoot(body) };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function revalidateHousekeeperBranchAction(
|
|
209
|
+
action,
|
|
210
|
+
current,
|
|
211
|
+
policy = {},
|
|
212
|
+
) {
|
|
213
|
+
const reasons = [];
|
|
214
|
+
if (current.name !== action.name)
|
|
215
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.RENAMED);
|
|
216
|
+
if (current.headOid !== action.expectedHeadOid)
|
|
217
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.HEAD_ADVANCED);
|
|
218
|
+
if (current.target?.headOid !== action.expectedTargetHeadOid)
|
|
219
|
+
reasons.push(HOUSEKEEPER_REASON_CODES.TARGET_ADVANCED);
|
|
220
|
+
const classification = classifyHousekeeperBranch(current, policy);
|
|
221
|
+
if (!classification.eligible) reasons.push(...classification.reasonCodes);
|
|
222
|
+
return {
|
|
223
|
+
ok: reasons.length === 0,
|
|
224
|
+
action,
|
|
225
|
+
currentHeadOid: current.headOid,
|
|
226
|
+
currentTargetHeadOid: current.target?.headOid,
|
|
227
|
+
reasonCodes: sortedUnique(reasons),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function createEngineeringHousekeeperReceipt({
|
|
232
|
+
plan,
|
|
233
|
+
outcomes,
|
|
234
|
+
appliedAt,
|
|
235
|
+
}) {
|
|
236
|
+
const orderedOutcomes = [...outcomes].sort((a, b) =>
|
|
237
|
+
stableJson(a).localeCompare(stableJson(b)),
|
|
238
|
+
);
|
|
239
|
+
const body = {
|
|
240
|
+
contract: ENGINEERING_HOUSEKEEPER_CONTRACT,
|
|
241
|
+
schemaVersion: ENGINEERING_HOUSEKEEPER_SCHEMA_VERSION,
|
|
242
|
+
mode: "receipt",
|
|
243
|
+
planRoot: plan.planRoot,
|
|
244
|
+
appliedAt,
|
|
245
|
+
outcomes: orderedOutcomes,
|
|
246
|
+
};
|
|
247
|
+
return { ...body, receiptRoot: engineeringHousekeeperRoot(body) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function classifyHousekeeperReplay(plan, priorReceipt) {
|
|
251
|
+
const alreadyApplied = priorReceipt?.planRoot === plan.planRoot;
|
|
252
|
+
return {
|
|
253
|
+
alreadyApplied,
|
|
254
|
+
action: alreadyApplied ? "no-op" : "apply",
|
|
255
|
+
reasonCodes: alreadyApplied
|
|
256
|
+
? [HOUSEKEEPER_REASON_CODES.REPEATED_NO_OP]
|
|
257
|
+
: [],
|
|
258
|
+
};
|
|
259
|
+
}
|
package/packages/core/index.js
CHANGED
|
@@ -58,6 +58,45 @@ export {
|
|
|
58
58
|
writeReleaseTransaction,
|
|
59
59
|
} from "./publish-transaction.js";
|
|
60
60
|
|
|
61
|
+
export {
|
|
62
|
+
RELEASE_TAIL_CAPABILITY_REGISTRY,
|
|
63
|
+
RELEASE_TAIL_DECLARATION_CONTRACT,
|
|
64
|
+
RELEASE_TAIL_EFFECT_SCHEMA,
|
|
65
|
+
RELEASE_TAIL_OBSERVATION_SCHEMA,
|
|
66
|
+
RELEASE_TAIL_RECEIPT_SCHEMA,
|
|
67
|
+
RELEASE_TAIL_STATES,
|
|
68
|
+
RELEASE_TAIL_TRANSACTION_POLICY,
|
|
69
|
+
RELEASE_TAIL_TRANSACTION_SCHEMA,
|
|
70
|
+
compileReleaseTailDeclaration,
|
|
71
|
+
createReleaseTailAdapterSet,
|
|
72
|
+
createReleaseTailTransaction,
|
|
73
|
+
executeReleaseTailTransaction,
|
|
74
|
+
parseReleaseTailDeclaration,
|
|
75
|
+
readReleaseTailTransaction,
|
|
76
|
+
releaseTailRetryPolicyFromDeclaration,
|
|
77
|
+
releaseTailRoot,
|
|
78
|
+
releaseTailStableJson,
|
|
79
|
+
validateReleaseTailEffectPlan,
|
|
80
|
+
validateReleaseTailTransaction,
|
|
81
|
+
writeReleaseTailTransaction,
|
|
82
|
+
} from "./release-tail-provider-plane.js";
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
ReleaseTailProviderError,
|
|
86
|
+
createActivationReceiptProjectorAdapter,
|
|
87
|
+
createGitHubReleaseAssetsAdapter,
|
|
88
|
+
createHttpJsonReadback,
|
|
89
|
+
createSignedStaticChannelAdapter,
|
|
90
|
+
createSiteReleaseActivationAdapter,
|
|
91
|
+
githubReleaseAssetsTargetRoot,
|
|
92
|
+
} from "./release-tail-provider-adapters.js";
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
RELEASE_TAIL_COMPATIBILITY_CONTRACT,
|
|
96
|
+
RELEASE_TAIL_LEGACY_HOOKS,
|
|
97
|
+
diagnoseLegacyReleaseTailHooks,
|
|
98
|
+
} from "./release-tail-compatibility.js";
|
|
99
|
+
|
|
61
100
|
export {
|
|
62
101
|
createPortableDevCachePlan,
|
|
63
102
|
createPortableDevCacheReceipt,
|
|
@@ -248,6 +287,9 @@ export {
|
|
|
248
287
|
verifyGithubGovernanceReceipt,
|
|
249
288
|
} from "./github-governance-authority.js";
|
|
250
289
|
|
|
290
|
+
export * from "./engineering-housekeeper.js";
|
|
291
|
+
export * from "./engineering-housekeeper-github.js";
|
|
292
|
+
|
|
251
293
|
export {
|
|
252
294
|
ARTIFACT_PASSPORT_LOCATOR_CONTRACT,
|
|
253
295
|
ARTIFACT_PASSPORT_POINTER_CONTRACT,
|
|
@@ -944,26 +944,49 @@ export function normalizeKfd1ContractWorldWitness(witness, { metadata = resolveK
|
|
|
944
944
|
};
|
|
945
945
|
}
|
|
946
946
|
|
|
947
|
-
function
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
947
|
+
function indexArtifactSearchRoots({ roots = [], artifactPaths = [] } = {}) {
|
|
948
|
+
const wanted = [...new Set(artifactPaths.filter(Boolean))];
|
|
949
|
+
const indexed = new Map();
|
|
950
|
+
const stack = roots.filter((root) => root && fs.existsSync(root) && fs.statSync(root).isDirectory());
|
|
951
|
+
while (stack.length > 0) {
|
|
952
|
+
const current = stack.pop();
|
|
953
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
954
|
+
const candidate = path.join(current, entry.name);
|
|
955
|
+
if (entry.isDirectory()) { stack.push(candidate); continue; }
|
|
956
|
+
if (!entry.isFile()) continue;
|
|
957
|
+
const normalized = candidate.replace(/\\/g, "/");
|
|
958
|
+
for (const artifactPath of wanted) {
|
|
959
|
+
if (normalized === artifactPath || normalized.endsWith(`/${artifactPath}`)) {
|
|
960
|
+
const candidates = indexed.get(artifactPath) || [];
|
|
961
|
+
candidates.push(candidate);
|
|
962
|
+
indexed.set(artifactPath, candidates);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return indexed;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function resolveArtifactFile({ cwd, artifactRoot, artifactIndex = new Map(), artifacts = [], artifactPath, expectedSha256 = "" }) {
|
|
971
|
+
const candidates = artifactIndex.size > 0
|
|
972
|
+
? [...(artifactIndex.get(artifactPath) || []), artifactRoot ? path.resolve(artifactRoot, artifactPath) : "", path.resolve(cwd, artifactPath)]
|
|
973
|
+
: [path.resolve(cwd, artifactPath), artifactRoot ? path.resolve(artifactRoot, artifactPath) : ""];
|
|
952
974
|
for (const artifact of artifacts) {
|
|
953
975
|
const sourcePath = artifact.sourcePath || artifact.path || "";
|
|
954
|
-
if (!sourcePath)
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
976
|
+
if (!sourcePath) continue;
|
|
957
977
|
const normalizedSource = sourcePath.replace(/\\/g, "/");
|
|
958
|
-
if (normalizedSource === artifactPath || normalizedSource.endsWith(`/${artifactPath}`))
|
|
959
|
-
candidates.push(path.resolve(sourcePath));
|
|
960
|
-
}
|
|
978
|
+
if (normalizedSource === artifactPath || normalizedSource.endsWith(`/${artifactPath}`)) candidates.push(path.resolve(sourcePath));
|
|
961
979
|
const name = artifact.name || artifact.filename || "";
|
|
962
|
-
if (name && name === artifactPath)
|
|
963
|
-
candidates.push(path.resolve(sourcePath));
|
|
964
|
-
}
|
|
980
|
+
if (name && name === artifactPath) candidates.push(path.resolve(sourcePath));
|
|
965
981
|
}
|
|
966
|
-
|
|
982
|
+
const existing = [...new Set(candidates)].filter(
|
|
983
|
+
(candidate) => candidate && fs.existsSync(candidate) && fs.statSync(candidate).isFile(),
|
|
984
|
+
);
|
|
985
|
+
if (expectedSha256) {
|
|
986
|
+
const exact = existing.find((candidate) => sha256File(candidate) === expectedSha256);
|
|
987
|
+
if (exact) return exact;
|
|
988
|
+
}
|
|
989
|
+
return existing[0] || "";
|
|
967
990
|
}
|
|
968
991
|
|
|
969
992
|
function resolveSourceFile({ cwd, sourcePath }) {
|
|
@@ -977,6 +1000,7 @@ function resolveSourceFile({ cwd, sourcePath }) {
|
|
|
977
1000
|
export function createKfd1ReleaseGateEvidence({
|
|
978
1001
|
cwd = process.cwd(),
|
|
979
1002
|
artifactRoot = "",
|
|
1003
|
+
artifactSearchRoots = [],
|
|
980
1004
|
artifacts = [],
|
|
981
1005
|
witnesses = [],
|
|
982
1006
|
verifiedAt = new Date().toISOString(),
|
|
@@ -986,6 +1010,10 @@ export function createKfd1ReleaseGateEvidence({
|
|
|
986
1010
|
if (normalizedWitnesses.length === 0) {
|
|
987
1011
|
return undefined;
|
|
988
1012
|
}
|
|
1013
|
+
const artifactIndex = indexArtifactSearchRoots({
|
|
1014
|
+
roots: artifactSearchRoots,
|
|
1015
|
+
artifactPaths: normalizedWitnesses.flatMap((witness) => witness.surfaces.map((surface) => surface.artifactPath)),
|
|
1016
|
+
});
|
|
989
1017
|
const worlds = normalizedWitnesses.map((witness) => {
|
|
990
1018
|
const preBuildWitnessSha256 = sha256Json(witness);
|
|
991
1019
|
const sourceResults = witness.surfaces.map((surface) => {
|
|
@@ -1025,8 +1053,10 @@ export function createKfd1ReleaseGateEvidence({
|
|
|
1025
1053
|
const filePath = resolveArtifactFile({
|
|
1026
1054
|
cwd,
|
|
1027
1055
|
artifactRoot,
|
|
1056
|
+
artifactIndex,
|
|
1028
1057
|
artifacts,
|
|
1029
1058
|
artifactPath: surface.artifactPath,
|
|
1059
|
+
expectedSha256: surface.expectedSha256,
|
|
1030
1060
|
});
|
|
1031
1061
|
if (!filePath) {
|
|
1032
1062
|
return {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stableJson,
|
|
13
13
|
workCheck,
|
|
14
14
|
} from "./paper-repository.js";
|
|
15
|
+
import { mergePublicationRehearsalAgentInstructions } from "./publication-rehearsal-projection.js";
|
|
15
16
|
|
|
16
17
|
export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
|
|
17
18
|
export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
|
|
@@ -167,9 +168,11 @@ export function paperAgentEntryFiles({
|
|
|
167
168
|
[PAPER_PATHS.agentEntry, jsonText(entry)],
|
|
168
169
|
[
|
|
169
170
|
PAPER_PATHS.agentInstructions,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
mergePublicationRehearsalAgentInstructions(
|
|
172
|
+
mergePaperAgentEntryInstructions(currentAgents, {
|
|
173
|
+
developmentRef: resolvedDevelopmentRef,
|
|
174
|
+
}),
|
|
175
|
+
),
|
|
173
176
|
],
|
|
174
177
|
]);
|
|
175
178
|
}
|
|
@@ -205,8 +208,11 @@ function ciContext({ env, developmentRef }) {
|
|
|
205
208
|
sourceBranch.startsWith(versionStatePrefix) &&
|
|
206
209
|
/^[0-9a-f]{12}$/i.test(sourceBranch.slice(versionStatePrefix.length));
|
|
207
210
|
const branchOk = pullRequest
|
|
208
|
-
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
209
|
-
|
|
211
|
+
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
212
|
+
targetBranch === developmentRef) ||
|
|
213
|
+
(channelIndex > 0 &&
|
|
214
|
+
sourceBranch === channelBranches[channelIndex - 1]) ||
|
|
215
|
+
generatedVersionState
|
|
210
216
|
: channelBranches.includes(refName);
|
|
211
217
|
return {
|
|
212
218
|
mode: "ci",
|
|
@@ -13,6 +13,7 @@ export const PAPER_PATHS = Object.freeze({
|
|
|
13
13
|
buildWorkflow: ".github/workflows/build.yml",
|
|
14
14
|
verifyWorkflow: ".github/workflows/verify.yml",
|
|
15
15
|
releaseWorkflow: ".github/workflows/paper-release.yml",
|
|
16
|
+
rehearsalWorkflow: ".github/workflows/publication-rehearsal.yml",
|
|
16
17
|
pnpmWorkspace: "pnpm-workspace.yaml",
|
|
17
18
|
reproducibilityReceipt:
|
|
18
19
|
".buildchain/publication/reproducibility-receipt.json",
|
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
8
|
function jsonText(value) {
|
|
2
9
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
3
10
|
}
|
|
@@ -47,6 +54,7 @@ export function paperPackageScripts(current = {}) {
|
|
|
47
54
|
"paper:work:start": "buildchain paper work start",
|
|
48
55
|
"paper:work:submit": "buildchain paper work submit",
|
|
49
56
|
"paper:status": "buildchain paper status --json",
|
|
57
|
+
"publication:rehearse": PUBLICATION_REHEARSAL_COMMAND,
|
|
50
58
|
};
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -106,6 +114,7 @@ pnpm paper:preflight
|
|
|
106
114
|
pnpm paper:agent:verify
|
|
107
115
|
pnpm paper:work:start -- <topic>
|
|
108
116
|
pnpm paper:work:submit
|
|
117
|
+
pnpm publication:rehearse
|
|
109
118
|
make pdf
|
|
110
119
|
\`\`\`
|
|
111
120
|
|
|
@@ -114,6 +123,11 @@ artifact generation, sealed publication, npm Trusted Publishing, and recovery;
|
|
|
114
123
|
this repository owns the paper source and review history.
|
|
115
124
|
|
|
116
125
|
See [docs/MAP.md](docs/MAP.md) for the repository map.
|
|
126
|
+
|
|
127
|
+
Publication qualification follows the Buildchain
|
|
128
|
+
\`${RELEASE_LOCAL_CONSTRUCTIBILITY_ADR}\` invariant:
|
|
129
|
+
${RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT} The capsule contract is
|
|
130
|
+
\`${PUBLICATION_REHEARSAL_CAPSULE_CONTRACT}\`.
|
|
117
131
|
`;
|
|
118
132
|
}
|
|
119
133
|
|
|
@@ -129,10 +143,17 @@ export function scaffoldMap() {
|
|
|
129
143
|
- \`.github/workflows/build.yml\`: thin read-only build and reproducibility caller.
|
|
130
144
|
- \`.github/workflows/verify.yml\`: thin required check that enforces the Paper entry and acceptance policy.
|
|
131
145
|
- \`.github/workflows/paper-release.yml\`: thin protected sealed-release caller.
|
|
146
|
+
- \`.github/workflows/publication-rehearsal.yml\`: thin hosted adapter over the same local capsule runtime.
|
|
132
147
|
|
|
133
148
|
The repository does not own npm transaction logic, publication authority,
|
|
134
149
|
release-state recovery, or site deployment mechanics. Those remain Buildchain,
|
|
135
150
|
npm/GitHub, and downstream site responsibilities respectively.
|
|
151
|
+
|
|
152
|
+
The exact local pre-publication command is:
|
|
153
|
+
|
|
154
|
+
\`\`\`sh
|
|
155
|
+
${PUBLICATION_REHEARSAL_COMMAND}
|
|
156
|
+
\`\`\`
|
|
136
157
|
`;
|
|
137
158
|
}
|
|
138
159
|
|
package/packages/core/paper.js
CHANGED
|
@@ -46,6 +46,11 @@ import {
|
|
|
46
46
|
scaffoldReadme,
|
|
47
47
|
} from "./paper-scaffold-content.js";
|
|
48
48
|
import { executePaperNpmBootstrap as executePaperNpmBootstrapOperation } from "./paper-npm-bootstrap.js";
|
|
49
|
+
import {
|
|
50
|
+
appendPublicationRehearsalToml,
|
|
51
|
+
projectPublicationRehearsalToml,
|
|
52
|
+
publicationRehearsalWorkflow,
|
|
53
|
+
} from "./publication-rehearsal-projection.js";
|
|
49
54
|
|
|
50
55
|
export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
|
|
51
56
|
export {
|
|
@@ -114,6 +119,7 @@ const PAPER_SCAFFOLD_PATHS = Object.freeze([
|
|
|
114
119
|
PAPER_PATHS.buildWorkflow,
|
|
115
120
|
PAPER_PATHS.verifyWorkflow,
|
|
116
121
|
PAPER_PATHS.releaseWorkflow,
|
|
122
|
+
PAPER_PATHS.rehearsalWorkflow,
|
|
117
123
|
PAPER_PATHS.pnpmWorkspace,
|
|
118
124
|
PAPER_PATHS.provisioningAuthority,
|
|
119
125
|
"Makefile",
|
|
@@ -350,7 +356,7 @@ immutable_base_url = ${tomlString(joinUrl(siteBaseUrl, "archive").replace(/\/$/,
|
|
|
350
356
|
registry_path = ".buildchain/publication/publication-registry.json"
|
|
351
357
|
`
|
|
352
358
|
: "";
|
|
353
|
-
return `schema = 1
|
|
359
|
+
return appendPublicationRehearsalToml(`schema = 1
|
|
354
360
|
|
|
355
361
|
[project]
|
|
356
362
|
type = "publication-artifact"
|
|
@@ -384,7 +390,7 @@ command = "make pdf"
|
|
|
384
390
|
|
|
385
391
|
[lifecycle.verify]
|
|
386
392
|
command = "make check"
|
|
387
|
-
|
|
393
|
+
`);
|
|
388
394
|
}
|
|
389
395
|
|
|
390
396
|
function scaffoldBuildWorkflow(
|
|
@@ -525,6 +531,7 @@ export function createPaperProvisioningAuthority({
|
|
|
525
531
|
buildWorkflow,
|
|
526
532
|
verifyWorkflow,
|
|
527
533
|
releaseWorkflow,
|
|
534
|
+
rehearsalWorkflow,
|
|
528
535
|
agentEntry,
|
|
529
536
|
agentInstructions,
|
|
530
537
|
environment = "",
|
|
@@ -564,6 +571,12 @@ export function createPaperProvisioningAuthority({
|
|
|
564
571
|
reusablePath: ".github/workflows/paper-release-sealed.yml",
|
|
565
572
|
reusableRef: buildchainSha,
|
|
566
573
|
},
|
|
574
|
+
rehearsal: {
|
|
575
|
+
path: PAPER_PATHS.rehearsalWorkflow,
|
|
576
|
+
sourceDigest: sha256Text(rehearsalWorkflow),
|
|
577
|
+
reusablePath: ".github/workflows/release-tail.yml",
|
|
578
|
+
reusableRef: buildchainSha,
|
|
579
|
+
},
|
|
567
580
|
},
|
|
568
581
|
agentEntry: {
|
|
569
582
|
contract: PAPER_AGENT_ENTRY_CONTRACT,
|
|
@@ -630,6 +643,7 @@ function scaffoldFiles({
|
|
|
630
643
|
releasePassportProductName: title,
|
|
631
644
|
});
|
|
632
645
|
const verifyWorkflow = scaffoldVerifyWorkflow(buildchainSha);
|
|
646
|
+
const rehearsalWorkflow = publicationRehearsalWorkflow(buildchainSha);
|
|
633
647
|
const agentEntry = paperAgentEntryFiles({
|
|
634
648
|
cwd,
|
|
635
649
|
buildchainVersion,
|
|
@@ -647,6 +661,7 @@ function scaffoldFiles({
|
|
|
647
661
|
buildWorkflow,
|
|
648
662
|
verifyWorkflow,
|
|
649
663
|
releaseWorkflow,
|
|
664
|
+
rehearsalWorkflow,
|
|
650
665
|
agentEntry: agentEntry.get(PAPER_PATHS.agentEntry),
|
|
651
666
|
agentInstructions: agentEntry.get(PAPER_PATHS.agentInstructions),
|
|
652
667
|
});
|
|
@@ -667,6 +682,7 @@ function scaffoldFiles({
|
|
|
667
682
|
[PAPER_PATHS.buildWorkflow, buildWorkflow],
|
|
668
683
|
[PAPER_PATHS.verifyWorkflow, verifyWorkflow],
|
|
669
684
|
[PAPER_PATHS.releaseWorkflow, releaseWorkflow],
|
|
685
|
+
[PAPER_PATHS.rehearsalWorkflow, rehearsalWorkflow],
|
|
670
686
|
[PAPER_PATHS.pnpmWorkspace, paperPnpmWorkspace("", buildchainVersion)],
|
|
671
687
|
[PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
|
|
672
688
|
...agentEntry,
|
|
@@ -964,6 +980,7 @@ function migrationFiles({
|
|
|
964
980
|
releasePassportProductName: config.publication.title,
|
|
965
981
|
});
|
|
966
982
|
const verifyWorkflow = scaffoldVerifyWorkflow(runtimeSha);
|
|
983
|
+
const rehearsalWorkflow = publicationRehearsalWorkflow(runtimeSha);
|
|
967
984
|
const agentEntry = paperAgentEntryFiles({
|
|
968
985
|
cwd,
|
|
969
986
|
buildchainVersion: runtimeIdentity.version,
|
|
@@ -978,6 +995,7 @@ function migrationFiles({
|
|
|
978
995
|
buildWorkflow,
|
|
979
996
|
verifyWorkflow,
|
|
980
997
|
releaseWorkflow,
|
|
998
|
+
rehearsalWorkflow,
|
|
981
999
|
agentEntry: agentEntry.get(PAPER_PATHS.agentEntry),
|
|
982
1000
|
agentInstructions: agentEntry.get(PAPER_PATHS.agentInstructions),
|
|
983
1001
|
});
|
|
@@ -997,11 +1015,18 @@ function migrationFiles({
|
|
|
997
1015
|
runtimeIdentity.version,
|
|
998
1016
|
);
|
|
999
1017
|
const files = new Map([
|
|
1018
|
+
[
|
|
1019
|
+
PAPER_PATHS.config,
|
|
1020
|
+
projectPublicationRehearsalToml(
|
|
1021
|
+
fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
|
|
1022
|
+
),
|
|
1023
|
+
],
|
|
1000
1024
|
[PAPER_PATHS.contractLock, contractLockText],
|
|
1001
1025
|
[PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
|
|
1002
1026
|
[PAPER_PATHS.buildWorkflow, buildWorkflow],
|
|
1003
1027
|
[PAPER_PATHS.verifyWorkflow, verifyWorkflow],
|
|
1004
1028
|
[PAPER_PATHS.releaseWorkflow, releaseWorkflow],
|
|
1029
|
+
[PAPER_PATHS.rehearsalWorkflow, rehearsalWorkflow],
|
|
1005
1030
|
[PAPER_PATHS.pnpmWorkspace, pnpmWorkspace],
|
|
1006
1031
|
[PAPER_PATHS.provisioningAuthority, jsonText(provisioningAuthority)],
|
|
1007
1032
|
...agentEntry,
|
|
@@ -1755,6 +1780,7 @@ function validatePaperProvisioningAuthority(cwd) {
|
|
|
1755
1780
|
value.workflows?.build,
|
|
1756
1781
|
value.workflows?.verify,
|
|
1757
1782
|
value.workflows?.release,
|
|
1783
|
+
value.workflows?.rehearsal,
|
|
1758
1784
|
]) {
|
|
1759
1785
|
if (!workflow?.path || !workflow?.sourceDigest) {
|
|
1760
1786
|
errors.push("paper workflow authority is incomplete");
|