@kungfu-tech/buildchain 4.1.3-alpha.3 → 4.1.3-alpha.4
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/architecture/agent-change-map.md +6 -0
- package/architecture/maintainability-debt.json +7 -1
- package/architecture/maintainability-policy.json +5 -5
- package/architecture/release-topology.json +2 -1
- package/dist/site/buildchain-contract.json +2 -2
- package/dist/site/buildchain-site.json +4 -4
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/public-surface-audit.json +1 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +4 -4
- package/dist/site/workflow-registry.json +4 -4
- package/package.json +1 -1
- package/packages/core/consumer/contract/plan.js +5 -2
- package/packages/core/providers/github/discussions/materials.js +12 -4
- package/packages/core/providers/github/pipeline-policy.js +44 -17
- package/packages/core/workflow/pipeline/build-result.js +8 -0
- package/packages/core/workflow/pipeline/channel-control.js +10 -1
- package/packages/core/workflow/pipeline/channel-source.js +53 -0
- package/packages/core/workflow/pipeline/group-control.js +2 -0
- package/packages/core/workflow/pipeline/host.js +24 -16
- package/packages/core/workflow/pipeline/selection.js +9 -4
|
@@ -137,6 +137,12 @@ Safe change route: Reduce one declared responsibility in its owning module, run
|
|
|
137
137
|
- `.github/workflows/.release-binary-assets.yml`
|
|
138
138
|
- `.github/workflows/public-ops-dev-auto-merge.yml`
|
|
139
139
|
- `tests/release-topology.test.mjs`
|
|
140
|
+
- `scripts/check-release-topology.mjs`
|
|
141
|
+
- `packages/core/providers/github/discussions/materials.js`
|
|
142
|
+
- `.github/workflows/self-build-alpha-dogfood.yml`
|
|
143
|
+
- `.github/workflows/self-build-binary-distribution.yml`
|
|
144
|
+
- `.github/workflows/self-ops-bootstrap-dogfood.yml`
|
|
145
|
+
- `packages/core/governance/buildchain-publication-authority.js`
|
|
140
146
|
|
|
141
147
|
## action-process-runtime
|
|
142
148
|
|
|
@@ -2247,7 +2247,13 @@
|
|
|
2247
2247
|
".github/workflows/self-release-promote.yml",
|
|
2248
2248
|
".github/workflows/.release-binary-assets.yml",
|
|
2249
2249
|
".github/workflows/public-ops-dev-auto-merge.yml",
|
|
2250
|
-
"tests/release-topology.test.mjs"
|
|
2250
|
+
"tests/release-topology.test.mjs",
|
|
2251
|
+
"scripts/check-release-topology.mjs",
|
|
2252
|
+
"packages/core/providers/github/discussions/materials.js",
|
|
2253
|
+
".github/workflows/self-build-alpha-dogfood.yml",
|
|
2254
|
+
".github/workflows/self-build-binary-distribution.yml",
|
|
2255
|
+
".github/workflows/self-ops-bootstrap-dogfood.yml",
|
|
2256
|
+
"packages/core/governance/buildchain-publication-authority.js"
|
|
2251
2257
|
],
|
|
2252
2258
|
"burnDownSlices": [
|
|
2253
2259
|
{
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repositoryBudgets": {
|
|
28
28
|
"baselineRevision": "9569f69c2cb609372b8a3d65ce5dec8fb7b34a84",
|
|
29
|
-
"maxHandMaintainedSourceFiles":
|
|
30
|
-
"maxHandMaintainedSourceLines":
|
|
29
|
+
"maxHandMaintainedSourceFiles": 1472,
|
|
30
|
+
"maxHandMaintainedSourceLines": 217902,
|
|
31
31
|
"maxWorkflowFiles": 73,
|
|
32
32
|
"maxWorkflowLines": 10457,
|
|
33
|
-
"latestTransition": "
|
|
34
|
-
"latestTransitionRationale": "Charge
|
|
33
|
+
"latestTransition": "Permission-safe pipeline policy and premerge channel source qualification.",
|
|
34
|
+
"latestTransitionRationale": "Charge one module and 123 measured implementation lines for exact version-blob and Rust-owned channel qualification before required checks and queue admission, viewer-enforced policy readback with check App identity, partitioned material retention, and the six documented task branch prefixes. Existing self workflows remain in place until the repaired runtime is published. Per-file, function, complexity, debt and exception ceilings remain unchanged. This aggregate budget change remains proposed until independent protected review.",
|
|
35
35
|
"rationale": "Bounded modules separate candidate, evidence, native proof and execution, canonical failure verification, closed-set execution transfer, process-boundary verification, Warrant state and qualification, authority state, qualification, Landing acquisition, verified-attempt admission, Landing settlement and fence validation, provider attempt and heartbeat readback, sealed terminal evidence, hosted heartbeat, resume, store, options, command-specific adapters, and CLI orchestration responsibilities. The protected delivery workflow gives candidate and seal jobs no provider write credential, recursively rejects transfer drift, scopes every transfer artifact to the exact provider run attempt, chains every durable heartbeat state and receipt root on a distinct hosted runner domain, and permits qualification or exact failure settlement only after the finalizer verifies live jobs and the latest durable authority state. Public Landing admission and heartbeat always perform non-injectable fresh exact-attempt provider readback, tolerate only a not-yet-materialized seal job while retaining exact native and duplicate-job rejection, and heartbeat-loss coordination never invokes GitHub's run-scoped cancellation API where a successor rerun could race. The hosted process boundary anchors trust in the kernel-resolved versioned Runner.Worker path while accepting an equivalent argv-zero spelling with the same basename; its public boundary marker is allowed only at the one exact non-secret value, so a forged value under the same credential-like name still fails closed. The credentialless transfer seal validates the rooted native reuse decision against the Warrant-bound source, closure, dependency, toolchain, environment and command inputs; qualifiedBase stays in the proof and decision where it is rooted and later independently rechecked, rather than being assumed on the Warrant schema. V2 mutation cannot initialize a second empty authority beside a legacy ref: migration reads and roots the canonical live v1 ref, rechecks it, applies the explicit configured v2 policy, and atomically replaces its bytes through one expected-old non-force update, so old v1 controllers fail closed while the prior commit remains immutable history. Candidate ancestry classifies the GitHub artifact runtime token as credential authority. Every Landing settlement, including an unexpired fence, requires product-owned terminal readback. Terminal cleanup reads the immutable historical attempt, derives workflow and hosted Landing job identity from live run data, reads the exact protected ref head and compares containment against that immutable SHA, and seals merged only when the admitted merge-group head is contained by that protected base; a later different-head merge settles the old attempt as dequeued. Terminal normalization preserves transfer, finalizer boundary, native and seal job, admitted provider attempt, protected-base head, containment result, and terminal readback coordinates. The public reusable workflow truthfully retains its single-flight v1 authority without advertising v2 migration, while bounded v2 remains a separate opt-in CLI, Node API, and schema contract; the tracked self-caller stays on public v4-alpha with matching stable and alpha locks, and transient train selection is dispatch-only. Next-development parity adds one versioned transition authority, one durable controller, one bounded reservation adapter, and pure public-contract recovery evidence while retaining the existing thin floating-channel callers byte-for-byte. The floating-consumer gate reuses the existing contract-world evaluator so SHA drift passes only under the selected lock's compatibility policy, breaking surface drift still fails closed, and external certification reconstructs the receipt from the observed workflow SHA rather than the lock's historical evidence SHA. Tail reseal adds no Stage Capsule effect or general rebuild authority: it verifies the exact retained four-platform bytes, admits one known macOS tail failure through live provider readback, fences signing and finalization to explicit credentials and Warrant identity, and requires standard candidate Passport plus provider and protected readback. Compatibility Facts parity isolates canonical encoding, temporal verification, lifecycle validation, fixture projection, public authority projection, and CLI adaptation into bounded modules; legacy digest and proof arrays are derived only from exact current Fact lineage. Publication Rehearsal adds no production release authority: the content-addressed capsule contract binds source, candidate, manifest, config, canonical data-only provider bindings, provider policy, observations and core version separately from filesystem, runtime and rehearsal-only provider authority; simulate, replay and explicitly authorized provider rehearsal all reuse the release-tail planner, while same-commit local dogfood resolves the new reusable contract and exact current runtime before external callers remain on floating v4-alpha. Cross-runtime candidate recovery preserves original runtime and provider roots and creates resume lineage only after exact public ref, tag, ancestry, npm version and integrity readback. Adopter Delivery restores the reviewed public v3 gate, drivers, Passport binding, N-1 bootstrap and exact archive loader through one public v4 surface while keeping provider and release authority explicit non-claims. Floating Alpha bootstrap uses only the existing exact resolver and protected Alpha private shell to move an otherwise stale floating shell, retains public v4 stable promotion, and removes itself after protected readback. CI lane change-budget enforcement adds a bounded parser, evaluator, guard, and tests without creating another merge authority. Minimal consumer contract adds exactly seven bounded contract modules, one generated-contract gate and six product build/test example sources; the source-file ceiling increases by fourteen for this reviewed feature slice. Existing per-file, function, complexity and debt limits are unchanged. The final readable template sources require exact source-line and total-automation ceilings of 203394 and 224451 respectively; this charges all generated example sources rather than hiding them from measurement.",
|
|
36
36
|
"governance": "legacy-debt-sunset-2026q4",
|
|
37
37
|
"maxActionDefinitions": 283,
|
|
38
38
|
"maxActionDefinitionLines": 12399,
|
|
39
|
-
"maxAutomationImplementationLines":
|
|
39
|
+
"maxAutomationImplementationLines": 239900
|
|
40
40
|
},
|
|
41
41
|
"sourceBudgets": {
|
|
42
42
|
"newFileLines": 600,
|
|
@@ -386,6 +386,7 @@
|
|
|
386
386
|
"packages/core/workflow/pipeline/build-qualification.js",
|
|
387
387
|
"packages/core/workflow/pipeline/build.js",
|
|
388
388
|
"packages/core/workflow/pipeline/channel-control.js",
|
|
389
|
+
"packages/core/workflow/pipeline/channel-source.js",
|
|
389
390
|
"packages/core/workflow/pipeline/delivery-observation.js",
|
|
390
391
|
"packages/core/workflow/pipeline/events.js",
|
|
391
392
|
"packages/core/workflow/pipeline/fence.js",
|
|
@@ -410,7 +411,7 @@
|
|
|
410
411
|
"packages/core/workflow/pipeline/wake.js",
|
|
411
412
|
"packages/core/workflow/pipeline/web-status.js"
|
|
412
413
|
],
|
|
413
|
-
"root": "sha256:
|
|
414
|
+
"root": "sha256:b474be097d7b1455785cf78247c966601a2adfb03ff1baee9cc009f26b9c2ac1"
|
|
414
415
|
},
|
|
415
416
|
"rustWasmAuthority": {
|
|
416
417
|
"loader": "packages/core/runtime/domain-wasm.js",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"product": {
|
|
5
5
|
"name": "Buildchain",
|
|
6
6
|
"package": "@kungfu-tech/buildchain",
|
|
7
|
-
"version": "4.1.3-alpha.
|
|
7
|
+
"version": "4.1.3-alpha.4",
|
|
8
8
|
"repository": "https://github.com/kungfu-systems/buildchain"
|
|
9
9
|
},
|
|
10
10
|
"majorLine": "v4",
|
|
@@ -2410,5 +2410,5 @@
|
|
|
2410
2410
|
"compatibilityFactCutRoot": "sha256:58f33e498596505a26b6fc3c7ae5acef847ff699eee6c7e17a7b318a40bdf0d6",
|
|
2411
2411
|
"compatibilityProofRegistryRoot": "sha256:2f6477405c7891b754b33ad6515d55618bd320e7cdc59330d7cc645e17201e72",
|
|
2412
2412
|
"compatibilityDigest": "sha256:14368a3f99f38a8c9c1e053f7ec3651766e3535237f4c551cc75417cf7ef97b3",
|
|
2413
|
-
"contractDigest": "sha256:
|
|
2413
|
+
"contractDigest": "sha256:08573fc38a476e8ba195f8ce48bd6b451e62a56aa093a02d929bcc081aa4015f"
|
|
2414
2414
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-bundle",
|
|
4
|
-
"generatedAt": "2026-09-
|
|
5
|
-
"publishedAt": "2026-09-
|
|
4
|
+
"generatedAt": "2026-09-13T11:18:20.000Z",
|
|
5
|
+
"publishedAt": "2026-09-13T11:18:20.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "5d19bfb4c25e83116f3bbd09a11dda8792bab7f7",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "4.1.3-alpha.
|
|
40
|
+
"version": "4.1.3-alpha.4",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"source": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
22
22
|
"path": "dist/site/public-surface-audit.json",
|
|
23
23
|
"status": "passed",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "0961a448474a107d8a0361e2f691f8f866da52a3d8b30db2edb616c6e737ebea",
|
|
25
25
|
"summary": {
|
|
26
26
|
"cliCommandCount": 117,
|
|
27
27
|
"workflowCount": 73,
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
247
247
|
"path": "dist/site/public-surface-audit.json",
|
|
248
248
|
"status": "passed",
|
|
249
|
-
"sha256": "
|
|
249
|
+
"sha256": "0961a448474a107d8a0361e2f691f8f866da52a3d8b30db2edb616c6e737ebea",
|
|
250
250
|
"summary": {
|
|
251
251
|
"cliCommandCount": 117,
|
|
252
252
|
"workflowCount": 73,
|
|
@@ -11128,7 +11128,7 @@
|
|
|
11128
11128
|
"workflowRegistryPath": "dist/site/workflow-registry.json",
|
|
11129
11129
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
11130
11130
|
"cliRegistryDigest": "e43fdb0289df88871f3f0febbe04dcb9311a252a2b1df9b3a83a2d10c9d4a70f",
|
|
11131
|
-
"workflowRegistryDigest": "
|
|
11131
|
+
"workflowRegistryDigest": "4e97a606f26351e0f946b4f455edb7017b54b6d16bb362d7ca91bf690a289666",
|
|
11132
11132
|
"pageRegistryDigest": "71e5bb99567bf82b2c896da95f83197b58fad84e35ef54f5e9e4dc0cc3b7e465"
|
|
11133
11133
|
},
|
|
11134
11134
|
"comparison": {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-publication-release-registry",
|
|
4
|
-
"generatedAt": "2026-09-
|
|
5
|
-
"publishedAt": "2026-09-
|
|
4
|
+
"generatedAt": "2026-09-13T11:18:20.000Z",
|
|
5
|
+
"publishedAt": "2026-09-13T11:18:20.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "5d19bfb4c25e83116f3bbd09a11dda8792bab7f7",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"package": {
|
|
34
34
|
"name": "@kungfu-tech/buildchain",
|
|
35
|
-
"version": "4.1.3-alpha.
|
|
35
|
+
"version": "4.1.3-alpha.4",
|
|
36
36
|
"versionSource": "package.json#version"
|
|
37
37
|
},
|
|
38
38
|
"sourceKind": "package-site-bundle",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-09-
|
|
5
|
-
"publishedAt": "2026-09-
|
|
4
|
+
"generatedAt": "2026-09-13T11:18:20.000Z",
|
|
5
|
+
"publishedAt": "2026-09-13T11:18:20.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "5d19bfb4c25e83116f3bbd09a11dda8792bab7f7",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "4.1.3-alpha.
|
|
40
|
+
"version": "4.1.3-alpha.4",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -1201,7 +1201,7 @@
|
|
|
1201
1201
|
"status": "preview",
|
|
1202
1202
|
"owner": "buildchain-workflows",
|
|
1203
1203
|
"maturity": "preview",
|
|
1204
|
-
"introducedVersion": "4.1.3-alpha.
|
|
1204
|
+
"introducedVersion": "4.1.3-alpha.4",
|
|
1205
1205
|
"compatibilityPromise": "current-contract-only",
|
|
1206
1206
|
"deprecationReplacement": "",
|
|
1207
1207
|
"sunsetCondition": "explicit-contract-change-review",
|
|
@@ -2699,7 +2699,7 @@
|
|
|
2699
2699
|
"status": "preview",
|
|
2700
2700
|
"owner": "buildchain-workflows",
|
|
2701
2701
|
"maturity": "preview",
|
|
2702
|
-
"introducedVersion": "4.1.3-alpha.
|
|
2702
|
+
"introducedVersion": "4.1.3-alpha.4",
|
|
2703
2703
|
"compatibilityPromise": "current-contract-only",
|
|
2704
2704
|
"deprecationReplacement": "",
|
|
2705
2705
|
"sunsetCondition": "explicit-contract-change-review",
|
|
@@ -2729,7 +2729,7 @@
|
|
|
2729
2729
|
"status": "preview",
|
|
2730
2730
|
"owner": "buildchain-workflows",
|
|
2731
2731
|
"maturity": "preview",
|
|
2732
|
-
"introducedVersion": "4.1.3-alpha.
|
|
2732
|
+
"introducedVersion": "4.1.3-alpha.4",
|
|
2733
2733
|
"compatibilityPromise": "current-contract-only",
|
|
2734
2734
|
"deprecationReplacement": "",
|
|
2735
2735
|
"sunsetCondition": "explicit-contract-change-review",
|
|
@@ -2759,7 +2759,7 @@
|
|
|
2759
2759
|
"status": "preview",
|
|
2760
2760
|
"owner": "buildchain-workflows",
|
|
2761
2761
|
"maturity": "preview",
|
|
2762
|
-
"introducedVersion": "4.1.3-alpha.
|
|
2762
|
+
"introducedVersion": "4.1.3-alpha.4",
|
|
2763
2763
|
"compatibilityPromise": "current-contract-only",
|
|
2764
2764
|
"deprecationReplacement": "",
|
|
2765
2765
|
"sunsetCondition": "explicit-contract-change-review",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "4.1.3-alpha.
|
|
3
|
+
"version": "4.1.3-alpha.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -30,7 +30,7 @@ function compileChannels(values) {
|
|
|
30
30
|
text(
|
|
31
31
|
value.from,
|
|
32
32
|
`${field}.from`,
|
|
33
|
-
/^(?:feature|dev|alpha|release)\/[A-Za-z0-9/*._-]+$/u,
|
|
33
|
+
/^(?:feature|fix|chore|docs|ci|refactor|dev|alpha|release)\/[A-Za-z0-9/*._-]+$/u,
|
|
34
34
|
);
|
|
35
35
|
text(
|
|
36
36
|
value.to,
|
|
@@ -45,7 +45,10 @@ function compileChannels(values) {
|
|
|
45
45
|
const source = value.from.split("/")[0],
|
|
46
46
|
target = value.to.split("/")[0];
|
|
47
47
|
const legal = {
|
|
48
|
-
develop:
|
|
48
|
+
develop:
|
|
49
|
+
["feature", "fix", "chore", "docs", "ci", "refactor"].includes(
|
|
50
|
+
source,
|
|
51
|
+
) && target === "dev",
|
|
49
52
|
alpha: source === "dev" && target === "alpha",
|
|
50
53
|
stable: source === "alpha" && target === "release",
|
|
51
54
|
major: source === "release" && value.to === "publish-gate/major",
|
|
@@ -10,10 +10,13 @@ export function discussionMaterials({
|
|
|
10
10
|
octokit,
|
|
11
11
|
repository,
|
|
12
12
|
intentId,
|
|
13
|
+
partition = "",
|
|
13
14
|
authorityDescription = "The associated Discussion owns transaction state.",
|
|
14
15
|
}) {
|
|
15
16
|
const [owner, repo] = repository.split("/");
|
|
16
|
-
|
|
17
|
+
if (partition && !/^[1-9][0-9]*-[1-9][0-9]*-[1-9][0-9]*$/u.test(partition))
|
|
18
|
+
throw new Error("Invalid material archive writer partition");
|
|
19
|
+
const tag = `buildchain-records/${intentId.replace("sha256:", "")}${partition ? `/${partition}` : ""}`;
|
|
17
20
|
const repos = octokit.rest.repos;
|
|
18
21
|
let retainedArchive;
|
|
19
22
|
async function findArchive() {
|
|
@@ -32,8 +35,7 @@ export function discussionMaterials({
|
|
|
32
35
|
throw new Error("Ambiguous transaction material archive");
|
|
33
36
|
return matches[0];
|
|
34
37
|
}
|
|
35
|
-
async function
|
|
36
|
-
if (retainedArchive) return retainedArchive;
|
|
38
|
+
async function createArchive() {
|
|
37
39
|
let release = await findArchive();
|
|
38
40
|
if (!release) {
|
|
39
41
|
try {
|
|
@@ -60,9 +62,15 @@ export function discussionMaterials({
|
|
|
60
62
|
}
|
|
61
63
|
if (!release.draft || release.tag_name !== tag)
|
|
62
64
|
throw new Error("Transaction material archive identity mismatch");
|
|
63
|
-
retainedArchive = release;
|
|
64
65
|
return release;
|
|
65
66
|
}
|
|
67
|
+
function archive() {
|
|
68
|
+
// Concurrent puts in one writer share the same creation, including readback.
|
|
69
|
+
return (retainedArchive ||= createArchive().catch((error) => {
|
|
70
|
+
retainedArchive = undefined;
|
|
71
|
+
throw error;
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
66
74
|
async function read(handle) {
|
|
67
75
|
const response = await repos.getReleaseAsset({
|
|
68
76
|
owner,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
2
|
|
|
3
3
|
const QUERY = `query PipelinePolicy($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){id number headRefOid baseRefName isDraft reviewDecision author{login}}}}`;
|
|
4
|
-
const BRANCH_QUERY = `query PipelineBranchPolicy($owner:String!,$name:String!,$branch:String!,$ref:String!){repository(owner:$owner,name:$name){ref(qualifiedName:$ref){
|
|
4
|
+
const BRANCH_QUERY = `query PipelineBranchPolicy($owner:String!,$name:String!,$branch:String!,$ref:String!){repository(owner:$owner,name:$name){ref(qualifiedName:$ref){refUpdateRule{requiredApprovingReviewCount requiresCodeOwnerReviews requiredStatusCheckContexts}}mergeQueue(branch:$branch){id}}}`;
|
|
5
5
|
|
|
6
6
|
async function classicPolicy(request, owner, name, branch) {
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// Read viewer-enforced rules and the ordinary Contents:read branch projection.
|
|
8
|
+
// Administrative branchProtectionRule and /protection queries are not needed.
|
|
9
9
|
const result = await request("/graphql", {
|
|
10
10
|
method: "POST",
|
|
11
11
|
body: {
|
|
@@ -14,23 +14,50 @@ async function classicPolicy(request, owner, name, branch) {
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
const repository = result.data?.repository;
|
|
17
|
-
if (!repository)
|
|
18
|
-
|
|
17
|
+
if (!repository?.ref)
|
|
18
|
+
throw new Error("Protected branch metadata is unavailable");
|
|
19
|
+
const rule = repository.ref.refUpdateRule;
|
|
20
|
+
const observed = await request(
|
|
21
|
+
`/repos/${owner}/${name}/branches/${encodeURIComponent(branch)}`,
|
|
22
|
+
);
|
|
23
|
+
const status = observed.protection?.required_status_checks;
|
|
24
|
+
const contexts = status?.contexts;
|
|
25
|
+
const checks = status?.checks;
|
|
26
|
+
if (
|
|
27
|
+
observed.name !== branch ||
|
|
28
|
+
!Array.isArray(contexts) ||
|
|
29
|
+
!Array.isArray(checks) ||
|
|
30
|
+
checks.some(
|
|
31
|
+
(check) =>
|
|
32
|
+
typeof check.context !== "string" ||
|
|
33
|
+
!check.context ||
|
|
34
|
+
!Object.hasOwn(check, "app_id") ||
|
|
35
|
+
!(
|
|
36
|
+
check.app_id === null ||
|
|
37
|
+
check.app_id === -1 ||
|
|
38
|
+
(Number.isSafeInteger(check.app_id) && check.app_id > 0)
|
|
39
|
+
),
|
|
40
|
+
) ||
|
|
41
|
+
contexts.some(
|
|
42
|
+
(context) => !checks.some((check) => check.context === context),
|
|
43
|
+
) ||
|
|
44
|
+
checks.some((check) => !contexts.includes(check.context)) ||
|
|
45
|
+
(rule?.requiredStatusCheckContexts || []).some(
|
|
46
|
+
(context) => !contexts.includes(context),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
throw new Error("Protected branch check identity metadata is incomplete");
|
|
19
50
|
return {
|
|
20
51
|
required_merge_queue: Boolean(repository.mergeQueue?.id),
|
|
21
|
-
required_pull_request_reviews:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
required_pull_request_reviews:
|
|
53
|
+
rule?.requiredApprovingReviewCount > 0
|
|
54
|
+
? {
|
|
55
|
+
require_code_owner_reviews: rule.requiresCodeOwnerReviews,
|
|
56
|
+
required_approving_review_count: rule.requiredApprovingReviewCount,
|
|
57
|
+
}
|
|
58
|
+
: null,
|
|
27
59
|
required_status_checks: {
|
|
28
|
-
checks
|
|
29
|
-
? (rule.requiredStatusChecks || []).map((check) => ({
|
|
30
|
-
context: check.context,
|
|
31
|
-
app_id: check.app?.databaseId || null,
|
|
32
|
-
}))
|
|
33
|
-
: [],
|
|
60
|
+
checks,
|
|
34
61
|
},
|
|
35
62
|
};
|
|
36
63
|
}
|
|
@@ -37,6 +37,14 @@ export async function retainPipelineBuildResult(
|
|
|
37
37
|
session,
|
|
38
38
|
host,
|
|
39
39
|
) {
|
|
40
|
+
if (
|
|
41
|
+
readback.outcome === "success" &&
|
|
42
|
+
session.intent.expectedNodes.includes("publish")
|
|
43
|
+
)
|
|
44
|
+
await host.qualifyChannel(
|
|
45
|
+
context.source,
|
|
46
|
+
session.intent.source.targetBranch,
|
|
47
|
+
);
|
|
40
48
|
const reference = await resultReference(context, readback, session, host);
|
|
41
49
|
await session.progress.progress({
|
|
42
50
|
attempt: context.attempt,
|
|
@@ -68,6 +68,10 @@ export async function controlPipelineChannel(session, admission, inputs, host) {
|
|
|
68
68
|
}
|
|
69
69
|
if (host.terminalOnly)
|
|
70
70
|
return { operation: "wait", reason: "terminal-event-cannot-execute" };
|
|
71
|
+
const qualification = await host.qualifyChannel(
|
|
72
|
+
live.source,
|
|
73
|
+
live.targetBranch,
|
|
74
|
+
);
|
|
71
75
|
const build = await pipelineBuildEvidence(session, host);
|
|
72
76
|
if (!build) return { operation: "build", admission };
|
|
73
77
|
if (build.run.status !== "completed" || build.run.conclusion !== "success")
|
|
@@ -97,7 +101,12 @@ export async function controlPipelineChannel(session, admission, inputs, host) {
|
|
|
97
101
|
session,
|
|
98
102
|
"merge",
|
|
99
103
|
"waiting",
|
|
100
|
-
{
|
|
104
|
+
{
|
|
105
|
+
schema: "buildchain.pipeline-channel-enqueue/v1",
|
|
106
|
+
qualification,
|
|
107
|
+
policy,
|
|
108
|
+
queue,
|
|
109
|
+
},
|
|
101
110
|
host,
|
|
102
111
|
);
|
|
103
112
|
const again = await host.source.observeIntent(
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { selectProductPublicationIntent } from "../../release/product-publication.js";
|
|
3
|
+
import { pipelineExpectedProducts } from "../../publication/pipeline/plan.js";
|
|
4
|
+
import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
|
|
5
|
+
|
|
6
|
+
// This read-only qualification must precede both required checks and queue
|
|
7
|
+
// admission. Publication repeats the Rust-owned decision after protected merge.
|
|
8
|
+
export async function qualifyPipelineChannelSource(source, branch, host) {
|
|
9
|
+
if (branch.startsWith("dev/")) return null;
|
|
10
|
+
const admitted = await host.source.source(source.commit, source.configPath);
|
|
11
|
+
if (recordDigest(admitted.identity) !== recordDigest(source))
|
|
12
|
+
throw new Error("Channel qualification source identity drift");
|
|
13
|
+
const { plan } = admitted;
|
|
14
|
+
const routes = plan.channels.filter(
|
|
15
|
+
(route) => route.to === branch && route.operation !== "develop",
|
|
16
|
+
);
|
|
17
|
+
if (routes.length !== 1)
|
|
18
|
+
throw new Error("Channel qualification requires one publication route");
|
|
19
|
+
const route = routes[0];
|
|
20
|
+
const from = `${route.operation === "alpha" ? "dev" : "alpha"}/${branch.split("/").slice(1).join("/")}`;
|
|
21
|
+
if (["alpha", "stable"].includes(route.operation) && route.from !== from)
|
|
22
|
+
throw new Error("Channel source and target version lines must match");
|
|
23
|
+
const version = await githubPipelineVersion(
|
|
24
|
+
host.request,
|
|
25
|
+
host.repository,
|
|
26
|
+
).inspect(source, plan.version);
|
|
27
|
+
const selection = selectProductPublicationIntent({
|
|
28
|
+
channel: route.operation === "alpha" ? "alpha" : "stable",
|
|
29
|
+
targetRef: branch,
|
|
30
|
+
sourceSha: source.commit,
|
|
31
|
+
sourceTimestamp: version.sourceTimestamp,
|
|
32
|
+
repository: source.repository,
|
|
33
|
+
artifactKind: "custom",
|
|
34
|
+
requiredArtifactsRoot: recordDigest(pipelineExpectedProducts(plan)),
|
|
35
|
+
candidateVersion: version.version,
|
|
36
|
+
observedVersions: [],
|
|
37
|
+
});
|
|
38
|
+
if (
|
|
39
|
+
plan.version.strategy === "anchored" &&
|
|
40
|
+
selection.version !== version.version
|
|
41
|
+
)
|
|
42
|
+
throw new Error("Anchored channel source requires a materialized version");
|
|
43
|
+
const body = {
|
|
44
|
+
schema: "buildchain.pipeline-channel-source-qualification/v1",
|
|
45
|
+
source,
|
|
46
|
+
route,
|
|
47
|
+
contractRoot: recordDigest(plan),
|
|
48
|
+
version: version.version,
|
|
49
|
+
versionFilesRoot: recordDigest(version.files),
|
|
50
|
+
selection,
|
|
51
|
+
};
|
|
52
|
+
return { ...body, root: recordDigest(body) };
|
|
53
|
+
}
|
|
@@ -36,6 +36,8 @@ export async function recordPipelineGroup(context, host) {
|
|
|
36
36
|
context.source,
|
|
37
37
|
context.platforms.map((platform) => platform.platform),
|
|
38
38
|
);
|
|
39
|
+
if (readback.outcome === "success")
|
|
40
|
+
await host.qualifyChannel(context.source, context.branch);
|
|
39
41
|
await host.request(`/repos/${host.repository}/check-runs`, {
|
|
40
42
|
method: "POST",
|
|
41
43
|
body: {
|
|
@@ -17,6 +17,7 @@ import { GitHubClient } from "../../dev-delivery/admission/github-client.js";
|
|
|
17
17
|
import { githubPipelineIntegration } from "../../providers/github/pipeline-integration.js";
|
|
18
18
|
import { recordPipelineGroup } from "./group-control.js";
|
|
19
19
|
import { controlPipelineChannel } from "./channel-control.js";
|
|
20
|
+
import { qualifyPipelineChannelSource } from "./channel-source.js";
|
|
20
21
|
import { settlePipeline } from "./settlement.js";
|
|
21
22
|
import { pipelineProjection } from "./projection.js";
|
|
22
23
|
import { resumePipelineNotifications } from "./notifications.js";
|
|
@@ -48,6 +49,24 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
48
49
|
const writer = await runs.writer(runId, runAttempt, jobName);
|
|
49
50
|
const source = githubPipelineSource(request, repository);
|
|
50
51
|
const [owner, repo] = repository.split("/");
|
|
52
|
+
const archives = new Map();
|
|
53
|
+
function archive(session) {
|
|
54
|
+
if (!archives.has(session.intent.id))
|
|
55
|
+
archives.set(
|
|
56
|
+
session.intent.id,
|
|
57
|
+
discussionMaterials({
|
|
58
|
+
octokit: github,
|
|
59
|
+
repository,
|
|
60
|
+
intentId: session.intent.id,
|
|
61
|
+
// Storage is partitioned by verified provider job, not attempt authority.
|
|
62
|
+
// Journal references still select exact immutable asset IDs and digests.
|
|
63
|
+
partition: `${writer.runId}-${writer.runAttempt}-${writer.jobId}`,
|
|
64
|
+
authorityDescription:
|
|
65
|
+
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
return archives.get(session.intent.id);
|
|
69
|
+
}
|
|
51
70
|
const host = {
|
|
52
71
|
repository,
|
|
53
72
|
token,
|
|
@@ -70,6 +89,8 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
70
89
|
groupRecord: (context) => recordPipelineGroup(context, host),
|
|
71
90
|
channel: (session, admission, inputs) =>
|
|
72
91
|
controlPipelineChannel(session, admission, inputs, host),
|
|
92
|
+
qualifyChannel: (source, branch) =>
|
|
93
|
+
qualifyPipelineChannelSource(source, branch, host),
|
|
73
94
|
settle: (session, fresh, delivery) =>
|
|
74
95
|
settlePipeline(session, fresh, delivery, host),
|
|
75
96
|
project: (session) =>
|
|
@@ -81,24 +102,11 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
81
102
|
recoverPublication: (session, admitted) =>
|
|
82
103
|
inspectRecoveryPublication(session, admitted, host),
|
|
83
104
|
materialStore: (session) =>
|
|
84
|
-
pipelineMaterials(
|
|
85
|
-
discussionMaterials({
|
|
86
|
-
octokit: github,
|
|
87
|
-
repository,
|
|
88
|
-
intentId: session.intent.id,
|
|
89
|
-
authorityDescription:
|
|
90
|
-
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
91
|
-
}),
|
|
92
|
-
{ repository, attempt: session.observed.history.at(-1).identity },
|
|
93
|
-
),
|
|
94
|
-
productArchive: (session) =>
|
|
95
|
-
discussionMaterials({
|
|
96
|
-
octokit: github,
|
|
105
|
+
pipelineMaterials(archive(session), {
|
|
97
106
|
repository,
|
|
98
|
-
|
|
99
|
-
authorityDescription:
|
|
100
|
-
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
107
|
+
attempt: session.observed.history.at(-1).identity,
|
|
101
108
|
}),
|
|
109
|
+
productArchive: archive,
|
|
102
110
|
wake: async (attempt) => {
|
|
103
111
|
await index.resolve(attempt);
|
|
104
112
|
await request(`/repos/${repository}/dispatches`, {
|
|
@@ -10,8 +10,12 @@ async function successorSelection(
|
|
|
10
10
|
inputs,
|
|
11
11
|
host,
|
|
12
12
|
) {
|
|
13
|
+
const failedBuild =
|
|
14
|
+
session.observed.status === "failure" &&
|
|
15
|
+
session.observed.phases.build?.payload.state === "failure";
|
|
13
16
|
if (
|
|
14
|
-
!
|
|
17
|
+
(!failedBuild &&
|
|
18
|
+
!["superseded", "cancelled"].includes(session.observed.status)) ||
|
|
15
19
|
event.terminalOnly ||
|
|
16
20
|
admission.live.state !== "open" ||
|
|
17
21
|
admission.live.targetBranch !== session.intent.source.targetBranch
|
|
@@ -27,9 +31,10 @@ async function successorSelection(
|
|
|
27
31
|
.id !== session.observed.generation;
|
|
28
32
|
if (
|
|
29
33
|
!changed &&
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
(failedBuild ||
|
|
35
|
+
!["reopened", "enqueued", "ready_for_review", "labeled"].includes(
|
|
36
|
+
payload.action,
|
|
37
|
+
))
|
|
33
38
|
)
|
|
34
39
|
return { session, admission };
|
|
35
40
|
await host.notifyTerminal?.(session);
|