Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/pylon-preview-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
- name: Verify and prepare six exact subjects
run: |
npm run release:pylon:verify
npm run release:pylon:preview -- --publication-policy-revision 2
npm run release:pylon:preview -- --publication-policy-revision 3
npm run release:pylon:verify-preview

- name: Upload isolated preview subjects
Expand Down Expand Up @@ -281,7 +281,7 @@ jobs:
release.source?.commit !== context.sha || release.source?.tree !== preview.build?.source?.tree ||
release.build?.id !== tag || preview.build?.tag !== tag ||
preview.build?.releaseManifest?.sha256 !== crypto.createHash("sha256").update(releaseBytes).digest("hex") ||
preview.publicationPolicyRevision !== 2 || preview.sequenceEpoch !== 1 ||
preview.publicationPolicyRevision !== 3 || preview.sequenceEpoch !== 1 ||
preview.sequence !== Number(process.env.GITHUB_RUN_NUMBER) || preview.workflowRunId !== process.env.GITHUB_RUN_ID
) throw new Error("Preview tag plan is not bound to the exact source and workflow sequence.");
core.setOutput("tag", tag);
Expand Down Expand Up @@ -469,7 +469,7 @@ jobs:
if (
release.source?.commit !== context.sha || release.source?.tree !== preview.build?.source?.tree ||
release.build?.id !== tag || preview.build?.tag !== tag || preview.build?.releaseManifest?.sha256 !== sha256(releaseBytes) ||
preview.publicationPolicyRevision !== 2 || preview.sequenceEpoch !== 1 ||
preview.publicationPolicyRevision !== 3 || preview.sequenceEpoch !== 1 ||
preview.sequence !== Number(process.env.GITHUB_RUN_NUMBER) || preview.workflowRunId !== process.env.GITHUB_RUN_ID
) throw new Error("Preview draft manifests do not bind the exact source and workflow sequence.");
const expectedNames = [...release.assets.map((asset) => asset.file), "pylon-prime-agent-release-v1.json", "pylon-preview-channel-v1.json"].sort();
Expand Down Expand Up @@ -552,7 +552,7 @@ jobs:
await requireLivePylon();
}
for (const asset of missing) {
await github.request("POST /repos/{owner}/{repo}/releases/{release_id}/assets", {
await github.rest.repos.uploadReleaseAsset({
owner, repo, release_id: draft.id, name: asset.name, data: asset.bytes,
headers: { "content-type": "application/octet-stream", "content-length": asset.size },
});
Expand Down Expand Up @@ -795,7 +795,7 @@ jobs:
previewManifest.build.source.commit !== sourceSha ||
previewManifest.build.source.tree !== releaseManifest.source.tree ||
previewManifest.build.releaseManifest.sha256 !== sha256(releaseBytes) ||
previewManifest.publicationPolicyRevision !== 2 || previewManifest.sequenceEpoch !== 1 ||
previewManifest.publicationPolicyRevision !== 3 || previewManifest.sequenceEpoch !== 1 ||
previewManifest.sequence !== Number(process.env.GITHUB_RUN_NUMBER) || previewManifest.workflowRunId !== process.env.GITHUB_RUN_ID
) {
throw new Error("Downloaded preview metadata is not bound to this exact push and workflow sequence.");
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/pylon-stable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ jobs:
--operation "$OPERATION"
--policy-sha "${{ github.sha }}"
--policy-tree "$policy_tree"
--publication-policy-revision 2
--publication-policy-revision 3
)
if [ "$OPERATION" = withdraw ]; then
args+=(--revoke-tag "$REVOKE_STABLE_TAG" --reason "$REASON")
Expand Down Expand Up @@ -557,7 +557,7 @@ jobs:
if (
manifest.schemaVersion !== 1 || manifest.channel !== "stable" ||
manifest.repository !== "https://github.com/pylon-code/prime-agent" ||
manifest.promotion?.publicationPolicyRevision !== 2 ||
manifest.promotion?.publicationPolicyRevision !== 3 ||
!/^pylon-stable-[0-9]{6}-g[0-9a-f]{12}-r[1-9][0-9]*$/.test(manifest.tag)
) throw new Error("Stable manifest identity is malformed.");
NODE
Expand Down Expand Up @@ -663,8 +663,8 @@ jobs:
if (
!parsed || !preview || Number(parsed[1]) !== manifest.sequence || parsed[2] !== manifest.build.source?.commit?.slice(0, 12) ||
Number(parsed[3]) !== manifest.build.recipeRevision || preview[1] !== parsed[2] || Number(preview[2]) !== manifest.build.recipeRevision ||
manifest.build.previewTag !== manifest.build.id || ![1, 2].includes(manifest.build.publicationPolicyRevision) ||
manifest.promotion?.policyCommit !== context.sha || manifest.promotion?.publicationPolicyRevision !== 2
manifest.build.previewTag !== manifest.build.id || ![1, 2, 3].includes(manifest.build.publicationPolicyRevision) ||
manifest.promotion?.policyCommit !== context.sha || manifest.promotion?.publicationPolicyRevision !== 3
) throw new Error("Stable draft identity is malformed or not signed by this policy commit.");
const name = `Pylon Prime stable ${tag}`;
const encoded = bytes.toString("base64");
Expand Down Expand Up @@ -712,7 +712,7 @@ jobs:
(asset && (asset.name !== "pylon-stable-channel-v1.json" || asset.size !== bytes.length || asset.digest !== `sha256:${digest}`))
) throw new Error("Existing stable draft identity or asset differs.");
if (!asset) {
await github.request("POST /repos/{owner}/{repo}/releases/{release_id}/assets", {
await github.rest.repos.uploadReleaseAsset({
owner, repo, release_id: draft.id, name: "pylon-stable-channel-v1.json", data: bytes,
headers: { "content-type": "application/json", "content-length": bytes.length },
});
Expand Down Expand Up @@ -816,7 +816,7 @@ jobs:
let asset = draft.assets[0];
if (asset && asset.name !== "pylon-stable-channel-v1.json") throw new Error("Stable draft singleton asset has an unexpected name.");
if (!asset) {
await github.request("POST /repos/{owner}/{repo}/releases/{release_id}/assets", {
await github.rest.repos.uploadReleaseAsset({
owner, repo, release_id: draft.id, name: "pylon-stable-channel-v1.json", data: bytes,
headers: { "content-type": "application/json", "content-length": bytes.length },
});
Expand All @@ -840,10 +840,10 @@ jobs:
Number(previewMatch[2]) !== manifest.build?.recipeRevision || manifest.build.previewTag !== manifest.build.id ||
manifest.build.previewSequence?.sequenceEpoch !== 1 || !Number.isSafeInteger(manifest.build.previewSequence?.sequence) ||
manifest.build.previewSequence.sequence < 1 || !/^[1-9][0-9]*$/.test(manifest.build.previewSequence?.workflowRunId ?? "") ||
manifest.build.previewTag !== process.env.PREVIEW_TAG || ![1, 2].includes(manifest.build.publicationPolicyRevision) ||
manifest.build.previewTag !== process.env.PREVIEW_TAG || ![1, 2, 3].includes(manifest.build.publicationPolicyRevision) ||
manifest.promotion?.kind !== operation ||
(mode === "normal" ? manifest.promotion?.publicationPolicyRevision !== 2 :
![1, 2].includes(manifest.promotion?.publicationPolicyRevision)) ||
(mode === "normal" ? manifest.promotion?.publicationPolicyRevision !== 3 :
![1, 2, 3].includes(manifest.promotion?.publicationPolicyRevision)) ||
manifest.promotion?.policyCommit !== process.env.POLICY_SHA || manifest.promotion?.policyTree !== process.env.POLICY_TREE
) throw new Error("Stable manifest, preview recipe, operator request, or policy identity differs.");
if (operation === "withdraw") {
Expand Down
2 changes: 1 addition & 1 deletion .pylon/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ decisions:
- https://github.com/pylon-code/prime-agent/issues/53
upstream_refs:
- https://github.com/PrimeIntellect-ai/prime-agent/pull/32
fork_change: protected-publication-with-inode-bound-consumer-generations-v3
fork_change: protected-publication-with-inode-bound-consumer-generations-v3-and-upload-policy-3
upstream_support: Prime's inherited publication path targets upstream R2 and npm channels and does not provide canonical Pylon-only exact-SHA admission, six immutable preview subjects, byte-preserving manual promotion, signed monotonic stable history, or append-only withdrawal.
revisit_when:
- Prime ships a repository-neutral immutable release and promotion primitive that preserves Pylon's exact source, workflow, attestation, stable-history, and withdrawal policy without upstream credentials or channel names.
Expand Down
8 changes: 8 additions & 0 deletions .pylon/upstream-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,11 @@ Follow-up: Task10 builds/packs the exact merged tree into a private prefix and r
- Callback staging never publishes before successful return. Exact canonical receipt inodes permit interrupted publication recovery without replaying callbacks. Rotation recovery reconstructs the pre-certificate source when its deterministic rotation claim was linked before the winning index CAS; it excludes only that exact claim and still requires the complete certificate and winning-CAS successor checks. A claim that consumes the remaining heartbeat admission margin is released and rotated before callback entry, preserving recovery at small supported state limits.
- Verification gates retain the protected and historical v2 matrices alongside current-public v3 verifier tests, complete mutation-boundary SIGKILL traces with fresh-process recovery, repeated four-process migration/rotation competition, pinned-read handoffs and last-proof replacement negatives. CI requires separate retained/current-public, exhaustive crash and repeated stress suites followed sequentially by the actual 16 MiB maximum on Ubuntu 24.04 and macOS 15 under Node 22.23.2; both results are mandatory in `build-check-test`. Process crashes do not simulate physical power loss, and resource limits are separately bounded historical inventories, metadata and generation-journal budgets.
- Revisit only when an upstream primitive preserves these exact local authority, bounded-work, migration and no-replay guarantees. Publication still requires current exact-source checks, independent review and every existing protected environment approval; filesystem tests do not authorize a release.

## 2026-09-11 — release asset transport and publication policy 3

- Reviewed upstream compatibility remains v0.9.4 at `1eee2938b4eeb7a4d72e17035adda669a89b63de`; this correction does not advance the frozen integration range. Current upstream issue/PR searches for release asset upload yielded no matching implementation. The inherited `build-binaries.yml` still publishes through R2 and mutable GitHub upload/clobber paths; it does not replace Pylon's immutable transport contract.
- `protected-pylon-publication`: **redesign**, retaining the existing protected workflow and consumer-generation design. An actual preview reached verified six-subject provenance but its first upload used the generic API host and omitted the filename query. All three preview/stable staging and zero-asset recovery uploads now use the pinned client's `repos.uploadReleaseAsset` endpoint, preserving exact bytes, content length, metadata/digest readback and upload-before-reservation order.
- Publication policy 3 appends exact preview/stable workflow digests. Historical policies 1/2 remain immutable, artifact recipe 1 and runtime 0.9.4 are unchanged, and stable recovery still accepts only explicitly registered historical policies. The matching Pylon consumer entry is required before managed installation.
- Validation: all three inline upload regressions fail on the old generic request and pass with the specialized method. Preview covers six binary subjects, complete/partial draft joins, and altered receipt refusal. Stable normal staging retains crash-after-create recovery; zero-asset recovery verifies body bytes and rejects altered metadata/downloads before policy proof or reservation. An independent offline probe against the exact pinned action bundle proves the upload host, filename encoding and binary body preservation. The full focused publication suite and required repository checks accompany the PR.
- A live corrected protected workflow remains the publication acceptance gate. Preserve the prior source's valid tag and empty draft; do not manually replace assets, expand credentials, or relax approval/CAS controls. Revisit when upstream provides an equivalent immutable, byte-preserving publication primitive.
6 changes: 5 additions & 1 deletion docs/pylon-publication.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ The canonical preview manifest binds the full source commit/tree, artifact recip

```json
{
"publicationPolicyRevision": 2,
"publicationPolicyRevision": 3,
"sequenceEpoch": 1,
"sequence": 123,
"workflowRunId": "33428882721"
Expand All @@ -103,6 +103,10 @@ The approved attester signs exactly six subjects with pinned `actions/attest-bui

The directly `pylon-preview`-gated staging job re-reads live `pylon`, then a dedicated App-authenticated step performs the exact combined REST/GraphQL audit immediately before the separate `GITHUB_TOKEN` preview-tag CAS step. The directly gated publisher repeats live branch/tag checks and a fresh combined audit immediately before the separate immutable-release update. GraphQL is the final authoritative read in each audit. A stale admission or earlier audit is irrelevant. GitHub does not offer an atomic transaction across branch reads, tag creation, and release publication. Each read and compare-and-set is a separate fail-closed point-in-time check; this design does not claim cross-resource atomicity.

All preview and stable asset uploads use the pinned GitHub client's `repos.uploadReleaseAsset` method. It selects `uploads.github.com`, encodes the required `name` query parameter, and sends the exact binary body and content length. A generic API-host POST does not supply that transport contract. Preview staging checks every returned asset receipt; stable staging and zero-asset recovery also download and hash the singleton before reservation or publication.

Policy revision 3 binds these upload workflow bytes. Revisions 1 and 2 retain their original digests for historical verification and explicit stable recovery. A failed preview's valid source tag and empty draft remain attached to that source; a correction on a new source commit uses its own source-derived preview tag. Do not retarget the old tag or attach new-source bytes to its draft.

## Preview consumer high-water

Download one preview into a new directory. The integrated verifier checks all bytes, six attestations, signer workflow at the signer digest, signed run invocation, Actions run number, and then atomically advances explicit consumer-local state:
Expand Down
7 changes: 7 additions & 0 deletions scripts/pylon-prime-supported-release-recipes-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
"previewWorkflowSha256": "9f4e3f38fb0bdb9c11662310c5369fb792765a3090e0f74b0ec0b34127b43ed8",
"stableWorkflowPath": ".github/workflows/pylon-stable-release.yml",
"stableWorkflowSha256": "0f04d1f55f54312d933087d88de6883e8408bb0cd9f060d3b5851d710698b1af"
},
{
"publicationPolicyRevision": 3,
"previewWorkflowPath": ".github/workflows/pylon-preview-release.yml",
"previewWorkflowSha256": "16f68e46801eccca5e7e99736f346b5ffd96ce7188792d4ac8fbc4580408a736",
"stableWorkflowPath": ".github/workflows/pylon-stable-release.yml",
"stableWorkflowSha256": "96e4f3ccd889a17a391f00b4398d132a4234b5bc337045fedd3605f4eb74c331"
}
]
}
Loading
Loading