Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Latest commit

 

History

History
217 lines (170 loc) · 13.1 KB

File metadata and controls

217 lines (170 loc) · 13.1 KB

Connector Release Contract

This document describes the supply-chain guarantees the connector project makes about its published artifacts (npm package and GHCR container image), and the recommended pinning strategy for downstream consumers.

Artifacts

Each release publishes two artifacts:

Artifact Location Architectures
npm package @toon-protocol/connector on npmjs.com n/a (pure JS)
Container image ghcr.io/toon-protocol/connector on GitHub Container Registry linux/amd64, linux/arm64 (from the first release after PR #63)

Releases are cut by semantic-release on every push to main, when the conventional-commit history warrants a version bump. The release pipeline is defined in .github/workflows/release.yml.

Multi-arch images (linux/amd64 + linux/arm64) ship from the first release after PR #63; adding architectures is a build-only change (no semver bump). Removing an architecture is a breaking change requiring a MAJOR bump.

Stability guarantees

For releases cut after PR #60 merged (i.e. the first release cut from a connector main containing the docker-release ref: main checkout fix):

  • Container image, semver tag → digest stability: the digest a semver tag resolves to (e.g. ghcr.io/toon-protocol/connector:3.5.1) is stable for the lifetime of that tag. Releases never reuse a previously-published version number, so a given vX.Y.Z tag points to a single digest forever.
  • Container image, label correctness: the org.opencontainers.image.version label on the manifest equals the semver tag the image was published under. This is enforced by a post-publish assertion in the release workflow that fails the run on mismatch.
  • npm package, version stability: @toon-protocol/[email protected] is immutable on npmjs.com per npm's package-management rules.

API stability

The connector's HTTP admin API surface (everything under /admin/*) follows strict semver discipline. The rules below tell consumers what kind of version bump to expect for any change.

Change Bump Example
/admin/* field addition MINOR v3.3.x → v3.4.0
/admin/* field rename or removal MAJOR v3.x → v4.0
/admin/* endpoint addition MINOR v3.3.x → v3.4.0
/admin/* endpoint rename or removal MAJOR v3.x → v4.0
ILP packet wire-format change MAJOR v3.x → v4.0
Image architecture addition (e.g. arm64) none build-only change
Image architecture removal MAJOR breaks pinning consumers

Townhouse pin discipline

Townhouse pins the connector image by digest in packages/townhouse/dist/image-manifest.json (built by the publish workflow — Story 45.1). Each MINOR connector release triggers a manual digest-pin bump in townhouse, gated on the contract canary (pnpm --filter @toon-protocol/sdk test:integration -- tests/integration/connector-contract.test.ts) passing at the new digest. Patch releases (vX.Y.z → vX.Y.z+1) do not require a townhouse bump unless the patch fixes a behavior townhouse actively relied on being broken. Major bumps require a deliberate townhouse migration cycle and a CONNECTOR_MIGRATION.md row.

Supply-chain signing

Starting from v3.6.0 (cut after PR #66 merged), every connector and ATOR sidecar image is cosign-signed via keyless OIDC — no static keys, no secrets beyond the default GITHUB_TOKEN.

Verifying a release image

# Connector
DIGEST=$(docker buildx imagetools inspect ghcr.io/toon-protocol/connector:<tag> \
  --format '{{ json .Manifest }}' | jq -r '.digest')

cosign verify "ghcr.io/toon-protocol/connector@${DIGEST}" \
  --certificate-identity-regexp \
    'https://github\.com/toon-protocol/connector/\.github/workflows/(build-and-publish|release)\.yml@.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

# ATOR sidecar (narrower regex — only build-and-publish.yml signs the sidecar)
SIDECAR_DIGEST=$(docker buildx imagetools inspect ghcr.io/toon-protocol/ator-sidecar:<tag> \
  --format '{{ json .Manifest }}' | jq -r '.digest')

cosign verify "ghcr.io/toon-protocol/ator-sidecar@${SIDECAR_DIGEST}" \
  --certificate-identity-regexp \
    'https://github\.com/toon-protocol/connector/\.github/workflows/build-and-publish\.yml@.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Expected output: Verification for ... -- The following checks were performed: ... certificate identity is ... and exit 0.

Notes

  • Signatures cover the multi-arch manifest index digest (not per-platform sub-manifests). Verifying against the index digest is sufficient; docker pull <image>@<index-digest> resolves to the correct per-platform sub-manifest at pull time.
  • Each signature is automatically published to the Sigstore Rekor transparency log. cosign verify consults the log automatically — no separate flag is needed.
  • The signing certificate's SAN encodes the exact workflow path (e.g. https://github.com/toon-protocol/connector/.github/workflows/build-and-publish.yml@refs/tags/v3.6.0). The --certificate-identity-regexp flag is required; omitting identity flags causes cosign to reject the verification — by design.
  • The (build-and-publish|release)\.yml regex tolerates both signers: release.yml fires on the merge-commit-to-main and build-and-publish.yml fires on the tag push. Both produce valid signatures on the same digest.

Recommended pinning strategy

For maximum supply-chain integrity, pin by content digest rather than by semver tag:

ghcr.io/toon-protocol/connector@sha256:<digest>

Digest pinning gives byte-for-byte reproducibility regardless of any future tag-pointer changes (re-tagging, deletion, or registry compromise). Combined with Supply-chain signing, digest pinning is verifiable end-to-end.

For non-production use where tag mutability is acceptable, semver tags (:3.5.1, :3.5, :3, :latest) are produced by docker/metadata-action and follow standard semver-tag floating semantics.

Staying current

Downstream consumers (notably toon-protocol/town's townhouse package) learn about new connector releases via:

  1. GitHub UI subscription — preferred: Watch → Custom → Releases on toon-protocol/connector. Releases-only is a UI-side filter the REST API does not expose.
  2. gh CLI subscription — fallback: subscribes to all repository events (not releases-only):
    gh api -X PUT /repos/toon-protocol/connector/subscription \
      -f subscribed=true -f ignored=false

Automated subscription (e.g. a GitHub Actions cron polling gh release view and opening a digest-bump PR into townhouse) is OUT OF SCOPE for v1 and tracked as Open Thread #2 in the Townhouse HS-Mode v1 epic.

Historical tag corruption (releases prior to first post-#60 release)

A bug in docker-release (introduced when the job was added in PR #45, fixed in PR #60) caused actions/checkout to resolve the workflow trigger SHA — the parent of the chore(release): X.Y.Z [skip ci] commit semantic-release creates — instead of main's tip. As a result, git describe --tags --abbrev=0 returned the previous release's tag, and docker/build-push-action silently overwrote that tag's GHCR pointer with the new release's content.

Net effect: every semver-tagged GHCR image published between PR #45 and PR #60 carries content one release ahead of what its tag and org.opencontainers.image.version label claim. The corruption is upward (newer content under older label), so consumers of these tags are not running stale code — but the tag-to-content mapping is unreliable for audit and compliance purposes.

The :latest, :<major> (e.g. :3), and commit-SHA (e.g. :f87d7b9) tags are not affected, because docker/metadata-action derives them from out-of-band sources rather than the broken git describe value.

GHCR tag Label org.opencontainers.image.version Manifest revision (commit) Actual release contents
:3.4.2 3.4.2 f87d7b9b (PR #59 merge) v3.5.0
:3.4.1 3.4.1 541a38ee (PR #57 merge) v3.4.2
:3.4.0 3.4.0 5059807a (PR #55 merge) v3.4.1
:3.3.3 3.3.3 c0068b4a v3.4.0
:3.3.2 3.3.2 2bed61c2 one release ahead
:3.3.1 3.3.1 96be4e78 one release ahead
:3.3.0 3.3.0 057b332c (PR #45 merge) first release with this workflow shape

No semver tag exists for v3.5.0 — the build that should have been published under :3.5.0 was published under :3.4.2 instead. Consumers needing v3.5.0 content must pin by digest (the same digest currently at :3.4.2 and :latest) until the next release after PR #60 fires, which will be the first cleanly-tagged release.

Historical tags will not be re-tagged or backfilled; doing so would silently swap content under tags some consumers may already be pinning, with unpredictable blast radius. From the first post-#60 release forward, the guarantees in Stability guarantees apply.

Verification

Three mechanisms guard against future tag-vs-content drift:

  1. Pre-publish (issue #61 / PR #60): the docker-release job checks out main's tip so git describe resolves to the just-cut tag, mirroring the same fix applied to npm-release in PR #48.

  2. Post-publish (issue #61): after docker/build-push-action, the workflow inspects the just-pushed manifest with docker buildx imagetools inspect and asserts that org.opencontainers.image.version equals the tag. Any mismatch fails the workflow run.

  3. Town mirror drift detection: The doc body is mirrored at packages/sdk/CONNECTOR_RELEASE_CONTRACT.md in toon-protocol/town. The town copy prepends a 3-line comment header; verify body equivalence from the toon-protocol/connector repo root (with toon-protocol/town cloned alongside as a sibling directory, e.g. ../town):

    diff CONNECTOR_RELEASE_CONTRACT.md \
         <(tail -n +4 ../town/packages/sdk/CONNECTOR_RELEASE_CONTRACT.md)

    Expected output: empty. Any diff is a drift defect — open a follow-up PR in both repos to restore equivalence.

References