PRD: Packaged infrastructure artifacts #537
@@ -71,7 +71,7 @@ jobs:
|
||||
BOT_BOTTLE_REF: ${{ needs.validate.outputs.sha }}
|
||||
run: |
|
||||
sh install.sh
|
||||
bot-bottle --help
|
||||
"$HOME/.local/bin/bot-bottle" --help
|
||||
- name: Publish release qualification and advance channel
|
||||
env:
|
||||
BOT_BOTTLE_RELEASE_TOKEN: ${{ secrets.BOT_BOTTLE_RELEASE_TOKEN }}
|
||||
|
||||
@@ -8,6 +8,7 @@ import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
from .release_bundle import parse_bundle_index
|
||||
from .release_manifest import ReleaseManifestError
|
||||
from .release_publish import (
|
||||
HTTP_TIMEOUT_SECONDS,
|
||||
@@ -37,11 +38,20 @@ def release_version(tag: str) -> tuple[int, int, int, int]:
|
||||
"""Return a monotonically comparable version tuple."""
|
||||
stable = _STABLE_TAG_RE.fullmatch(tag)
|
||||
if stable:
|
||||
return *(int(value) for value in stable.groups()), 1 << 30
|
||||
return (
|
||||
int(stable.group(1)),
|
||||
int(stable.group(2)),
|
||||
int(stable.group(3)),
|
||||
1 << 30,
|
||||
)
|
||||
staging = _STAGING_TAG_RE.fullmatch(tag)
|
||||
if staging:
|
||||
return *(int(value) for value in staging.groups()[:3]), int(
|
||||
staging.group(4))
|
||||
return (
|
||||
int(staging.group(1)),
|
||||
int(staging.group(2)),
|
||||
int(staging.group(3)),
|
||||
int(staging.group(4)),
|
||||
)
|
||||
raise ReleaseManifestError(
|
||||
"release tag must be vX.Y.Z or vX.Y.Z-rc.N")
|
||||
|
||||
@@ -138,9 +148,17 @@ def publish_qualification(
|
||||
channel_url = pointer_url(
|
||||
"bot-bottle-channels", channel, "channel.json")
|
||||
|
||||
if remote_bytes(str(pointer["bundle_index_url"])) is None:
|
||||
bundle = remote_bytes(str(pointer["bundle_index_url"]))
|
||||
if bundle is None:
|
||||
raise ReleaseManifestError(
|
||||
f"commit bundle does not exist for {pointer['source_commit']}")
|
||||
try:
|
||||
bundle_data = parse_bundle_index(json.loads(bundle))
|
||||
except (json.JSONDecodeError, UnicodeDecodeError) as exc:
|
||||
raise ReleaseManifestError("commit bundle index is malformed") from exc
|
||||
if bundle_data["source_commit"] != pointer["source_commit"]:
|
||||
raise ReleaseManifestError(
|
||||
"commit bundle index does not match the qualified source commit")
|
||||
existing_release = remote_bytes(release_url)
|
||||
if existing_release is not None and existing_release != wanted:
|
||||
raise ReleaseManifestError(
|
||||
|
||||
+17
@@ -45,6 +45,23 @@ report; macOS reports advisory coverage in its own job. The macOS infra
|
||||
container is a singleton, so its job uses a concurrency group and always tears
|
||||
the service down.
|
||||
|
||||
## Artifact publication and promotion
|
||||
|
||||
[`publish-artifacts.yml`](../.gitea/workflows/publish-artifacts.yml) is the
|
||||
manual deployment-test boundary. Dispatch it with any branch, tag, or commit;
|
||||
it resolves that input once, publishes the orchestrator, gateway, Claude,
|
||||
Codex, Pi, and Firecracker artifacts, then publishes only the wheel verified
|
||||
against those identities. Install the reported commit with
|
||||
`BOT_BOTTLE_REF=<sha> sh install.sh`; the installer warns that snapshots may
|
||||
not have completed release qualification.
|
||||
|
||||
[`release.yml`](../.gitea/workflows/release.yml) handles promotion tags.
|
||||
`vX.Y.Z-rc.N` must be reachable from `staging`; `vX.Y.Z` must be reachable
|
||||
from `production`. It runs the complete pre-release workflow, ensures that the
|
||||
same commit bundle is published, verifies a clean install, creates the
|
||||
qualified release pointer, and advances the matching channel. Promotion
|
||||
reuses immutable bundle bytes and never rebuilds them.
|
||||
|
||||
## Scheduled canary
|
||||
|
||||
[`.gitea/workflows/canaries.yml`](../.gitea/workflows/canaries.yml) runs weekly
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PRD 0083: Packaged infrastructure artifacts
|
||||
|
||||
- **Status:** Draft
|
||||
- **Status:** Active
|
||||
- **Author:** Codex
|
||||
- **Created:** 2026-07-27
|
||||
- **Issue:** #536
|
||||
@@ -167,7 +167,8 @@ immutable external bundle index under that SHA. The index contains:
|
||||
- Claude, Codex, and Pi agent OCI digest references;
|
||||
- Firecracker package versions and compressed-file SHA-256 values;
|
||||
- producing workflow/run identity and publication timestamp; and
|
||||
- qualification records, if any, naming the test workflow and release tag.
|
||||
- producing workflow provenance. Qualification is recorded separately in an
|
||||
immutable tag pointer so the commit bundle itself never changes.
|
||||
|
||||
The embedded wheel manifest contains the runtime subset of the same identities.
|
||||
Publication fails if the embedded manifest and external index differ.
|
||||
@@ -234,7 +235,7 @@ The release workflow is triggered by an eligible staging or production tag:
|
||||
then if absent; otherwise verify every existing identity and checksum.
|
||||
4. Install using the published installer and bundle index on clean supported
|
||||
hosts, and run the post-install smoke test against the acquired artifacts.
|
||||
5. Attach a qualification record to the bundle, create the Gitea release, and
|
||||
5. Publish an immutable qualified tag pointer, create the Gitea release, and
|
||||
advance the matching channel pointer.
|
||||
|
||||
No release artifact or channel update occurs before qualification succeeds.
|
||||
@@ -256,9 +257,9 @@ and publish them first, then install the reported SHA.
|
||||
|
||||
An exact-commit install prints a prominent warning that the snapshot may not
|
||||
have passed release qualification, even if the same SHA is later associated
|
||||
with a tag. A tag or channel install suppresses that warning only when the
|
||||
external index contains a successful qualification record for that tag and
|
||||
the tag selects the same bundle SHA.
|
||||
with a tag. A tag or channel install suppresses that warning only when its
|
||||
validated pointer is marked qualified and selects the same immutable bundle
|
||||
SHA.
|
||||
|
||||
The installer downloads the wheel named by the external index, verifies its
|
||||
SHA-256 before invoking pipx or pip, and reports the selected channel/tag,
|
||||
@@ -361,9 +362,10 @@ Firecracker, and Apple Container.
|
||||
|
||||
Create and protect `staging` and `production`, then promote one commit from
|
||||
`main` through both branches. Publish its commit bundle, qualify an RC tag,
|
||||
perform clean installs through the staging channel, promote the same SHA,
|
||||
qualify a stable tag, and only then make the production channel the installer's
|
||||
default. The stable promotion must demonstrate that every production artifact
|
||||
perform clean installs through the staging channel, promote the same SHA, and
|
||||
qualify a stable tag. Until that first stable qualification exists, the
|
||||
installer's production default fails closed because there is no production
|
||||
pointer. The stable promotion must demonstrate that every production artifact
|
||||
identity matches the staging-qualified bundle.
|
||||
|
||||
Existing source checkouts use explicit local mode. Existing mutable local
|
||||
|
||||
@@ -16,6 +16,37 @@ from bot_bottle.release_qualification import (
|
||||
COMMIT = "a" * 40
|
||||
|
||||
|
||||
def bundle() -> bytes:
|
||||
return json.dumps({
|
||||
"schema": 1,
|
||||
"source_commit": COMMIT,
|
||||
"wheel": {
|
||||
"filename": "bot_bottle.whl",
|
||||
"url": (
|
||||
"https://gitea.dideric.is/api/packages/didericis/generic/"
|
||||
f"bot-bottle-builds/{COMMIT}/bot_bottle.whl"
|
||||
),
|
||||
"sha256": "b" * 64,
|
||||
},
|
||||
"oci": {
|
||||
role: f"registry.example/{role}@sha256:{'c' * 64}"
|
||||
for role in (
|
||||
"orchestrator",
|
||||
"gateway",
|
||||
"agent_claude",
|
||||
"agent_codex",
|
||||
"agent_pi",
|
||||
)
|
||||
},
|
||||
"firecracker": {
|
||||
role: {"version": "v1", "sha256": "d" * 64}
|
||||
for role in ("orchestrator", "gateway")
|
||||
},
|
||||
"provenance": {"workflow_run": "run", "published_at": "now"},
|
||||
"qualifications": [],
|
||||
}).encode()
|
||||
|
||||
|
||||
class ReleaseQualificationTest(unittest.TestCase):
|
||||
def test_tag_selects_channel(self) -> None:
|
||||
self.assertEqual(release_channel("v1.2.3"), "production")
|
||||
@@ -38,7 +69,7 @@ class ReleaseQualificationTest(unittest.TestCase):
|
||||
def test_publishes_bundle_release_and_channel(
|
||||
self, remote: mock.Mock, upload: mock.Mock,
|
||||
) -> None:
|
||||
remote.side_effect = [b"bundle", None, None]
|
||||
remote.side_effect = [bundle(), None, None]
|
||||
pointer = build_release_pointer(
|
||||
tag="v1.2.3",
|
||||
source_commit=COMMIT,
|
||||
@@ -59,7 +90,7 @@ class ReleaseQualificationTest(unittest.TestCase):
|
||||
workflow_run="old-run",
|
||||
qualified_at="old-time",
|
||||
)
|
||||
remote.side_effect = [b"bundle", None, json.dumps(old).encode()]
|
||||
remote.side_effect = [bundle(), None, json.dumps(old).encode()]
|
||||
pointer = build_release_pointer(
|
||||
tag="v1.2.3",
|
||||
source_commit=COMMIT,
|
||||
|
||||
Reference in New Issue
Block a user