Skip to content

feat: publish the container image and the PyPI package - #86

Merged
yi-here merged 3 commits into
Continuum-AI-Corp:mainfrom
xizhuomengcontin:feat/release-pipeline
Aug 27, 2026
Merged

feat: publish the container image and the PyPI package#86
yi-here merged 3 commits into
Continuum-AI-Corp:mainfrom
xizhuomengcontin:feat/release-pipeline

Conversation

@xizhuomengcontin

@xizhuomengcontin xizhuomengcontin commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Orca-Code-Review — push 2

Severity Count Δ vs previous push
P0 0 0
P1 0 0
P2 0 -4
P3 0 0

✅ no blocking findings

Why

The README advertises two artifacts that do not exist:

Artifact State on main today
Container image none — the README cell is a literal ghcr.io/... placeholder, "(image coming soon)"
PyPI package orcarouter-lite returns 404

There is no workflow that publishes anything. ci.yml builds an image only to docker rm -f it, and nothing has ever touched PyPI; the v0.1.0 tag has zero assets.

pip install orcarouter-lite would not have produced anything usable even if it were published: there is no console script, and packages.find ships only app/ + packages/ — leaving scripts/start.py (the boot path) and design/ (the entire dashboard) out of the wheel. You would get a library with no way to start it and nothing behind /.

What

feat(packaging) — make the wheel installable and self-contained

  • app/cli.py holds the boot logic and is exposed as the orcarouter-lite console script (--host/--port/--log-level/--reload/--version). scripts/start.py and app/__main__.py delegate to it, so a checkout, the container and the wheel all take one code path. The logic cannot stay in scripts/ — that directory is not part of the distribution.
  • Build backend moves to hatchling for force-include, which maps the repo-root design/ tree to app/design/ inside the wheel. No file moves (nothing conflicts with open PRs) and no explicit package list to drift out of sync when a subpackage is added.
  • _find_design_dir() replaces the hardcoded ../design: $ORCA_DESIGN_DIR, then app/design (wheel), then ../design (checkout and Docker image).
  • PyPI metadata: readme, SPDX license + license-files, authors, keywords, classifiers, project.urls.

The Dockerfile builder had to change with it. It pre-installed dependencies via mkdir -p app packages && pip install ".", which now fails outright because readme = "README.md" is not in that build context. It reads the dependency list straight out of pyproject.toml instead — clearer about the intent (the project itself is never installed there; the runtime stage puts the source on PYTHONPATH) and the cached layer no longer breaks on a code or README edit.

ci(release) — publish the container image and the PyPI package

  • push to mainghcr.io/…:edge (amd64 only; arm64 is emulated and slow, not worth it on every merge)
  • push a v* tag → :latest, :X.Y.Z, :X.Y, :sha-xxxx on amd64 + arm64, a provenance attestation, the PyPI release, and both dists attached to the GitHub release
  • the pushed image is pulled back by digest and curl'd on /health before the job passes
  • PyPI uses trusted publishing (OIDC) — no token to store or rotate. The tag must match the pyproject version or the job refuses to publish. The job is scoped to this repository, so a fork can rehearse a release against its own ghcr namespace instead of dying at the OIDC exchange.

