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.