Skip to content
Open
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
71 changes: 40 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ jobs:
copy: [a, b]
env:
PYLON_RELEASE_NODE: 22.23.2
PYLON_RELEASE_MODE: ci
PYLON_RELEASE_NPM: 11.10.1
steps:
- name: Checkout exact source
Expand All @@ -240,36 +241,29 @@ jobs:
with:
node-version: ${{ env.PYLON_RELEASE_NODE }}

- name: Install pinned build inputs
- name: Run isolated release sandbox
id: sandbox
shell: /usr/bin/bash --noprofile --norc -euo pipefail {0}
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
npm install --global "npm@${PYLON_RELEASE_NPM}"
test "$(node --version)" = "v${PYLON_RELEASE_NODE}"
test "$(npm --version)" = "${PYLON_RELEASE_NPM}"
npm ci

- name: Test release contract
run: npm run test:pylon-release

- name: Build offline and pack without network
run: |
sudo env \
"PATH=$PATH" \
"HOME=$HOME" \
"GIT_CONFIG_COUNT=1" \
"GIT_CONFIG_KEY_0=safe.directory" \
"GIT_CONFIG_VALUE_0=$GITHUB_WORKSPACE" \
unshare --net -- npm run release:pylon:pack

- name: Verify artifact contract
run: npm run release:pylon:verify

- name: Upload candidate
node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')"
/usr/bin/env -i \
"HOME=/tmp" \
"PATH=${node_path%/*}:/usr/bin:/bin" \
"$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \
--task pack \
--mode "$PYLON_RELEASE_MODE" \
--host-os "${{ runner.os }}" \
--workspace "$GITHUB_WORKSPACE" \
--github-output "$GITHUB_OUTPUT" \
--run-id "${{ github.run_id }}" \
--run-number "${{ github.run_number }}" \
--publication-policy-revision 3

- name: Upload isolated subjects
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pylon-prime-pack-${{ matrix.copy }}
path: .npm/pylon-release/artifacts
path: ${{ steps.sandbox.outputs.artifact_dir }}
include-hidden-files: true
if-no-files-found: error
retention-days: 3
Expand Down Expand Up @@ -301,12 +295,16 @@ jobs:
if: needs.trust.outputs.allowed == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 15
permissions:
actions: read
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15]
env:
PYLON_RELEASE_NODE: 22.23.2
PYLON_RELEASE_MODE: ci
PYLON_RELEASE_NPM: 11.10.1
steps:
- name: Checkout exact source
Expand All @@ -321,17 +319,28 @@ jobs:
with:
node-version: ${{ env.PYLON_RELEASE_NODE }}

- name: Install pinned npm
run: npm install --global "npm@${{ env.PYLON_RELEASE_NPM }}"

- name: Download reproducible pack
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: pylon-prime-pack-a
path: .npm/pylon-release/artifacts

- name: Verify temporary-prefix runtime
run: npm run release:pylon:smoke
- name: Run isolated artifact smoke
shell: /usr/bin/bash --noprofile --norc -euo pipefail {0}
run: |
node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')"
/usr/bin/env -i \
"HOME=/tmp" \
"PATH=${node_path%/*}:/usr/bin:/bin" \
"$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \
--task smoke \
--mode "$PYLON_RELEASE_MODE" \
--host-os "${{ runner.os }}" \
--workspace "$GITHUB_WORKSPACE" \
--artifact-dir "$GITHUB_WORKSPACE/.npm/pylon-release/artifacts" \
--run-id "${{ github.run_id }}" \
--run-number "${{ github.run_number }}" \
--publication-policy-revision 3

build-check-test:
name: build-check-test
Expand Down
84 changes: 42 additions & 42 deletions .github/workflows/pylon-preview-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@ jobs:
needs: admission
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
PYLON_RELEASE_MODE: preview
permissions:
contents: read
strategy:
fail-fast: false
matrix:
copy: [a, b]
steps:
- name: Checkout exact pushed source
- name: Checkout exact source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.sha }}
Expand All @@ -69,41 +71,29 @@ jobs:
with:
node-version: ${{ env.PYLON_RELEASE_NODE }}

- name: Install pinned build inputs
- name: Run isolated release sandbox
id: sandbox
shell: /usr/bin/bash --noprofile --norc -euo pipefail {0}
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
npm install --global "npm@${PYLON_RELEASE_NPM}"
test "$(node --version)" = "v${PYLON_RELEASE_NODE}"
test "$(npm --version)" = "${PYLON_RELEASE_NPM}"
npm ci
node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')"
/usr/bin/env -i \
"HOME=/tmp" \
"PATH=${node_path%/*}:/usr/bin:/bin" \
"$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \
--task pack \
--mode "$PYLON_RELEASE_MODE" \
--host-os "${{ runner.os }}" \
--workspace "$GITHUB_WORKSPACE" \
--github-output "$GITHUB_OUTPUT" \
--run-id "${{ github.run_id }}" \
--run-number "${{ github.run_number }}" \
--publication-policy-revision 3

