Give draft and live state different meanings
Draft state answers: “What change is currently being proposed and previewed?” Live state answers: “What approved artifact is currently serving users?” These states need separate identities because a new draft can appear immediately after an earlier draft is approved.
A minimal release record contains:
draftRevision
approvedRevision
liveRevision
promotionReceipts[]
Saving a draft changes only draftRevision. Approval binds a reviewer to the
exact artifact digest. Promotion can change liveRevision only when the
artifact still matches that approval and the promotion succeeds.
The P23 fixture exercises this model in memory. Saving its first draft leaves live state empty. Attempting to promote that unapproved draft records a rejection and still leaves live state unchanged.
Approve an artifact, not a moving label
“Approve latest” is ambiguous when another change can arrive between review and release. Store the revision and digest that the reviewer actually inspected. When promotion begins, compare the candidate artifact with that approval.
If the draft changes after approval, the new artifact is not automatically approved. The P23 suite verifies this directly: revision A can be approved, revision B can become the current draft, and promotion of B is still rejected until B receives its own approval.
Human review remains necessary even when tests pass. The approval record should identify the reviewer, scope, evidence considered, and material exceptions. An agent that produced the change or tests cannot approve its own production authority.
Keep preview configuration outside the live boundary
Separate code state is not enough if a draft silently receives production credentials, production data access, or live side effects. Define an environment boundary for:
- identity and test users;
- databases, buckets, and queues;
- API keys and service accounts;
- email, payments, webhooks, and scheduled jobs;
- callback URLs and network access; and
- logging, monitoring, and retention.
The local fixture represents this as a simple configuration scope and rejects a draft labeled with live configuration. That is only a model. A real system must verify its secret scopes, connector behavior, network paths, and data permissions with current platform evidence.
Use synthetic or approved non-production data. A preview is not safe merely because its URL is obscure or its interface displays a draft badge.
Make promotion an explicit action
Promotion should take an approved artifact and target environment, apply the release procedure, evaluate a defined success signal, and then record the outcome. The receipt should contain at least:
- revision and artifact digest;
- target environment;
- promotion actor;
- successful, failed, or rejected outcome; and
- attributable time or sequence.
GitHub deployment environments provide one concrete model for environment-specific protection and deployment activity. The important requirement is not GitHub itself. It is a protected, inspectable transition from approved artifact to live state.
Do not record success before the defined promotion and validation steps finish. If the health signal fails, retain a failed receipt and keep the prior live revision known.
Test failure without changing live state
The P23 fixture injects a failure when revision B is promoted after revision A is already live. The result is a failed receipt, while A remains the live artifact. This is the behavior a release owner needs during a failed rollout: the system should not report B as live and should not lose the identity of A.
A real platform needs stronger checks. Depending on the application, test:
- partial rollout across multiple instances;
- a migration that completed before application health failed;
- a side effect triggered before promotion was declared successful;
- stale traffic or cache behavior;
- cancellation and concurrent promotion; and
- missing deployment history.
The in-memory model does not demonstrate any of those distributed behaviors. It supplies a small state contract that a real platform implementation can be tested against.
Roll back through the same gate
Rollback should not be a hidden mutation of live state. Re-promote a known, previously approved artifact and issue a new receipt identifying the rollback actor and outcome.
The P23 test suite promotes revision A, promotes revision B, and then re-promotes A. The final action produces the third receipt and restores A as the modeled live artifact. Seven tests passed on Node.js 22.18.0 with no failures.
This result is bounded to synthetic in-memory state. The fixture does not authenticate reviewers, persist receipts, protect a real environment, run a deployment, or recover data. A code rollback can succeed while data remains incompatible, so data recovery needs its own procedure.
Release-boundary checklist
- Saving or previewing a draft cannot change live state.
- Approval identifies an immutable revision and artifact digest.
- A later draft does not inherit an earlier approval.
- Preview cannot silently use live-only configuration or data.
- Promotion is an explicit, attributable action.
- Rejected and failed promotions retain receipts.
- Failed promotion leaves the known live revision unchanged.
- Rollback uses the promotion gate and creates a new receipt.
- Data recovery is evaluated separately from application rollback.
Frequently asked questions
Are separate URLs enough?
No. Separate URLs help users distinguish environments, but code identity, configuration, data, credentials, approval, and release authority also need separate controls.
Can tests automatically approve a draft?
Tests can supply release evidence and block known failures. A human owner still needs to accept the scope, limitations, and production decision required by the organization.
Should every internal tool have a complex CI system?
No. The mechanism can be small, but draft state, approval, live identity, failure outcome, and recovery ownership should remain explicit when coworkers depend on the tool.