ci.yml gains a package job so packaging breaks on the PR rather than at release time: build, twine check --strict, install the wheel into a clean venv, boot it, and assert / still answers with HTML. That last assertion is the point — drop the design force-include and everything still builds and boots, only the dashboard silently vanishes. design/** joins the path filters for the same reason.

Verification

Rehearsed end to end on a fork before opening this, so every step below has actually run:

Result
docker smoke real docker build + boot + /health
package build, twine check --strict, clean-venv install, boot, dashboard served ✅
release pushed ghcr.io/<fork>/orcarouter-lite:edge — linux/amd64, 12 layers, non-root orca, CMD ["python","scripts/start.py"] unchanged — signed provenance, pulled the image back by digest, healthy /health; pypi job skipped as designed ✅

Locally: 312 passed, ruff clean, twine check --strict PASSED on both wheel and sdist.

Before the first tag

RELEASING.md has the detail. Two things need a human once:

  1. PyPI trusted publisher — pypi.org → Publishing → pending publisher: owner Continuum-AI-Corp, repo OrcaRouter-Lite, workflow release.yml, environment pypi. Until it exists a v* tag fails at the OIDC exchange. No rush: merging this and pushing to main runs only the image job.
  2. GHCR package visibility — the first push creates the package private; flip it to public in the org's package settings.

Note the existing v0.1.0 tag cannot be released via workflow_dispatch: a dispatch runs the workflow file from the ref you select, and that tag predates release.yml. Cut v0.1.1 instead.

Not included

The README is untouched here, so after merge it will still say ghcr.io/... (image coming soon), and its Railway button still points at https://railway.app/new/template — a URL with no template behind it, which lands visitors on the generic marketplace page and deploys nothing (Railway only serves one-click deploys for a published template code; the legacy ?template=<github-url> form no longer resolves). Both are doc-only changes across 12 files and can follow separately.

🤖 Generated with Claude Code

xizhuomengcontin and others added 2 commits August 24, 2026 17:53
`pip install orcarouter-lite` produced nothing usable: no console script, and
`packages.find` shipped only `app/` + `packages/`, leaving `scripts/start.py`
(the boot path) and `design/` (the entire dashboard) out of the wheel. Anyone
installing from PyPI would have got a library with no way to start it and no UI
behind `/`.

  * `app/cli.py` holds the boot logic and is exposed as the `orcarouter-lite`
    console script (`--host/--port/--log-level/--reload/--version`).
    `scripts/start.py` and `app/__main__.py` delegate to it, so a checkout, the
    container and the wheel all take one code path. `scripts/` cannot hold the
    logic — it is not part of the distribution.

  * Build backend moves to hatchling for `force-include`, which maps the
    repo-root `design/` tree to `app/design/` inside the wheel. No file moves
    (nothing conflicts with the open PRs) and no explicit package list to drift
    out of sync when a subpackage is added.

  * `_find_design_dir()` replaces the hardcoded `../design`, checking
    `$ORCA_DESIGN_DIR`, then `app/design` (wheel), then `../design` (checkout
    and Docker image).

  * Metadata for a real PyPI listing: readme, SPDX license + license-files,
    authors, keywords, classifiers, project.urls.

The Dockerfile builder had to change with it. It pre-installed dependencies via
`mkdir -p app packages && pip install "."`, which now fails outright because
`readme = "README.md"` is not in the build context. It reads the dependency list
straight out of pyproject instead — clearer about the intent (the project itself
is never installed there; the runtime stage uses PYTHONPATH) and the cached
layer no longer breaks on a README edit.

Verified end to end: `python -m build`, `twine check --strict`, install the
wheel into a clean venv, boot it — `/health` 200, `/` serves the dashboard,
`/static/style.css` 200 (42561 bytes).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neither artifact the README points at exists. There is no workflow that pushes
anything: `ci.yml` builds an image only to `docker rm -f` it, and nothing ever
touches PyPI. The v0.1.0 tag has zero assets.

`release.yml`:
  * push to main  -> ghcr.io/<repo>:edge (amd64 — arm64 is emulated and slow,
                     not worth it on every merge)
  * push a v* tag -> :latest, :X.Y.Z, :X.Y, :sha-xxxx on amd64 + arm64, a
                     provenance attestation, the PyPI release, and both dists
                     attached to the GitHub release
  * the pushed image is pulled back by digest and curl'd on /health before the
    job passes
  * PyPI uses trusted publishing (OIDC), so there is no token to store; the tag
    has to match the pyproject version or the job refuses to publish. The job is
    scoped to this repository so a fork can rehearse a release against its own
    ghcr namespace without dying at the OIDC exchange.

`ci.yml` gains a `package` job so packaging breaks on the PR, not at release
time: build, `twine check --strict`, install the wheel into a clean venv, boot
it, and assert `/` still answers with HTML. That last assertion is the point —
drop the design force-include and everything still builds and boots, only the
dashboard silently vanishes. `design/**` joins the path filters for the same
reason.

RELEASING.md covers the one-time setup each artifact needs. Note that the
existing v0.1.0 tag cannot be released with a workflow_dispatch: a dispatch runs
the workflow file from the ref you select, and that tag predates release.yml.

Verified end to end on a fork before this PR: the image job built and pushed
ghcr.io/<fork>/orcarouter-lite:edge (linux/amd64, non-root, CMD unchanged),
signed provenance, pulled the image back by digest and got a healthy /health;
the pypi job skipped as designed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@orcacode-review orcacode-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐳 OrcaCode Review

Found 4 issues in this PR: 🟡 4 P2.

Reviewed via OrcaRouter — Route Smarter. Ship Safer. Spend Less.

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
…e-run safe

Four review findings on release.yml, all of them real:

* The build pushed `latest`/`X.Y.Z`/`X.Y` and only then smoke-tested. A failed
  smoke could not unpublish them. The build now pushes only the immutable
  `sha-<short>` tag; a `promote` step moves the release tags onto that digest
  with `docker buildx imagetools create` after the smoke passes.

* The smoke was a plain `docker run`, which executes the host (amd64) variant
  only — the arm64 image was published without ever having been booted. It now
  loops over every platform in the manifest, with a longer timeout for the
  emulated one.

* `attest build provenance` moved after `promote` and now attests the digest
  the release tags actually resolve to, read back rather than assumed:
  imagetools copies a multi-platform index verbatim but wraps a single-platform
  manifest in a fresh one. That keeps `gh attestation verify oci://…:latest`
  pointing at the image people pull. The step also asserts every tag landed on
  one digest.

* The concurrency group was per-ref, so a v0.1.1 and a v0.1.2 push ran in
  parallel and both wrote the shared `latest` tag — last writer wins, and the
  slower older release wins it. All tag releases now share one group.

* The `pypi` job published to PyPI before attaching the dists to the GitHub
  release. A failure in between left the release half-done and unretryable: a
  published PyPI version cannot be replaced, and without `skip-existing` the
  re-run aborts at the upload before reaching the attach. The GitHub release
  now goes first, `skip-existing: true` lets a re-run pass through, and
  `needs: image` stops PyPI from shipping a version whose container never
  booted.

RELEASING.md gains a "What gates what" section describing the new ordering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@orcacode-review orcacode-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐳 OrcaCode Review

No findings — nothing to flag in this PR. Great work!

OrcaCode Review — Route Smarter. Ship Safer. Spend Less.
Engine-reported: 149 calls · 6.2M tokens · 96% cached

❤️ Share · Install OrcaCode Review

Free on GitHub — the review runs on your own OrcaRouter key. If it helped, a shout-out goes a long way.

Share: X · Reddit · LinkedIn
Follow: X · Discord · LinkedIn · OrcaRouter

@yi-here
yi-here merged commit 0d471f3 into Continuum-AI-Corp:main Aug 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants