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
55 changes: 44 additions & 11 deletions .github/workflows/production-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Production deployment

on:
release:
types: [published]
workflow_dispatch:
inputs:
source_ref:
Expand All @@ -19,23 +17,31 @@ concurrency:

jobs:
deploy:
name: Deploy the published release
# Production never follows arbitrary branch pushes. A release publication
# or an explicit dispatch of an exact stable SemVer tag is required. The
# protected GitHub environment supplies the final human approval gate.
if: github.event_name == 'workflow_dispatch' || (github.event.release.prerelease == false && startsWith(github.event.release.tag_name, 'v'))
name: Qualify and publish the exact release
# Production never follows arbitrary branch pushes. An exact stable tag is
# dispatched, kept as a draft during qualification, and published only
# after the image and service-UID smoke gates pass.
runs-on: ubuntu-24.04
timeout-minutes: 60
environment: production
env:
RELEASE_REF: ${{ github.event.release.tag_name || inputs.source_ref }}
RELEASE_REF: ${{ inputs.source_ref }}
PRODUCTION_DOMAIN: ${{ secrets.PCRSTUDIO_PRODUCTION_DOMAIN }}
PYTHONDONTWRITEBYTECODE: "1"
steps:
- name: Validate exact stable release tag input
id: requested-release
env:
SOURCE_REF: ${{ inputs.source_ref }}
run: |
set -euo pipefail
[[ "$SOURCE_REF" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
echo "source_ref=$SOURCE_REF" >> "$GITHUB_OUTPUT"

- name: Check out the exact release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
ref: ${{ github.event.release.tag_name || inputs.source_ref }}
ref: ${{ steps.requested-release.outputs.source_ref }}
fetch-depth: 1

- name: Materialize locked web verification dependencies
Expand Down Expand Up @@ -64,6 +70,7 @@ jobs:
run: |
set -euo pipefail
[[ "$RELEASE_REF" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
[[ "$(git rev-parse --verify "refs/tags/$RELEASE_REF^{commit}")" == "$(git rev-parse HEAD)" ]]
python3 -B scripts/validate-release-version.py --tag "$RELEASE_REF"
[[ "$PRODUCTION_DOMAIN" =~ ^[A-Za-z0-9.-]+$ ]]
[[ "$PRODUCTION_DOMAIN" != .* && "$PRODUCTION_DOMAIN" != *..* ]]
Expand All @@ -78,6 +85,23 @@ jobs:
python3 -B scripts/qualify-source.py --no-write
python3 -B scripts/verify-release.py --root .

- name: Prepare an unpublished release draft
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if draft_state="$(gh release view "$RELEASE_REF" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null)"; then
if [[ "$draft_state" != true ]]; then
echo "refusing to rebuild or mutate published release $RELEASE_REF" >&2
exit 1
fi
echo "reusing unpublished draft for $RELEASE_REF"
else
gh release create "$RELEASE_REF" --draft --verify-tag \
--title "PCRStudio ${RELEASE_REF#v} — Verified production release" \
--notes-file release/RELEASE-NOTES.md --repo "$GITHUB_REPOSITORY"
fi

- name: Build the qualified runtime image set on GitHub's runner
env:
BUILD_ID: ${{ steps.release.outputs.build_id }}
Expand Down Expand Up @@ -128,7 +152,7 @@ jobs:
[[ "$source_sha" =~ ^[0-9a-f]{40}$ ]]
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"

- name: Publish the verified HTTPS deployment bundle
- name: Upload the verified HTTPS deployment bundle to the draft
env:
GH_TOKEN: ${{ github.token }}
RELEASE_SHA: ${{ steps.release.outputs.release_sha }}
Expand All @@ -153,4 +177,13 @@ jobs:
--image-archive "$image_archive" \
--output "$manifest"
gh release upload "$RELEASE_REF" "$source_archive" "$image_archive" "$manifest" --repo "$GITHUB_REPOSITORY" --clobber
echo "published HTTPS deployment assets for $RELEASE_REF ($RELEASE_SHA)"
echo "uploaded verified HTTPS deployment assets for $RELEASE_REF ($RELEASE_SHA)"

- name: Publish only after every release gate passes
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release edit "$RELEASE_REF" --draft=false --latest --verify-tag \
--repo "$GITHUB_REPOSITORY"
echo "published qualified release $RELEASE_REF"
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
This file summarizes the current public-source milestone. Detailed scientific and
qualification evidence is maintained under `release/current/`.

## v1.0.5 — 2026-09-13
## v1.0.5 — superseded before deployment — 2026-09-13

- Normalize permissions across complete pinned native-tool trees so nested MAFFT and BLAST helpers are readable/executable by the non-root service.
- Add regression coverage for nested executables, libraries, directories, and unsafe special/write bits; smoke both API and runner images as the service UID before publishing.
- Preserve upstream archive digests and bind the generated normalized runtime tree in the image; pinned base images, scientific behavior, and user data formats remain unchanged.
- Its source changes normalized permissions across pinned native-tool trees, but the final runtime smoke exposed a missing launcher interpreter before the deployment bundle was published. Use v1.0.6 for a deployable release; no production rollout occurred from v1.0.5.

## v1.0.6 — 2026-09-13

- Fix production startup in the restricted runtime without changing pinned product images or user data.
- Publish production releases only after image startup qualification passes, so an incomplete build cannot become the public Latest release.
- Refresh the host storage guard before deployment image operations, so old reserve policies cannot stop services on the dedicated VM.

## v1.0.4 — 2026-09-13

Expand Down
1 change: 0 additions & 1 deletion contracts/capability-truth.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"files": [
"README.md",
"release/PUBLIC-SOURCE.md",
"release/RELEASE-NOTES.md",
"scripts/generate-expert-audit-artifacts.py",
"knowledge/reviews/EXPERT-MODULE-AUDIT.md"
],
Expand Down
12 changes: 7 additions & 5 deletions docker/api.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PCRStudio Linux x86_64 application images.

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-33818 stdlib v1.26.0 1.25.13, 1.26.6, 1.27.0-rc.3 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-33814 stdlib v1.26.0 1.25.10, 1.26.3 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-33811 stdlib v1.26.0 1.25.10, 1.26.3 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-33810 stdlib v1.26.0 1.26.2 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-32283 stdlib v1.26.0 1.25.9, 1.26.2 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-32281 stdlib v1.26.0 1.25.9, 1.26.2 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-32280 stdlib v1.26.0 1.25.9, 1.26.2 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-27145 stdlib v1.26.0 1.25.11, 1.26.4 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-27137 stdlib v1.26.0 1.26.1 HIGH

Check notice on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Trivy finding

opt/pcrstudio/tools/mfeprimer/mfeprimer CVE-2026-25679 stdlib v1.26.0 1.25.8, 1.26.1 HIGH

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-33818 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-33814 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-33811 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-33810 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-32283 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-32281 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-32280 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-27145 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-27137 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09

Check warning on line 1 in docker/api.Dockerfile

View workflow job for this annotation

GitHub Actions / Linux image qualification

Accepted temporary security exception

CVE-2026-25679 in opt/pcrstudio/tools/mfeprimer/mfeprimer remains present; review date is 2026-10-09
#
# One build graph produces three deliberately separate runtime artifacts:
# api-runtime interactive HTTP/scientific gateway
Expand Down Expand Up @@ -29,7 +29,7 @@
COPY docker/configure-debian-snapshot.sh /usr/local/bin/configure-debian-snapshot
RUN --network=host configure-debian-snapshot "$DEBIAN_SNAPSHOT" \
&& apt-get update \
&& apt-get install --no-install-recommends -y ca-certificates libgomp1 \
&& apt-get install --no-install-recommends -y bash ca-certificates libgomp1 \
&& rm -rf /var/lib/apt/lists/*

# ── Worker + scientific toolchain ──────────────────────────────────────────
Expand Down Expand Up @@ -90,10 +90,12 @@
# ── Scientific runtime shared only by API and durable runner ───────────────
FROM process-runtime-base AS science-runtime-base
USER root
# BusyBox supplies the small POSIX command surface used by the entrypoint and
# MAFFT wrapper. Python's standard-library HTTP client is used by the API
# health probe; the glibc/native scientific dependencies are copied from the
# already-qualified builder without carrying its package database.
# The official MAFFT 7.526 launcher has a Bash shebang, so copy the snapshot-
# pinned Bash runtime explicitly rather than silently depending on a missing
# interpreter. Keep it out of the database-only migrator image. BusyBox still
# supplies the small POSIX command surface used by the entrypoint; Python's
# standard-library HTTP client serves the API health probe.
COPY --from=runtime-assets /bin/bash /bin/bash
COPY --from=science-builder /usr/local /usr/local
COPY --from=science-builder /opt/uv /opt/uv
COPY --from=science-builder /opt/worker /opt/worker
Expand Down
19 changes: 13 additions & 6 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ internal cleanup do not create a new public version until the operator decides
to publish one. `CURRENT` remains the internal foundation/current-state
identity and is not a public version.

The `production-deploy.yml` workflow handles only a published `vMAJOR.MINOR.PATCH`
release tag, or the same exact tag when an operator starts the workflow
manually. The GitHub `production` environment should require approval and
The `production-deploy.yml` workflow is started manually for an exact
`vMAJOR.MINOR.PATCH` tag. It creates or reuses a draft release, verifies and
builds the release, runs image startup checks, uploads the verified bundle, and
publishes only after every gate succeeds. Failed attempts remain unpublished
drafts and can be retried without changing the immutable source tag. The GitHub
`production` environment should require approval and
contain only:

- `PCRSTUDIO_PRODUCTION_DOMAIN`
Expand All @@ -146,8 +149,9 @@ journalctl -u pcrstudio-release-pull.service
```

The release workflow builds the qualified runtime image set on an ephemeral
runner and publishes the source plus OCI bundle only after release verification.
The server's agent performs the deployment and readiness checks locally. The
runner and publishes the source plus OCI bundle only after release verification
and startup smoke tests. The server's agent performs the deployment and
readiness checks locally. The
server still verifies pinned base images, image presence, migrations, public
readiness and bounded Docker cleanup. Scientific readiness and the durable
runner remain intentionally withheld until the approved reference database is
Expand Down Expand Up @@ -185,7 +189,10 @@ it is an operational guard, not a kernel-enforced filesystem quota. It starts
one minute after boot and rechecks every five minutes. At the 16 GiB budget
threshold it pauses scientific work; at 20 GiB managed use or 4 GiB free host
space it stops the application to protect the host. Cache and backup retention
rules are documented in `docs/DOCKER-STORAGE.md`. Inspect the guard with:
rules are documented in `docs/DOCKER-STORAGE.md`. Every normal bootstrap
reinstalls the guard from the current release and restarts its timer before
image pull/build operations, independently of the optional backup/restore
timers. Inspect the guard with:

```bash
systemctl list-timers pcrstudio-storage-guard.timer
Expand Down
2 changes: 1 addition & 1 deletion knowledge/reviews/EXPERT-MODULE-AUDIT.json
Original file line number Diff line number Diff line change
Expand Up @@ -4362,7 +4362,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
2 changes: 1 addition & 1 deletion knowledge/reviews/EXPERT-UPGRADE-BACKLOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
2 changes: 1 addition & 1 deletion knowledge/reviews/MODULE-TOOLCHAIN-MATRIX.json
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
2 changes: 1 addition & 1 deletion knowledge/reviews/SCIENTIFIC-RISK-REGISTER.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
2 changes: 1 addition & 1 deletion knowledge/reviews/UI-UX-MODULE-AUDIT.json
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
2 changes: 1 addition & 1 deletion knowledge/runtime/numeric-provenance-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -5388,7 +5388,7 @@
"tools/src/pcr_tools/scientific_authority.py": "e4bb66e24f7495b3a01cd7af46196bd1390e6880eef29b1889842a2609c4205a",
"scripts/generate-scientific-authority-registry.py": "71477eb3e49a3ede063960795a0e8a629119236ca83466d0a4380838802654f1",
"scripts/generate-flanking-source-snapshots.py": "c811fa9eec2fce82bee18637ee74f99ae2e9d16efa7bb475bbc89d9901ca8de0",
"contracts/capability-truth.json": "eb2a1335b3f0c24cc4de64700c151ad7f4539c943eb15b335cf82f066a3cccb1"
"contracts/capability-truth.json": "7e6de5588144749c360514d9dd8231f838ddb4e0ba47a8685bfc7bdaa6adf3a5"
},
"generator_sha256": "3338c5f2831d6c5e4a480600985961ed701599a27061d0152816af9b71820d8e"
}
Loading