← All articles
EngineeringApril 5, 2026 · 6 min read

Promotion Gates: How Your Registry Should Protect Production

Most artifact registries are dumb storage. They have no opinion about whether a package is safe to promote to production. Trunx thinks that's the wrong design.

The promotion problem

Your team builds a new version of payment-service. The CI pipeline passes. The artifact lands in the registry. A developer promotes it to staging. It passes QA. Someone manually promotes it to production.

Six weeks later, a security researcher finds a CRITICAL vulnerability in one of its dependencies. It's been in production for 42 days. You have no record of who promoted it, whether the CVE scan passed at the time, or whether the approval process was followed.

This is what happens when your registry is just a file server.

What a promotion gate actually is

A promotion gate is a set of conditions that must all pass before an artifact can move from one environment to the next. In Trunx, promotion is a first-class concept — not a webhook you have to wire up yourself.

You define a pipeline:

# trunx-policy.yaml
promotion:
  stages:
    - name: dev
    - name: staging
      gates:
        - cve_threshold: HIGH    # no HIGH or CRITICAL CVEs
        - test_coverage: 80      # minimum 80% test coverage
        - approvals: 1           # at least one approval from @platform-team
    - name: prod
      gates:
        - cve_threshold: MEDIUM  # stricter in prod
        - approvals: 2           # two approvals required
        - immutable: true        # cannot be overwritten once promoted

Then you promote:

trunx promote @platform/payment-service@2.5.0 dev → staging

  Checking gates for staging...
  ✓ CVE scan: 0 HIGH, 0 CRITICAL (2 MEDIUM, 4 LOW — below threshold)
  ✓ Test coverage: 87% (threshold: 80%)
  ✗ Approvals: 0/1 required

  Gate failed: missing approval from @platform-team
  Run `trunx approve @platform/payment-service@2.5.0 --stage staging` to approve

The gate is enforced by the registry, not by a CI script that someone can comment out. The artifact physically cannot be pulled from the staging repository until all gates pass.

Semver enforcement: no more accidental breaking changes

Every push to Trunx is validated against semantic versioning rules. Not just "is this a valid semver string" — actually validated against the previous version.

If @platform/api-client@2.1.0 exported a function authenticate() and your 2.1.1 removes it, that's a breaking change in a patch release. Trunx rejects it:

trunx push @platform/api-client@2.1.1

  Semver validation failed:
  → Removed export: authenticate() was present in 2.1.0
  → Patch releases must not contain breaking changes

  Suggested version: 3.0.0 (breaking change)
  Override with: trunx push --allow-breaking (requires admin token)

This catches the most common source of accidental breakage in multi-team repositories: someone bumps a patch version, removes a function they thought was internal, and three dependent services start failing in production.

Immutable releases

Once an artifact is promoted to production in Trunx, it's locked. No overwrites. No deletions. Not by developers, not by CI pipelines, not by administrators without a two-person approval.

This matters for two reasons:

Reproducibility. The artifact that's running in production on day 1 is byte-for-byte identical to the one running on day 365. No "we must have quietly fixed something" surprises.

Compliance. SOC2 requires evidence that production artifacts weren't modified after deployment. Trunx's immutability is cryptographically enforced — the Sigstore attestation covers the exact SHA256 digest, and any modification would produce a different digest that fails verification.

Version diff: know what changed

Before promoting any artifact, you can diff two versions to understand exactly what changed:

trunx diff @platform/payment-service@2.4.0 @platform/payment-service@2.5.0

  Changed files (12):
    M  internal/stripe/client.go          +45 -12
    M  internal/stripe/webhooks.go        +120 -67
    A  internal/stripe/idempotency.go     +88
    M  go.mod

  New dependencies (2):
    + github.com/stripe/stripe-go/v78@78.3.0
    + github.com/google/uuid@1.6.0

  Removed dependencies (1):
    - github.com/satori/go.uuid@1.2.0     ⚠ had 1 MEDIUM CVE

  CVE delta:
    + 0 new CVEs introduced
    - 1 CVE resolved (CVE-2021-3538 via uuid package swap)

This is the information you want before you approve a promotion. Not "LGTM, ship it" — actually understanding what changed, what new dependencies were added, whether the CVE surface grew or shrank.

The audit trail

Every promotion, approval, rejection, and override is logged with actor, timestamp, gate results, and cryptographic proof. Permanently. The log is append-only.

trunx audit @platform/payment-service --stage prod

  2026-04-05T14:22:01Z  alice@company.com   promoted 2.5.0 dev→staging
  2026-04-05T15:03:44Z  bob@company.com     approved 2.5.0 for staging
  2026-04-05T16:45:12Z  alice@company.com   promoted 2.5.0 staging→prod
  2026-04-05T17:01:33Z  carol@company.com   approved 2.5.0 for prod
  2026-04-05T17:02:11Z  dave@company.com    approved 2.5.0 for prod
  2026-04-05T17:02:15Z  system              2.5.0 marked immutable in prod

One-click export for SOC2, ISO27001, or any internal audit. Every question your auditor will ask about production artifact changes is answered by this log.

Release channels

Production isn't always binary. Some teams run a canary deployment alongside stable. Some ship a public API with stable and beta tracks that consumers can opt into.

Trunx supports release channels natively:

trunx channel create --repo platform/api-client --name beta
trunx push @platform/api-client@3.0.0-beta.1 --channel beta

# Consumers pin to a channel, not a version:
# registry.company.com/npm/platform/api-client@stable
# registry.company.com/npm/platform/api-client@beta

When you promote 3.0.0-beta.1 to stable, all consumers on the stable channel get the update automatically. No version bumps across fifteen repositories.

The net result

A registry that enforces promotion gates, semver, immutability, and full audit logs isn't just storage. It's the last line of defence before your artifacts reach production. It's where supply chain security actually happens — not in a separate tool, not in a separate process, but embedded in the artifact lifecycle itself.

That's what Trunx is built to be.