- name: Test publication contract
run: |
npm run test:pylon-release
npm run test:pylon-publication

- name: Build and pack without network
run: |
sudo env \
"PATH=$PATH" \
"HOME=$HOME" \
"GIT_CONFIG_COUNT=1" \
"GIT_CONFIG_KEY_0=safe.directory" \
"GIT_CONFIG_VALUE_0=$GITHUB_WORKSPACE" \
unshare --net -- npm run release:pylon:pack

- 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:verify-preview

- name: Upload isolated preview subjects
- name: Upload isolated subjects
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pylon-preview-pack-${{ matrix.copy }}
path: .npm/pylon-release/artifacts
path: ${{ steps.sandbox.outputs.artifact_dir }}
include-hidden-files: true
if-no-files-found: error
retention-days: 3
Expand Down Expand Up @@ -183,6 +173,8 @@ jobs:
needs: [pack, reproducibility]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
env:
PYLON_RELEASE_MODE: preview
permissions:
actions: read
contents: read
Expand All @@ -191,7 +183,7 @@ jobs:
matrix:
os: [ubuntu-24.04, macos-15]
steps:
- name: Checkout exact pushed source
- name: Checkout exact source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.sha }}
Expand All @@ -203,9 +195,6 @@ jobs:
with:
node-version: ${{ env.PYLON_RELEASE_NODE }}

- name: Install pinned npm
run: npm install --global "npm@${{ env.PYLON_RELEASE_NPM }}"

- name: Verify workflow artifact provenance
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
Expand Down Expand Up @@ -235,11 +224,22 @@ jobs:
name: pylon-preview-pack-a
path: .npm/pylon-release/artifacts

- name: Verify and install the exact preview bytes
- name: Run isolated artifact smoke
shell: /usr/bin/bash --noprofile --norc -euo pipefail {0}
run: |
npm run release:pylon:verify-preview
node -e "require('node:fs').unlinkSync('.npm/pylon-release/artifacts/pylon-preview-channel-v1.json')"
npm run release:pylon:smoke
node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')"
/usr/bin/env -i \
"HOME=/tmp" \
"PATH=${node_path%/*}:/usr/bin:/bin" \
"$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \
--task smoke \
--mode "$PYLON_RELEASE_MODE" \
--host-os "${{ runner.os }}" \
--workspace "$GITHUB_WORKSPACE" \
--artifact-dir "$GITHUB_WORKSPACE/.npm/pylon-release/artifacts" \
--run-id "${{ github.run_id }}" \
--run-number "${{ github.run_number }}" \
--publication-policy-revision 3

stage-draft:
name: Stage exact preview draft
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 @@ -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
39 changes: 25 additions & 14 deletions .github/workflows/pylon-stable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ jobs:
needs: [admission, verify-preview]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
env:
PYLON_RELEASE_MODE: historical
permissions:
actions: read
contents: read
Expand All @@ -340,7 +342,7 @@ jobs:
matrix:
os: [ubuntu-24.04, macos-15]
steps:
- name: Checkout current protected install policy
- name: Checkout exact source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.sha }}
Expand All @@ -352,9 +354,6 @@ jobs:
with:
node-version: ${{ env.PYLON_RELEASE_NODE }}

- name: Install pinned npm
run: npm install --global "npm@${{ env.PYLON_RELEASE_NPM }}"

- name: Verify workflow artifact provenance
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
Expand Down Expand Up @@ -382,10 +381,22 @@ jobs:
name: verified-stable-preview
path: .npm/pylon-release/artifacts

- name: Install the same exact preview bytes
- name: Run isolated artifact smoke
shell: /usr/bin/bash --noprofile --norc -euo pipefail {0}
run: |
npm run release:pylon:verify-preview -- --historical
npm run release:pylon:smoke -- --historical
node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')"
/usr/bin/env -i \
"HOME=/tmp" \
"PATH=${node_path%/*}:/usr/bin:/bin" \
"$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \
--task smoke \
--mode "$PYLON_RELEASE_MODE" \
--host-os "${{ runner.os }}" \
--workspace "$GITHUB_WORKSPACE" \
--artifact-dir "$GITHUB_WORKSPACE/.npm/pylon-release/artifacts" \
--run-id "${{ github.run_id }}" \
--run-number "${{ github.run_number }}" \
--publication-policy-revision 3

prepare:
name: Resolve exact stable transaction
Expand Down Expand Up @@ -473,7 +484,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 +568,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 +674,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 @@ -840,10 +851,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
Loading
Loading