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
35 changes: 24 additions & 11 deletions .github/workflows/release-runner-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,20 @@ name: release-runner-image
# ## Verify side
#
# The daemon runs `cosign verify` against this image at boot
# (`sidecars/dev-runner-daemon/src/imageVerify.mjs`). Because the signer moved
# from byte5ai/omadia to this repository, the certificate identity CHANGED — see
# docs/SUPPLY_CHAIN.md for the transition regexp that accepts both, and for the
# narrowing step that removes the old one a release later. The `verify` job at
# the bottom re-checks the published digest with that exact regexp, so a drift
# between publisher and consumer fails CI here instead of a daemon somewhere
# else, days later, refusing to boot.
# (`sidecars/dev-runner-daemon/src/imageVerify.mjs`). The signer moved from
# byte5ai/omadia to this repository, so 0.3.2 shipped a transition regexp that
# accepted both publishers; 0.3.4 NARROWED it, on the schedule written down in
# docs/SUPPLY_CHAIN.md, to this repository's workflow alone and only from
# `refs/heads/main` or `refs/tags/vX.Y.Z`.
#
# Consequence worth knowing before you reach for the escape hatch: a
# `workflow_dispatch` run from a NON-MAIN branch still builds and still pushes,
# but the identity it signs (`@refs/heads/<branch>`) is one every daemon now
# REFUSES. Release from `main` or from a version tag.
#
# The `verify` job at the bottom re-checks the published digest with the exact
# regexp the daemon enforces, so a drift between publisher and consumer fails CI
# here instead of a daemon somewhere else, days later, refusing to boot.

on:
push:
Expand Down Expand Up @@ -114,8 +121,8 @@ jobs:
# sidecars/dev-runner/** the Dockerfile itself
# packages/runner-shim/** compiled INTO the image
# sidecars/dev-runner-daemon/** the consumer of the signature: it
# owns DEFAULT_TRANSITION_IDENTITY_REGEXP
# and the drift guard against this file
# owns DEFAULT_IDENTITY_REGEXP and the
# drift guard against this file
# this workflow how the image is built at all
#
# Unknown or unreachable `before` (first push on a branch, force-push,
Expand Down Expand Up @@ -374,11 +381,17 @@ jobs:
IMAGE_DIGEST: ${{ needs.publish.outputs.digest }}
run: |
set -euo pipefail
# Kept BYTE-IDENTICAL to DEFAULT_TRANSITION_IDENTITY_REGEXP in
# Kept BYTE-IDENTICAL to DEFAULT_IDENTITY_REGEXP in
# sidecars/dev-runner-daemon/src/imageVerify.mjs. The daemon suite pins
# that constant against this file; this is the other end of the same
# contract. Change one without the other and this job goes red.
IDENTITY_RE='^(?:https://github\.com/byte5ai/omadia/\.github/workflows/publish-images\.yml|https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml)@refs/(?:heads|tags)/[A-Za-z0-9._/-]+$'
#
# NARROWED in 0.3.4 (docs/SUPPLY_CHAIN.md): one signer, and only the
# two refs this workflow publishes from. A `workflow_dispatch` run from
# a NON-MAIN branch signs `@refs/heads/<branch>`, which this job — and
# every daemon — will refuse. That is the trade the narrowing makes:
# release from `main` or from a `vX.Y.Z` tag.
IDENTITY_RE='^https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml@refs/(?:heads/main|tags/v[0-9]+\.[0-9]+\.[0-9]+)$'
cosign verify \
--certificate-identity-regexp "$IDENTITY_RE" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
Expand Down
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,96 @@ project versions the ARTIFACT, not the repository: a release is a ZIP an
operator can install, so anything that does not change the ZIP does not get a
version.

## 0.3.4 — 2026-08-21

Two supply-chain fixes with the same shape: a guarantee that had stopped being
checked, and a window that had stopped being temporary.

### Fixed

