@kungfu-tech/buildchain 4.1.2 → 4.1.3-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/CONTRIBUTING.md +7 -4
- package/architecture/action-taxonomy.json +4 -0
- package/architecture/agent-change-map.md +44 -0
- package/architecture/internal-capabilities.json +56 -0
- package/architecture/maintainability-debt.json +2 -1
- package/architecture/maintainability-policy.json +4 -4
- package/architecture/release-topology.json +14 -1
- package/architecture/universal-workflow-bootstrap.json +13 -2
- package/architecture/universal-workflow-capability-policy.json +14 -8
- package/contracts/promotion-invocation-v1.schema.json +6 -3
- package/contracts/promotion-request-v1.schema.json +6 -3
- package/contracts/release-discussion-v1.schema.json +143 -0
- package/dist/readers/release-discussion.cjs +207 -0
- package/dist/site/buildchain-contract.json +15 -11
- package/dist/site/buildchain-site.json +64 -8
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/kfd-claims.json +95 -9
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +83 -1
- package/dist/site/page-registry.json +59 -3
- package/dist/site/public-surface-audit.json +53 -7
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +79 -7
- package/docs/node-api-reference.md +9 -0
- package/docs/release-discussions.md +193 -0
- package/package.json +10 -6
- package/packages/core/providers/github/discussions/materials.js +157 -0
- package/packages/core/providers/github/discussions/transport.js +126 -0
- package/packages/core/publication/binary/action.js +21 -7
- package/packages/core/release/discussion/actions.js +65 -0
- package/packages/core/release/discussion/binary.js +103 -0
- package/packages/core/release/discussion/checkpoints.js +282 -0
- package/packages/core/release/discussion/envelope.js +142 -0
- package/packages/core/release/discussion/evidence.js +51 -0
- package/packages/core/release/discussion/presentation.js +92 -0
- package/packages/core/release/discussion/publication.js +123 -0
- package/packages/core/release/discussion/qualification.js +122 -0
- package/packages/core/release/discussion/reader-entry.js +11 -0
- package/packages/core/release/discussion/reader.js +177 -0
- package/packages/core/release/discussion/recovery.js +132 -0
- package/packages/core/release/discussion/session.js +115 -0
- package/packages/core/release/discussion/store.js +166 -0
- package/packages/core/release/discussion/threads.js +83 -0
- package/packages/core/release/github-release.js +3 -1
- package/packages/core/release/promote-candidate/action.js +86 -6
- package/packages/core/release/promote-candidate/preparation.js +106 -0
- package/packages/core/release/promote-candidate/product-provider.js +4 -1
- package/packages/core/release/promote-candidate/provider-settlement.js +36 -23
- package/packages/core/release/promote-candidate/transaction.js +55 -97
- package/packages/core/release/promotion/candidate.js +11 -2
- package/packages/core/release/promotion/qualification.js +5 -2
- package/packages/core/release/promotion-request.js +8 -4
- package/packages/core/workflow/engine/execution.js +5 -1
- package/packages/core/workflow/engine/provider-context.js +2 -0
- package/packages/core/workflow/engine/release-observation.js +1 -0
- package/packages/core/workflow/engine/release-promotion.js +38 -26
- package/packages/core/workflow/universal-workflow-bootstrap.js +1 -0
- package/scripts/build-release-discussion-reader.mjs +34 -0
- package/scripts/inventory/binary.mjs +1 -1
- package/scripts/maintainability-metrics.mjs +1 -0
- package/scripts/site-capability-metadata.mjs +1 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { readDiscussionThreads, eventParent } from "./threads.js";
|
|
2
|
+
import { renderIntent, renderEvent } from "./presentation.js";
|
|
3
|
+
import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
|
|
4
|
+
import {
|
|
5
|
+
canonicalJson,
|
|
6
|
+
decodeRecord,
|
|
7
|
+
encodeRecord,
|
|
8
|
+
INTENT_SCHEMA,
|
|
9
|
+
} from "./envelope.js";
|
|
10
|
+
import { readReleaseDiscussion } from "./reader.js";
|
|
11
|
+
|
|
12
|
+
function assertDiscussion(discussion, intent, writerId) {
|
|
13
|
+
if (
|
|
14
|
+
discussion.repository.nameWithOwner.toLowerCase() !==
|
|
15
|
+
intent.repository.toLowerCase()
|
|
16
|
+
)
|
|
17
|
+
throw new Error("Discussion belongs to a different consumer repository");
|
|
18
|
+
if (discussion.author?.id !== writerId || discussion.lastEditedAt)
|
|
19
|
+
throw new Error("Discussion intent has an untrusted author or was edited");
|
|
20
|
+
const existing = decodeRecord(discussion.body);
|
|
21
|
+
if (existing?.id !== intent.id || existing.schema !== INTENT_SCHEMA)
|
|
22
|
+
throw new Error("Discussion contains a different release intent");
|
|
23
|
+
if (
|
|
24
|
+
canonicalJson(existing.expectedNodes) !==
|
|
25
|
+
canonicalJson(intent.expectedNodes) ||
|
|
26
|
+
canonicalJson(existing.source) !== canonicalJson(intent.source)
|
|
27
|
+
)
|
|
28
|
+
throw new Error("Release intent changed during recovery");
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function releaseDiscussionStore(
|
|
33
|
+
transport,
|
|
34
|
+
{ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) } = {},
|
|
35
|
+
) {
|
|
36
|
+
async function observeAfterUnknown(readback, cause) {
|
|
37
|
+
if (
|
|
38
|
+
[401, 403].includes(cause.status) ||
|
|
39
|
+
cause.errors?.some((error) =>
|
|
40
|
+
["FORBIDDEN", "UNAUTHORIZED"].includes(error.type),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Consumer workflow cannot write this Discussion; grant discussions: write and permit Announcements creation",
|
|
45
|
+
{ cause },
|
|
46
|
+
);
|
|
47
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
48
|
+
const record = await readback();
|
|
49
|
+
if (record) return record;
|
|
50
|
+
if (attempt < 3) await sleep(250 * (attempt + 1));
|
|
51
|
+
}
|
|
52
|
+
throw new Error(
|
|
53
|
+
"Discussion mutation outcome is unknown; retry by reading the same intent before any further write",
|
|
54
|
+
{ cause },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
async function initialize({
|
|
58
|
+
intent,
|
|
59
|
+
discussionId = "",
|
|
60
|
+
category = "Announcements",
|
|
61
|
+
dryRun = false,
|
|
62
|
+
}) {
|
|
63
|
+
if (dryRun) return { dryRun: true, intent };
|
|
64
|
+
const repository = await transport.repository(intent.repository);
|
|
65
|
+
const writerId = repository.viewer.id;
|
|
66
|
+
if (discussionId) {
|
|
67
|
+
const discussion = await transport.get(discussionId);
|
|
68
|
+
const original = assertDiscussion(discussion, intent, writerId);
|
|
69
|
+
return { discussion, intent: original, writerId };
|
|
70
|
+
}
|
|
71
|
+
const selected = repository.discussionCategories.nodes.filter(
|
|
72
|
+
(entry) => entry.name === category,
|
|
73
|
+
);
|
|
74
|
+
if (selected.length !== 1)
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Release Discussion category ${category} must exist exactly once`,
|
|
77
|
+
);
|
|
78
|
+
const find = async () => {
|
|
79
|
+
const candidates = await collectDiscussionPages((after) =>
|
|
80
|
+
transport.list(intent.repository, selected[0].id, after),
|
|
81
|
+
);
|
|
82
|
+
const matching = candidates.filter(
|
|
83
|
+
(discussion) =>
|
|
84
|
+
discussion.author?.id === writerId &&
|
|
85
|
+
decodeRecord(discussion.body)?.id === intent.id,
|
|
86
|
+
);
|
|
87
|
+
if (matching.length > 1)
|
|
88
|
+
throw new Error(
|
|
89
|
+
"Multiple Discussions own this release intent; refusing ambiguous recovery",
|
|
90
|
+
);
|
|
91
|
+
if (matching.length) assertDiscussion(matching[0], intent, writerId);
|
|
92
|
+
return matching[0];
|
|
93
|
+
};
|
|
94
|
+
let discussion = await find();
|
|
95
|
+
if (!discussion) {
|
|
96
|
+
try {
|
|
97
|
+
discussion = await transport.create({
|
|
98
|
+
repositoryId: repository.id,
|
|
99
|
+
categoryId: selected[0].id,
|
|
100
|
+
title: intent.source?.qualification
|
|
101
|
+
? `Buildchain qualification: ${intent.source.qualification}`
|
|
102
|
+
: `Buildchain release: ${intent.key}`,
|
|
103
|
+
body: encodeRecord(intent, renderIntent(intent)),
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
discussion = await observeAfterUnknown(find, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const original = assertDiscussion(discussion, intent, writerId);
|
|
110
|
+
return { discussion, intent: original, writerId };
|
|
111
|
+
}
|
|
112
|
+
async function read(session) {
|
|
113
|
+
const discussion = await transport.get(session.discussion.id);
|
|
114
|
+
const intent = assertDiscussion(
|
|
115
|
+
discussion,
|
|
116
|
+
session.intent,
|
|
117
|
+
session.writerId,
|
|
118
|
+
);
|
|
119
|
+
const threads = await readDiscussionThreads(transport, {
|
|
120
|
+
...session,
|
|
121
|
+
intent,
|
|
122
|
+
discussion,
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
discussion,
|
|
126
|
+
...threads,
|
|
127
|
+
...readReleaseDiscussion({
|
|
128
|
+
body: discussion.body,
|
|
129
|
+
records: threads.records,
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function append(session, record) {
|
|
134
|
+
if (session.dryRun) return { dryRun: true };
|
|
135
|
+
let observed;
|
|
136
|
+
const find = async () => {
|
|
137
|
+
observed = await read(session);
|
|
138
|
+
const existing = observed.comments.find(
|
|
139
|
+
(comment) => decodeRecord(comment.body)?.id === record.id,
|
|
140
|
+
);
|
|
141
|
+
if (!existing)
|
|
142
|
+
readReleaseDiscussion({
|
|
143
|
+
body: observed.discussion.body,
|
|
144
|
+
records: [...observed.records, record],
|
|
145
|
+
});
|
|
146
|
+
return existing;
|
|
147
|
+
};
|
|
148
|
+
const existing = await find();
|
|
149
|
+
if (existing) return existing;
|
|
150
|
+
const parent = eventParent(observed, record);
|
|
151
|
+
const body = encodeRecord(
|
|
152
|
+
record,
|
|
153
|
+
renderEvent(record, session.intent, observed),
|
|
154
|
+
);
|
|
155
|
+
try {
|
|
156
|
+
await transport.append(session.discussion.id, body, parent);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
return observeAfterUnknown(find, error);
|
|
159
|
+
}
|
|
160
|
+
return observeAfterUnknown(
|
|
161
|
+
find,
|
|
162
|
+
new Error("Discussion append requires provider readback"),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return { initialize, read, append };
|
|
166
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { decodeRecord } from "./envelope.js";
|
|
2
|
+
import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
|
|
3
|
+
|
|
4
|
+
export function isAttemptRoot(record) {
|
|
5
|
+
return (
|
|
6
|
+
record?.node === "attempt" &&
|
|
7
|
+
record.kind === "progress" &&
|
|
8
|
+
record.status === "running" &&
|
|
9
|
+
record.sequence === 0
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Provider placement is checked before the pure reader reduces event semantics.
|
|
14
|
+
// Historical flat records retain their original representation; new writes always
|
|
15
|
+
// use roots and replies. An explicit intent organization makes placement strict.
|
|
16
|
+
export async function readDiscussionThreads(transport, session) {
|
|
17
|
+
const top = await collectDiscussionPages((after) =>
|
|
18
|
+
transport.comments(session.discussion.id, after),
|
|
19
|
+
);
|
|
20
|
+
const comments = [...top];
|
|
21
|
+
for (const root of top) {
|
|
22
|
+
if (root.replies?.totalCount === 0) continue;
|
|
23
|
+
const replies = await collectDiscussionPages((after) =>
|
|
24
|
+
transport.replies(root.id, after),
|
|
25
|
+
);
|
|
26
|
+
for (const reply of replies) {
|
|
27
|
+
if (reply.replyTo?.id !== root.id)
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Discussion reply parent differs from its queried thread",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
comments.push(...replies);
|
|
33
|
+
if (Buffer.byteLength(JSON.stringify(comments)) > 32 * 1024 * 1024)
|
|
34
|
+
throw new Error("Discussion threads exceed the complete-view byte bound");
|
|
35
|
+
}
|
|
36
|
+
const trusted = comments.filter(
|
|
37
|
+
(comment) => comment.author?.id === session.writerId,
|
|
38
|
+
);
|
|
39
|
+
const records = new Map();
|
|
40
|
+
for (const comment of trusted) {
|
|
41
|
+
const record = decodeRecord(comment.body);
|
|
42
|
+
if (!record) continue;
|
|
43
|
+
if (comment.lastEditedAt)
|
|
44
|
+
throw new Error(
|
|
45
|
+
"A transaction record was edited; historical facts are no longer intact",
|
|
46
|
+
);
|
|
47
|
+
records.set(comment.id, record);
|
|
48
|
+
}
|
|
49
|
+
const roots = new Map();
|
|
50
|
+
for (const comment of trusted) {
|
|
51
|
+
const record = records.get(comment.id);
|
|
52
|
+
if (!isAttemptRoot(record)) continue;
|
|
53
|
+
if (comment.replyTo || roots.has(record.attempt))
|
|
54
|
+
throw new Error("Ambiguous or nested Discussion attempt root");
|
|
55
|
+
roots.set(record.attempt, comment);
|
|
56
|
+
}
|
|
57
|
+
for (const comment of trusted) {
|
|
58
|
+
const record = records.get(comment.id);
|
|
59
|
+
if (!record || isAttemptRoot(record)) continue;
|
|
60
|
+
const root = roots.get(record.attempt);
|
|
61
|
+
if (comment.replyTo) {
|
|
62
|
+
if (!root || comment.replyTo.id !== root.id)
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Transaction event is attached to a different attempt root",
|
|
65
|
+
);
|
|
66
|
+
} else if (session.intent.organization === "attempt-threads/v1") {
|
|
67
|
+
throw new Error("Transaction event requires its attempt root reply");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { comments: trusted, records: [...records.values()], roots };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function eventParent(state, record) {
|
|
74
|
+
const root = state.roots.get(record.attempt);
|
|
75
|
+
if (isAttemptRoot(record)) {
|
|
76
|
+
if (root)
|
|
77
|
+
throw new Error("Attempt root already exists with different content");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (!root)
|
|
81
|
+
throw new Error("Open the attempt root before appending its events");
|
|
82
|
+
return root.id;
|
|
83
|
+
}
|
|
@@ -302,6 +302,7 @@ export async function publishGitHubReleaseEvidence({
|
|
|
302
302
|
declarationPath,
|
|
303
303
|
qualificationRoot = "",
|
|
304
304
|
failureAfterCapability = "",
|
|
305
|
+
checkpoint: retainCheckpoint,
|
|
305
306
|
} = {}) {
|
|
306
307
|
const assetPaths = collectGitHubReleaseEvidenceAssets({
|
|
307
308
|
publishEvidencePath,
|
|
@@ -349,8 +350,9 @@ export async function publishGitHubReleaseEvidence({
|
|
|
349
350
|
}),
|
|
350
351
|
...createGitHubProviderAdapters(octokit, materialized.documents),
|
|
351
352
|
}),
|
|
352
|
-
checkpoint: (checkpoint) => {
|
|
353
|
+
checkpoint: async (checkpoint) => {
|
|
353
354
|
writeReleaseTailTransaction(resolvedStatePath, checkpoint);
|
|
355
|
+
if (retainCheckpoint) await retainCheckpoint(checkpoint);
|
|
354
356
|
if (
|
|
355
357
|
failureAfterCapability &&
|
|
356
358
|
checkpoint.receipts.some(
|
|
@@ -1,9 +1,89 @@
|
|
|
1
1
|
import * as github from "@actions/github";
|
|
2
|
-
import {
|
|
2
|
+
import { publishWithDiscussion } from "../discussion/publication.js";
|
|
3
|
+
import { selectedRecordRuntime } from "../discussion/session.js";
|
|
3
4
|
export async function candidatePublicationAction(core, env) {
|
|
4
|
-
const required = new Set([
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
const required = new Set([
|
|
6
|
+
"candidate-build-summary-path",
|
|
7
|
+
"required-artifacts-path",
|
|
8
|
+
"target-ref",
|
|
9
|
+
"target-sha",
|
|
10
|
+
"candidate-passport-path",
|
|
11
|
+
"channel",
|
|
12
|
+
"product-publication-intent-path",
|
|
13
|
+
"publication-qualification-path",
|
|
14
|
+
"publisher-workflow-sha",
|
|
15
|
+
"repository",
|
|
16
|
+
"runtime-commit",
|
|
17
|
+
"runtime-tree",
|
|
18
|
+
"source-sha",
|
|
19
|
+
"stage-capsules-path",
|
|
20
|
+
"tag",
|
|
21
|
+
"token",
|
|
22
|
+
"version",
|
|
23
|
+
]);
|
|
24
|
+
const request = Object.fromEntries(
|
|
25
|
+
[
|
|
26
|
+
"recovery-receipt-path",
|
|
27
|
+
"candidate-build-summary-path",
|
|
28
|
+
"candidate-passport-path",
|
|
29
|
+
"channel",
|
|
30
|
+
"failure-after-capability",
|
|
31
|
+
"mutation-token",
|
|
32
|
+
"product-publication-intent-path",
|
|
33
|
+
"publication-qualification-path",
|
|
34
|
+
"publish-artifact-kind",
|
|
35
|
+
"publish-auth",
|
|
36
|
+
"publish-command",
|
|
37
|
+
"publish-dist-tag",
|
|
38
|
+
"publish-mode",
|
|
39
|
+
"publish-package-main",
|
|
40
|
+
"publish-package-set-order",
|
|
41
|
+
"publisher-workflow-sha",
|
|
42
|
+
"repository",
|
|
43
|
+
"required-artifacts-path",
|
|
44
|
+
"required-status-check",
|
|
45
|
+
"resume-transaction-id",
|
|
46
|
+
"resume-discussion-id",
|
|
47
|
+
"runtime-commit",
|
|
48
|
+
"runtime-tree",
|
|
49
|
+
"sealed-bundle-manifest",
|
|
50
|
+
"sealed-bundle-root",
|
|
51
|
+
"source-sha",
|
|
52
|
+
"stage-capsules-path",
|
|
53
|
+
"state-path",
|
|
54
|
+
"tag",
|
|
55
|
+
"target-ref",
|
|
56
|
+
"target-sha",
|
|
57
|
+
"token",
|
|
58
|
+
"version",
|
|
59
|
+
].map((name) => [
|
|
60
|
+
name,
|
|
61
|
+
core.getInput(name, { required: required.has(name) }).trim(),
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
64
|
+
for (const name of [
|
|
65
|
+
"publish-rematerialize-on-resume",
|
|
66
|
+
"publish-transaction-override",
|
|
67
|
+
"standalone-binary-distribution",
|
|
68
|
+
])
|
|
69
|
+
request[name] = core.getBooleanInput(name);
|
|
70
|
+
request["artifact-paths"] = core
|
|
71
|
+
.getMultilineInput("artifact-paths")
|
|
72
|
+
.filter(Boolean);
|
|
73
|
+
return publishWithDiscussion(request, {
|
|
74
|
+
octokit: github.getOctokit(request.token),
|
|
75
|
+
mutationOctokit: github.getOctokit(
|
|
76
|
+
request["mutation-token"] || request.token,
|
|
77
|
+
),
|
|
78
|
+
actor: env.GITHUB_ACTOR,
|
|
79
|
+
runId: env.GITHUB_RUN_ID,
|
|
80
|
+
attempt: `${env.GITHUB_RUN_ID}:${env.GITHUB_RUN_ATTEMPT}:${env.GITHUB_ACTION}`,
|
|
81
|
+
runtime: selectedRecordRuntime(env),
|
|
82
|
+
workspace: env.GITHUB_WORKSPACE,
|
|
83
|
+
runtimeRoot: env.BUILDCHAIN_RUNTIME_ROOT,
|
|
84
|
+
observe: (outputs) => {
|
|
85
|
+
for (const [key, value] of Object.entries(outputs))
|
|
86
|
+
core.setOutput(key, value);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
9
89
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveCandidateBuildSummaryPath,
|
|
3
|
+
resolveCandidateProviderInputs,
|
|
4
|
+
resolvePublicationTarget,
|
|
5
|
+
} from "./evidence-inputs.js";
|
|
6
|
+
import {
|
|
7
|
+
assertCandidateEvidenceBinding,
|
|
8
|
+
canonicalChannel,
|
|
9
|
+
observeProtectedPublicationSource,
|
|
10
|
+
} from "./evidence-binding.js";
|
|
11
|
+
import { productProviderRequest } from "./provider-request.js";
|
|
12
|
+
import { createReleaseDocuments } from "./release-documents.js";
|
|
13
|
+
import { read } from "./files.js";
|
|
14
|
+
import { planProductPublication } from "./product-provider.js";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
export async function prepareCandidatePublication(
|
|
17
|
+
request,
|
|
18
|
+
{ octokit, mutationOctokit, actor, runId },
|
|
19
|
+
) {
|
|
20
|
+
const repository = request["repository"];
|
|
21
|
+
const declaredSourceSha = request["source-sha"];
|
|
22
|
+
const fallbackVersion = request["version"];
|
|
23
|
+
const fallbackTag = request["tag"];
|
|
24
|
+
const channel = request["channel"];
|
|
25
|
+
const expectedTransactionId = request["resume-transaction-id"];
|
|
26
|
+
const candidatePassportPath = request["candidate-passport-path"];
|
|
27
|
+
const buildSummaryPath = resolveCandidateBuildSummaryPath({
|
|
28
|
+
declaredPath: request["candidate-build-summary-path"],
|
|
29
|
+
});
|
|
30
|
+
const candidate = read(candidatePassportPath);
|
|
31
|
+
const stageCapsules = read(request["stage-capsules-path"]);
|
|
32
|
+
const qualification = read(request["publication-qualification-path"]);
|
|
33
|
+
const token = request["token"];
|
|
34
|
+
const publicationTarget = resolvePublicationTarget({
|
|
35
|
+
recoveryReceiptPath: request["recovery-receipt-path"],
|
|
36
|
+
candidate,
|
|
37
|
+
repository,
|
|
38
|
+
channel,
|
|
39
|
+
sourceSha: declaredSourceSha,
|
|
40
|
+
targetRef: request["target-ref"],
|
|
41
|
+
targetSha: request["target-sha"],
|
|
42
|
+
expectedTransactionId,
|
|
43
|
+
});
|
|
44
|
+
const sourceSha = publicationTarget.sourceSha;
|
|
45
|
+
assertCandidateEvidenceBinding({ candidate, stageCapsules, repository });
|
|
46
|
+
const sourceBinding = await observeProtectedPublicationSource({
|
|
47
|
+
octokit,
|
|
48
|
+
repository,
|
|
49
|
+
protectedSourceSha: sourceSha,
|
|
50
|
+
candidate,
|
|
51
|
+
});
|
|
52
|
+
const providerInputs = resolveCandidateProviderInputs({
|
|
53
|
+
recoveryReceiptPath: request["recovery-receipt-path"],
|
|
54
|
+
artifactKind: request["publish-artifact-kind"] || "npm",
|
|
55
|
+
sealedBundleRoot: request["sealed-bundle-root"],
|
|
56
|
+
sealedBundleManifest: request["sealed-bundle-manifest"],
|
|
57
|
+
requiredArtifactsPath: request["required-artifacts-path"],
|
|
58
|
+
publishPackageMain: request["publish-package-main"],
|
|
59
|
+
});
|
|
60
|
+
const providerRequest = productProviderRequest({
|
|
61
|
+
request,
|
|
62
|
+
actor,
|
|
63
|
+
runId,
|
|
64
|
+
octokit,
|
|
65
|
+
mutationOctokit,
|
|
66
|
+
repository,
|
|
67
|
+
targetRef: publicationTarget.targetRef,
|
|
68
|
+
targetSha: publicationTarget.targetSha,
|
|
69
|
+
candidate,
|
|
70
|
+
candidatePassportPath,
|
|
71
|
+
buildSummaryPath,
|
|
72
|
+
qualification,
|
|
73
|
+
providerInputs,
|
|
74
|
+
});
|
|
75
|
+
const publicationPlan = await planProductPublication(providerRequest, {
|
|
76
|
+
fallbackVersion,
|
|
77
|
+
fallbackTag,
|
|
78
|
+
});
|
|
79
|
+
const documents = await createReleaseDocuments({
|
|
80
|
+
request,
|
|
81
|
+
actor,
|
|
82
|
+
runId,
|
|
83
|
+
repository,
|
|
84
|
+
sourceSha,
|
|
85
|
+
fallbackVersion,
|
|
86
|
+
channel,
|
|
87
|
+
candidate,
|
|
88
|
+
stageCapsules,
|
|
89
|
+
qualification,
|
|
90
|
+
sourceBinding,
|
|
91
|
+
publicationPlan,
|
|
92
|
+
publicationIntent: providerRequest.publicationIntent,
|
|
93
|
+
octokit,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
repository,
|
|
97
|
+
sourceSha,
|
|
98
|
+
token,
|
|
99
|
+
channel,
|
|
100
|
+
qualification,
|
|
101
|
+
providerRequest,
|
|
102
|
+
publicationPlan,
|
|
103
|
+
documents,
|
|
104
|
+
sourceBinding,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -159,7 +159,10 @@ export async function applyProductPublication(request, plan) {
|
|
|
159
159
|
});
|
|
160
160
|
transaction = await executeReleaseTailTransaction(transaction, {
|
|
161
161
|
adapters: runtime.adapters,
|
|
162
|
-
checkpoint: (next) =>
|
|
162
|
+
checkpoint: async (next) => {
|
|
163
|
+
writeReleaseTailTransaction(statePath, next);
|
|
164
|
+
if (request.discussionCheckpoint) await request.discussionCheckpoint("publication", next);
|
|
165
|
+
},
|
|
163
166
|
});
|
|
164
167
|
writeReleaseTailTransaction(statePath, transaction);
|
|
165
168
|
const releaseSha = await runtime.resolveReleaseSha();
|
|
@@ -18,12 +18,16 @@ export async function applyAndSettle({
|
|
|
18
18
|
providerRequest,
|
|
19
19
|
publicationPlan,
|
|
20
20
|
documents,
|
|
21
|
+
observeNode = (_node, effect) => effect(),
|
|
21
22
|
}) {
|
|
23
|
+
providerRequest.discussionCheckpoint = request.discussionCheckpoint;
|
|
22
24
|
let productProviderResult;
|
|
23
25
|
try {
|
|
24
|
-
productProviderResult = await
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
productProviderResult = await observeNode("publication", () =>
|
|
27
|
+
applyProductPublication(
|
|
28
|
+
providerRequest,
|
|
29
|
+
documents.productPublicationPlan,
|
|
30
|
+
),
|
|
27
31
|
);
|
|
28
32
|
} catch (error) {
|
|
29
33
|
if (error.providerProjection)
|
|
@@ -37,26 +41,35 @@ export async function applyAndSettle({
|
|
|
37
41
|
".buildchain/release-tail/product-provider-result.json",
|
|
38
42
|
productProviderResult,
|
|
39
43
|
);
|
|
40
|
-
const result = await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
? [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
const result = await observeNode("github-release", () =>
|
|
45
|
+
publishGitHubReleaseEvidence({
|
|
46
|
+
octokit,
|
|
47
|
+
repository,
|
|
48
|
+
sourceSha: productProviderResult.promotedSha,
|
|
49
|
+
version: documents.version,
|
|
50
|
+
tag: documents.tag,
|
|
51
|
+
channel,
|
|
52
|
+
publishEvidencePath: documents.evidencePath,
|
|
53
|
+
releasePassportPath: documents.passportPath,
|
|
54
|
+
releasePassportOutputDir: path.dirname(documents.passportPath),
|
|
55
|
+
additionalAssetPaths: [
|
|
56
|
+
...request["artifact-paths"],
|
|
57
|
+
...(request.discussionReaderPath ? [request.discussionReaderPath] : []),
|
|
58
|
+
...(request.discussionLocatorPath
|
|
59
|
+
? [request.discussionLocatorPath]
|
|
60
|
+
: []),
|
|
61
|
+
...(providerRequest.publicationIntent.artifactKind === "oci"
|
|
62
|
+
? [".buildchain/release-tail/oci-publication-readback.json"]
|
|
63
|
+
: []),
|
|
64
|
+
],
|
|
65
|
+
statePath: request["state-path"] || ".buildchain/release-tail/state.json",
|
|
66
|
+
qualificationRoot: qualification.receiptRoot,
|
|
67
|
+
failureAfterCapability: request["failure-after-capability"],
|
|
68
|
+
checkpoint: request.discussionCheckpoint
|
|
69
|
+
? (state) => request.discussionCheckpoint("github-release", state)
|
|
70
|
+
: undefined,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
60
73
|
const releaseReceipt = createReleaseReceipt({
|
|
61
74
|
schema: RELEASE_RECEIPT_CONTRACT,
|
|
62
75
|
transactionRoot: documents.releaseTransaction.transactionRoot,
|