- **`npm run package` now gates on a green build**
([#11](https://github.com/byte5ai/omadia-dev-platform/issues/11)). It used to
stage whatever `dist/` and `ui/` happened to contain. During the 0.3.1
acceptance run that produced `omadia-dev-platform-0.3.1.zip` at **142,081
bytes instead of 537,065**, from a `tsc` that had FAILED: `dist/` had been
deleted, the stale `*.tsbuildinfo` beside it told the compiler there was
nothing to do, and the archive came out with no UI bundle and otherwise
indistinguishable from a valid artifact. It uploaded, installed, activated,
and 404'd when the operator clicked the nav entry.

`scripts/build-zip.mjs` now **deletes** the build outputs *and* their
`*.tsbuildinfo` (`plugin-api`, `runner-shim`, `plugin`, and the `ui/` that
`packages/ui` writes into this package), runs `npm run build` from the
repository root, and refuses to package on a non-zero exit. Checking for the
presence of `dist/plugin.js` would not have caught the original bug — the file
was there, from the previous version.

### Added

- **A payload gate over the staged archive**, in the new
`packages/plugin/scripts/package-payload.mjs`. It asserts `dist/plugin.js`,
every `migrations/*.js` (counted against the SOURCE directory, floor of nine),
`ui/index.html` plus at least one hashed `ui/assets/index-<hash>.js`,
`handoff-plan.json`, `manifest.yaml`, `package.json` and the in-ZIP
`README.md`; and that no `.map`, `.css`, `node_modules/` or `.tsbuildinfo` is
present. Every problem is collected and reported **together** — one broken
build should cost one edit-run cycle, not one per missing thing. A size floor
(400 KB) and ceiling (2 MB) sit behind that as a backstop for whatever the
named assertions did not think of.

Driven from both ends: `test/packagePayload.test.ts` runs the gate over
synthetic stages (good stage, then one defect at a time — `ui/` absent, a
`.map` that sneaks past the prune, a short migration set, a staged
`node_modules`), and `test/packagedArtifact.test.ts` runs the same gate over
the archive the checkout actually staged, so the fixtures cannot drift into
describing a payload nobody ships.

- **`README.md` is a REQUIRED payload file**, not an optional one. The hub
renders a storefront page from the manifest and links no repository, so an
operator inspecting an unzipped plugin has this file or has nothing.

### Changed

- **The cosign certificate identity is NARROWED** (epic #470 P4, decision D5),
on the schedule [docs/SUPPLY_CHAIN.md](./docs/SUPPLY_CHAIN.md) wrote down: one
release after the first image published from this repository, which was 0.3.2.
`CORE_SIGNER_IDENTITY`, `TRANSITION_SIGNERS` and the `widened` branch of
`resolveCertificateIdentity` are deleted. The default is now

```
^https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml@refs/(?:heads/main|tags/v[0-9]+\.[0-9]+\.[0-9]+)$
```

renamed `DEFAULT_TRANSITION_IDENTITY_REGEXP` → `DEFAULT_IDENTITY_REGEXP`,
because it is not a transition any more. The `verify` job in
`release-runner-image.yml` carries the identical string; the daemon suite fails
if the two drift.

The **ref** arm narrowed too, and this is the part with a consequence: it was
`refs/(heads|tags)/<any ref>`, so a `workflow_dispatch` from *any* branch —
including one any contributor can push — minted an identity the daemon
accepted. It is now `refs/heads/main` or `refs/tags/vX.Y.Z`. A dispatch from a
feature branch still builds and still pushes, and the image it signs is
**refused by every daemon**. Release from `main` or from a version tag.

`certificateIdentity.test.mjs` is the counter-proof: the cases that asserted
the old signer is *accepted* now assert it is *rejected*, and the widening
cases assert nothing is widened.

### ⚠️ Operator action — only if you pinned core's identity

A daemon whose `DEV_IMAGE_COSIGN_IDENTITY` still names
`byte5ai/omadia/.github/workflows/publish-images.yml` was widened for it
automatically in 0.3.2 and 0.3.3, with a warning at every boot. From 0.3.4 it is
not: that daemon will **refuse to start** on a newly published image, with an
`ImageVerificationError` naming the image. The fix is one variable —
`DEV_IMAGE_COSIGN_IDENTITY_REGEXP` set to the pattern above, which takes
precedence over the exact pin. See
[docs/SUPPLY_CHAIN.md](./docs/SUPPLY_CHAIN.md#%EF%B8%8F-if-your-daemon-still-pins-cores-identity).

A refusal rather than a silent downgrade is the intended shape: the alternative
was granting a retired publisher standing authority nobody re-confirmed.

## 0.3.3 — 2026-08-21

Documentation only — no plugin code changed. Core PR
Expand Down
28 changes: 19 additions & 9 deletions docs/OPERATOR-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,33 +427,43 @@ drift fails CI rather than a daemon launch.
> (`gh api -X PATCH /orgs/byte5ai/packages/container/omadia-dev-platform-runner -f visibility=public`).
> Until then, pulling needs `docker login ghcr.io`.

Because the image was previously published by core, verification accepts either
signer during the transition:
Verification accepts exactly one signer, from the two refs this workflow
publishes from. The transition pattern that also accepted core's
`publish-images.yml` was **narrowed away in 0.3.4** (see docs/SUPPLY_CHAIN.md):

```
^(?:https://github\.com/byte5ai/omadia/\.github/workflows/publish-images\.yml|https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml)@refs/(?:heads|tags)/[A-Za-z0-9._/-]+$
^https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml@refs/(?:heads/main|tags/v[0-9]+\.[0-9]+\.[0-9]+)$
```

Two deliberate properties. It is **anchored at both ends** — cosign compiles
Three deliberate properties. It is **anchored at both ends** — cosign compiles
with Go RE2 and matches *unanchored*, so without `^…$` a URL like
`https://evil.example/?x=<a valid identity>` would satisfy it. And it is
**narrow**: two exact repo+workflow pairs, never "anything under `byte5ai`".
`https://evil.example/?x=<a valid identity>` would satisfy it. It names **one
exact repo+workflow**, never "anything under `byte5ai`". And it accepts **two
refs, not every ref**: a `workflow_dispatch` from a non-main branch still builds
and pushes, but the image it signs carries `@refs/heads/<branch>` and every
daemon now refuses it — release from `main` or from a `vX.Y.Z` tag.

| Configuration | cosign flag |
|---|---|
| `DEV_IMAGE_COSIGN_IDENTITY_REGEXP` set | `--certificate-identity-regexp <yours>` — validated at boot; unanchored or uncompilable is a refusal naming the variable |
| `DEV_IMAGE_COSIGN_IDENTITY` set to one of the two signers | `--certificate-identity-regexp <transition>` — widened, logged loudly once per boot |
| `DEV_IMAGE_COSIGN_IDENTITY` set to anything else | `--certificate-identity <yours>` |
| `DEV_IMAGE_COSIGN_IDENTITY` set | `--certificate-identity <yours>` — passed through exactly; **nothing is widened** since 0.3.4 |
| Neither set | verification **skips**, with a warning |

`DEV_IMAGE_COSIGN_ISSUER` must be set in any enforcing configuration — a regexp
alone is not a pin. `DEV_IMAGE_VERIFY=off` is the only full escape hatch.

> **Upgrading past 0.3.3?** A daemon whose `DEV_IMAGE_COSIGN_IDENTITY` still
> names core's `publish-images.yml` was widened for it automatically in 0.3.2 and
> 0.3.3. From 0.3.4 it is not, and that daemon will **refuse to start** on a
> newly published image. Set `DEV_IMAGE_COSIGN_IDENTITY_REGEXP` to the pattern
> above (it takes precedence over the exact pin) and keep
> `DEV_IMAGE_COSIGN_ISSUER` set.

Verify by hand:

```bash
cosign verify \
--certificate-identity-regexp '^https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml@refs/(?:heads|tags)/[A-Za-z0-9._/-]+$' \
--certificate-identity-regexp '^https://github\.com/byte5ai/omadia-dev-platform/\.github/workflows/release-runner-image\.yml@refs/(?:heads/main|tags/v[0-9]+\.[0-9]+\.[0-9]+)$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/byte5ai/omadia-dev-platform-runner@sha256:<digest>

Expand Down
Loading
Loading