Prompt and Demonstration Versioning
A prompt is executable configuration at the boundary between deterministic software and a
probabilistic model. Changing one sentence, delimiter, demonstration, schema, renderer, or model
capability can change product behavior. A filename such as prompt.txt, a database row called
latest, or a dashboard label such as “v2” is not enough to explain which program produced a
stored output.
The harness needs two distinct identities:
prompt release template + typed variables + schemas + behavior + demonstrations + compatibility │ ├── immutable release SHA-256 │ ▼render values ─► single-pass renderer ─► rendered prompt instance │ ├── rendered bytes SHA-256 ├── typed variable spans └── instance SHA-256The release describes reusable behavior. The rendered instance describes one request. Both belong in the run envelope and trace. Recording only the final string loses variable boundaries, demonstration provenance, declared compatibility, and the release from which it came. Recording only the release loses the exact user and evidence values supplied to this run.
This chapter builds mosaic-harness::prompt_release: a strict, provider-free prompt program with
typed variables, bounded single-pass rendering, development-only demonstrations, verification and
provenance receipts, capability declarations, schema bindings, semantic versions, and stable
content identities.
Learning objectives
Section titled “Learning objectives”By the end you will be able to:
- distinguish prompt source, prompt release, rendered instance, provider request, and model response;
- define the public behavior contract behind a prompt version;
- bind prompt releases to behavior, input schema, and output schema identities;
- choose when a change is patch, minor, major, or merely a new content digest;
- parse and render placeholders without recursively interpreting user content;
- retain instruction, user-input, and untrusted-evidence segment boundaries;
- bound every variable and the final render independently;
- reject undeclared, missing, malformed, duplicate, and unused variables;
- treat demonstrations as versioned data with provenance, rights, split, and verification;
- prevent held-out evaluation examples from leaking into prompt releases;
- distinguish fixed, retrieved, and generated demonstrations;
- measure demonstration value against token, latency, and leakage costs;
- avoid calling delimiters a complete prompt-injection defense;
- construct release and render identities that support experiments, rollback, and replay;
- test semantic drift with mutations and held-out evaluation rather than intuition.
Prerequisites and dependency order
Section titled “Prerequisites and dependency order”Complete the behavior-contract and run-envelope chapters. A prompt release binds to the exact behavior specification it is trying to satisfy. Its rendered identity becomes part of the run trace. Complete model release/capability chapters so compatibility is expressed as capabilities, not a list of marketing names.
Prompt versioning comes before provider-neutral message construction because a release should be independent of one API’s wire format. It comes before context selection because demonstrations and instructions consume the same finite model context as evidence. It comes before evaluation engineering because release decisions require frozen cases and held-out separation.
Completion artifact
Section titled “Completion artifact”Run:
cd rust/rust-ai-engineeringcargo test -p mosaic-harness prompt_releasecargo run -q -p mosaic-harness --example versioned_prompt_programPinned result:
release ID risk-review-promptsemantic version 1.2.0prompt release SHA-256 ccd0ab78d8648ad84f7ebe8779c718e41eb29324c3fefd6fe4075fdbe4bee14brendered prompt SHA-256 938a6de822f258f44d8c04f0d2974deddea6861dcd97652b4db5f3951db8047crendered instance SHA-256 80f961e1c0e6d17da52b882046177b36df65443148f6a9848b35ef9568511bcerendered bytes 253typed variable segments 3selected demonstrations demo-supported-riskevidence segment SHA-256 9bec125e2628abbfad0dd74793f236c1af138dc47ac2d740c742f7c996f196ccThe evidence value deliberately contains the literal text {{instruction}}. The renderer preserves
it as evidence; it does not substitute the trusted instruction a second time. Thirteen module tests
cover this property plus strict releases, schema/version drift, malformed placeholders, missing,
unknown, oversized, and unused variables, held-out leakage, unverified/duplicate demonstrations,
capability duplication, rendered-content tampering, overlapping detached ranges, and duplicate
detached demonstration references.
A prompt release is more than template text
Section titled “A prompt release is more than template text”The reference type contains:
pub struct PromptRelease { pub schema_version: u32, pub renderer_release: String, pub release_id: String, pub semantic_version: String, pub behavior_specification_sha256: String, pub input_schema_sha256: String, pub output_schema_sha256: String, pub template: String, pub variables: Vec<PromptVariable>, pub demonstrations: Vec<Demonstration>, pub compatible_capabilities: Vec<String>,}Each field answers a replay or release question:
| Field | Question |
|---|---|
| schema/renderer | Which parser and rendering semantics applied? |
| release ID/version | Which human-managed release line is this? |
| behavior digest | What definition of done was it designed for? |
| input schema | What data contract may be rendered? |
| output schema | What response contract does it request? |
| template | Which exact instruction/data layout was used? |
| variables | Which values, trust classes, and byte limits are accepted? |
| demonstrations | Which examples influence behavior and where did they come from? |
| capabilities | Which model contract can execute it honestly? |
The SHA-256 content identity remains authoritative for exact equality. The semantic version communicates compatibility intent to people and release tooling.
Define the public prompt contract before choosing version numbers
Section titled “Define the public prompt contract before choosing version numbers”Semantic Versioning 2.0.0 requires a declared public API and uses
MAJOR.MINOR.PATCH for incompatible changes, backwards-compatible functionality, and compatible
bug fixes. Applying that vocabulary to prompt programs requires an explicit public contract.
For this book, the public prompt contract includes:
- input variables, types, bounds, and trust roles;
- behavior specification identity;
- output schema and validation obligations;
- required model capabilities;
- tool and authority assumptions;
- demonstration-selection semantics;
- refusal, abstention, and escalation behavior;
- supported renderer semantics.
Example version decisions:
| Change | Typical release | Reason |
|---|---|---|
| Fix typo with no intended behavior change | Patch + new digest | Compatible correction; still re-evaluate |
| Add optional evidence variable | Minor | Backwards-compatible input capability |
| Require a new field or change output schema | Major | Existing callers/results are incompatible |
| Change demo selection or instruction wording | Depends on declared behavior | Content digest always changes; version communicates intent |
| Change renderer escaping/tokenization semantics | Major | Same source may produce different bytes |
| Re-run same immutable release | No new version | New rendered/run identities only |
Prompts are probabilistic programs, so “patch” never means “no evaluation required.” It means the declared interface remains compatible. Even punctuation can affect a model. Every released digest must pass the relevant eval gate.
The reference accepts strict three-component numeric versions and rejects leading zeroes. It omits pre-release/build metadata to keep the teaching parser narrow. A production implementation should use a tested SemVer library if it needs the full specification.
Typed variables preserve boundaries software can reason about
Section titled “Typed variables preserve boundaries software can reason about”The reference distinguishes:
pub enum PromptVariableKind { TrustedInstruction, UserInput, UntrustedEvidence,}and gives every variable a name and byte bound:
pub struct PromptVariable { pub name: String, pub kind: PromptVariableKind, pub max_bytes: usize,}The type is a harness fact, not a guarantee the model will respect it. It supports:
- rendering into provider message roles later;
- different preprocessing/redaction policy;
- trace manifests without losing provenance;
- security review of which content can influence instructions;
- token/context budget allocation;
- mutation tests targeted to untrusted segments.
Do not infer trust from the variable name. system_notes can still contain untrusted retrieved
text. The acquisition and policy path assigns the kind.
Strict variable contracts
Section titled “Strict variable contracts”Release validation rejects:
- malformed names;
- duplicate declarations;
- zero or excessive bounds;
- placeholders without declarations;
- declared variables absent from the template;
- unclosed or unexpected delimiters;
- oversized variable collections.
Render validation rejects:
- unknown provided values;
- missing required values;
- values over their individual byte limits;
- final output over the independent render limit.
Unused variables are errors because they often mean a refactor silently stopped supplying evidence or policy text. Unknown values are errors because accepting them creates the illusion that a caller’s field influenced behavior.
Optional variables need explicit semantics: a default, omission branch, or Option-like declaration.
The version-1 companion makes every declared variable required rather than treating empty and absent
as interchangeable.
Single-pass rendering prevents template reinterpretation
Section titled “Single-pass rendering prevents template reinterpretation”The unsafe pattern repeatedly replaces placeholder strings:
for every variable: output = output.replace("{{name}}", value)If a user value contains {{instruction}} and the instruction replacement occurs later, user
content can become template syntax. Order changes behavior.
The companion parses the trusted template once. For each placeholder it copies preceding literal text, appends the corresponding value as opaque bytes, records its byte range and digest, then continues scanning the original template. Inserted values are never parsed as templates.
let start_byte = rendered.len();rendered.push_str(value);let end_byte = rendered.len();segments.push(RenderedVariableSegment { name: name.to_owned(), kind: variable.kind, start_byte, end_byte, content_sha256: sha256_bytes(value),});The result preserves:
- release digest;
- final rendered digest;
- final rendered bytes;
- each variable’s name, kind, byte range, and content digest;
- ordered demonstration IDs.
RenderedPrompt::identity_sha256 recomputes the final digest and every ranged segment digest.
Changing stored bytes without updating receipts fails validation.
Byte ranges require stable encoding
Section titled “Byte ranges require stable encoding”Rust strings are UTF-8, and the renderer records byte ranges. Downstream code must slice only at recorded boundaries created during valid string construction. If another service normalizes Unicode, changes line endings, or encodes provider messages differently, that is a new transformation with a new identity. Do not assume a prompt digest names provider token IDs; tokenizer and message-template releases must also be bound later.
Delimiters help structure; they do not create authority
Section titled “Delimiters help structure; they do not create authority”The example renders:
Instruction:...<user_input>...</user_input><untrusted_evidence>...</untrusted_evidence>Clear separation helps models and reviewers understand intended roles. OWASP’s prompt-injection prevention guidance recommends structured separation as one defense while also calling for least privilege, output validation, human review for high-risk operations, monitoring, and adversarial testing. OWASP’s RAG security guidance warns not to rely solely on prompt positioning and delimiters.
Tags are text to a model, not an OS privilege boundary. An adversarial evidence segment can contain closing tags, fake roles, or instructions. The safe architecture keeps authority outside the model:
- evidence cannot mutate the release;
- tools require independent capability checks;
- proposed effects are authorized against original task intent;
- outputs are validated;
- destructive/high-impact effects require stronger controls;
- traces retain which segment supplied suspicious content.
Escaping tag characters can preserve parse structure, but no escaping scheme turns natural language into harmless data for every model. Test the complete harness.
Demonstrations are executable data dependencies
Section titled “Demonstrations are executable data dependencies”Few-shot examples can change format, reasoning pattern, tool use, tone, and error behavior. Each demonstration therefore needs an identity and data-governance record:
pub struct Demonstration { pub id: String, pub split: DemonstrationSplit, pub input_sha256: String, pub output_sha256: String, pub provenance_sha256: String, pub verification_receipt_sha256: String, pub usage_basis: String,}The reference validates:
- bounded unique IDs;
- unique input digests;
- exact input/output/provenance/verification digests;
- non-zero verification receipt;
- non-empty usage basis;
- development split only;
- bounded demonstration count.
usage_basis is deliberately generic because rights differ: synthetic fixture, authored example,
licensed data, consented customer case, or another approved source. Production governance needs a
typed policy with owner, permitted purpose, retention, jurisdiction, revocation, and sensitive-data
classification.
Verification is not provenance
Section titled “Verification is not provenance”Provenance says where an example came from and what transformations produced it. Verification says why its output is considered acceptable. Rights say whether it may be used. These are separate:
- a correct example can be unlicensed;
- a licensed example can be wrong;
- a verified example can contain sensitive data;
- a synthetic example can be safe to use but unrepresentative.
The verification receipt should bind the exact input/output, oracle release, result, and environment. A non-zero digest is only the first structural step.
Keep development demonstrations out of held-out evaluation
Section titled “Keep development demonstrations out of held-out evaluation”The release compiler rejects HeldOutEvaluation demonstrations. If held-out cases enter the prompt,
the system can appear to improve by memorizing evaluation answers or patterns.
Maintain at least:
- development cases visible to prompt authors;
- demonstration candidate pool derived only from permitted development data;
- held-out regression cases hidden from selection and optimization;
- safety/red-team sets with controlled exposure;
- production shadow cases governed separately.
NIST’s current GenAI evaluation program uses sequestered or blind evaluation data in multiple challenges, illustrating the same measurement principle: evaluation is more credible when the system builder cannot directly optimize against every answer (NIST GenAI evaluation program).
Split identity belongs in dataset metadata, not a filename convention. A copied example retains its origin split and lineage.
Fixed, retrieved, and generated demonstrations
Section titled “Fixed, retrieved, and generated demonstrations”Fixed demonstrations
Section titled “Fixed demonstrations”Pinned in the release and easiest to replay. They consume every request’s context even when irrelevant and may bias the model toward a narrow pattern.
Retrieved demonstrations
Section titled “Retrieved demonstrations”Selected by task similarity, difficulty, modality, or failure class. They can improve relevance but add a selector release, candidate corpus, query identity, scores, diversity policy, and selection receipt to the program identity.
Generated demonstrations
Section titled “Generated demonstrations”Created for the current task by a model or tool. They add quality and injection risks and can silently turn one call into a multi-stage system. Verify them independently and budget their cost. Do not label generated examples as ground truth.
The reference uses one fixed development demonstration. Later context chapters implement selection manifests.
Compatibility is a capability contract
Section titled “Compatibility is a capability contract”The release declares text_generation.json, not a provider name. Compatibility may depend on:
- modality and message roles;
- context capacity;
- structured-output or constrained-decoding support;
- tool calling;
- seed/determinism behavior;
- stop sequences;
- demonstration count;
- safety/data-region policy;
- tokenizer/message template.
A release compatible with one model is not automatically compatible with another model exposing the same marketing capability. The adapter must check the exact request contract, and evaluation must compare behavior.
The reference rejects duplicate/empty capability strings and includes their order/content in release identity. A richer implementation should use the typed capability structures built earlier instead of strings.
Release workflow
Section titled “Release workflow”A production workflow should be:
- define or update behavior and schemas;
- edit prompt source through review;
- validate placeholders and variable contracts;
- select permitted, verified development demonstrations;
- declare capability compatibility;
- compile an immutable release and content digest;
- run unit and mutation tests;
- evaluate development and held-out sets;
- inspect quality, safety, latency, tokens, and cost by slice;
- compare against the current production release;
- approve a semantic version and release identity;
- canary or shadow under explicit policy;
- promote by immutable identity;
- retain previous release for rollback;
- monitor field failures and create new cases.
Never edit the content behind a released version. SemVer explicitly requires released contents to remain immutable. A fix becomes a new version and digest.
Failure investigations
Section titled “Failure investigations”1. “Version 2” produces two behaviors
Section titled “1. “Version 2” produces two behaviors”Symptom: two traces name v2 but contain different rendered prompts.
Root cause: mutable alias was stored instead of immutable release digest.
Repair: resolve aliases at admission and persist the exact release identity. Never rewrite a released artifact.
2. User content becomes a trusted instruction
Section titled “2. User content becomes a trusted instruction”Symptom: a value containing {{instruction}} is replaced with system text.
Root cause: repeated global replacement reparsed inserted values.
Repair: parse only trusted template source and render in one pass. Preserve segment roles.
3. Caller sends a field that has no effect
Section titled “3. Caller sends a field that has no effect”Symptom: API accepts policy_notes, but prompt behavior is unchanged.
Root cause: unknown render values were ignored.
Repair: reject unknown values and unused declarations. Make optional behavior explicit.
4. Prompt works locally but truncates in production
Section titled “4. Prompt works locally but truncates in production”Symptom: provider drops evidence or output capacity.
Root cause: byte limits were mistaken for token/context budgets, or demonstrations were omitted from planning.
Repair: bind tokenizer and message template, estimate and measure tokens, reserve output, and apply a context allocation policy.
5. Eval jumps after adding a demonstration
Section titled “5. Eval jumps after adding a demonstration”Symptom: held-out score becomes nearly perfect on one case family.
Root cause: evaluation inputs or derivatives leaked into the demonstration pool.
Repair: inspect lineage and split receipts, invalidate the result, rebuild clean splits, and evaluate on untouched cases.
6. Demonstration is correct but cannot be used
Section titled “6. Demonstration is correct but cannot be used”Symptom: verification passes; governance blocks release.
Root cause: correctness was confused with rights/consent.
Repair: replace or obtain valid usage basis. Keep provenance, verification, and authorization separate.
7. Delimiters appear intact but an effect is unsafe
Section titled “7. Delimiters appear intact but an effect is unsafe”Symptom: untrusted evidence convinces the model to propose an external action.
Root cause: text separation was treated as authorization.
Repair: enforce capability policy outside the model and test indirect injection. Treat tags as one model-facing signal, not a security boundary.
8. Patch release regresses a rare slice
Section titled “8. Patch release regresses a rare slice”Symptom: aggregate score is stable; one language or high-risk slice falls.
Root cause: semantic-version intent was mistaken for evidence of compatibility.
Repair: gate severe slice regressions and retain rollback. Every digest runs evals.
9. Stored segment receipts no longer validate
Section titled “9. Stored segment receipts no longer validate”Symptom: final rendered digest matches, but a variable range splits UTF-8 or names wrong bytes.
Root cause: normalization or encoding occurred after range creation.
Repair: create a new transformation/identity after normalization and calculate ranges on final bytes. Never reuse stale coordinates.
10. “Best” demonstrations increase cost without quality
Section titled “10. “Best” demonstrations increase cost without quality”Symptom: token use and latency rise; held-out behavior is flat.
Root cause: examples were selected by intuition or training similarity, not measured marginal value.
Repair: ablate each example, test order, measure slices, and keep only justified context.
Security and privacy implications
Section titled “Security and privacy implications”- Prompt source may contain policy details but must not contain credentials.
- System prompts cannot be relied on as secret security controls.
- User/evidence segments stay untrusted regardless of delimiters.
- Demonstrations can leak private data, copyrighted content, secrets, or attack payloads.
- Retrieved demonstrations inherit source and tenant scope.
- Release tools must reject held-out and unauthorized sources.
- Tool authority stays outside prompt text.
- Rendered prompts and traces require access controls and retention policy.
- Low-entropy sensitive values should use protected references rather than public hashes.
- Output validation and effect screening remain mandatory.
- Every prompt, demo, retrieval, model, tool, or policy change reruns adversarial tests.
Performance implications
Section titled “Performance implications”Measure prompt systems across:
- release validation/identity time;
- rendering time and allocations;
- source bytes, rendered bytes, tokens, and modality payloads;
- demonstration tokens and marginal quality;
- tokenizer/provider message overhead;
- time to first token and total latency;
- cache hit rate by immutable prefix identity;
- output length and repair rate;
- quality/safety/cost by release and slice.
The renderer is linear in trusted template bytes plus inserted value bytes, subject to placeholder search and string growth. Precompute parsed template segments for hot paths, reserve final capacity from checked bounds, and cache immutable releases by digest. Never cache rendered private prompts across tenants without explicit scope.
Alternatives and trade-offs
Section titled “Alternatives and trade-offs”Plain text files in Git
Section titled “Plain text files in Git”Excellent review history and simple deployment. Add a manifest/compiler so schemas, demonstrations, compatibility, and immutable identities are not hidden in filenames.
Database prompt registry
Section titled “Database prompt registry”Supports UI editing and rollout metadata. Require append-only releases, review, export, backup, and exact content identity; mutable rows are dangerous.
Provider-managed prompt objects
Section titled “Provider-managed prompt objects”Convenient for one provider, but can hide rendering/version semantics. Store provider object and revision as an adapter artifact while retaining a provider-neutral release.
Code-generated prompts
Section titled “Code-generated prompts”Powerful conditional composition, but behavior can depend on arbitrary code and ambient state. Return a manifest of chosen components and bind compiler/executable identity.
Templating libraries
Section titled “Templating libraries”Mature parsing and escaping can be valuable. Choose explicit missing/unknown behavior, sandbox features, pin versions, disable arbitrary execution, and retain typed segment manifests.
Fine-tuning instead of demonstrations
Section titled “Fine-tuning instead of demonstrations”Can reduce repeated context and stabilize a pattern, but adds dataset, training, model-release, serving, and regression complexity. Compare it against the versioned prompt baseline.
Practical milestone
Section titled “Practical milestone”Extend versioned_prompt_program:
- add optional variables with explicit defaults;
- replace capability strings with typed capability contracts;
- add two development demonstrations and an order-ablation experiment;
- add a held-out fixture and prove the compiler rejects it;
- add a provenance/rights revocation and block new releases using it;
- tokenize the final provider messages with a pinned tokenizer;
- emit a trace manifest linking release, render, demonstrations, message template, and model;
- run a small eval comparing zero-, one-, and two-demonstration releases;
- report quality, tokens, latency, and repair rate;
- write a release decision and rollback trigger.
Exercises
Section titled “Exercises”Recall and predict
Section titled “Recall and predict”- Distinguish release digest, rendered digest, and rendered-instance digest.
- Explain why a semantic version is not exact identity.
- Predict what happens when a variable appears in values but not the release.
- Explain why correct, licensed, and non-sensitive are separate demo properties.
- Predict whether delimiters stop an indirect prompt injection.
Implement and break
Section titled “Implement and break”- Add strict optional-variable defaults.
- Add a repeated placeholder and decide whether one value may appear in multiple ranges.
- Add canonical newline policy and prove it changes release identity.
- Add a maximum total demonstration byte/token budget.
- Add a revoked usage-basis state and release failure.
- Mutate rendered bytes, segment ranges, and demo order; test each identity.
- Add full SemVer pre-release/build parsing using a maintained library.
Design and measure
Section titled “Design and measure”- Design a prompt release for image-plus-text evidence.
- Design retrieved-demo selection without held-out leakage.
- Compare fixed demos with a small fine-tuned adapter.
- Define major/minor/patch rules for your public prompt contract.
- Threat-model a non-technical prompt editor UI.
- Benchmark parsed-template caching and allocation.
Solution guidance
Section titled “Solution guidance”- Release digest names reusable program semantics; rendered digest names final bytes; instance digest also binds typed segments and demonstrations.
- Versions communicate compatibility intent; only immutable content identity proves equality.
- Strict rendering rejects the unknown variable.
- Verification, usage authorization, and privacy classification use different evidence.
- No. Delimiters help structure; authority and effect controls remain external.
- Model optionality in the schema, including whether absent differs from empty, and bind defaults to release identity.
- Repetition can be legitimate. Record every occurrence or declare one logical variable with multiple ranges; never silently collapse them.
- Normalize before release hashing/rendering, name the policy, and preserve source when needed.
- Bound each demo, count, total bytes, and provider tokens.
- New compilation fails; historical releases remain auditable but cannot be newly promoted.
- Every semantic mutation must change or invalidate the responsible identity.
- Test official SemVer vectors rather than writing a partial parser and calling it complete.
- Keep image artifacts outside text, bind locators/processor, and construct provider-neutral multimodal messages later.
- Candidate pool must be development-only; preserve selector/corpus/query/score identities.
- Hold task/eval constant and compare quality, latency, context cost, serving cost, and regressions.
- Base rules on inputs, outputs, behavior, capabilities, and renderer—not line counts.
- Require authentication, review, diff, schema validation, demo governance, eval gates, immutable publish, and rollback.
- Measure complete rendering, identities, bytes/tokens, concurrency, and cache scope.
Check your understanding
Section titled “Check your understanding”- Which changes must alter the prompt release digest?
- What is the prompt program’s public API?
- Why is renderer identity part of the release?
- How does single-pass rendering treat placeholder-looking user content?
- What does a typed untrusted segment enforce by itself?
- Why reject unused variables?
- Which receipts does a demonstration need?
- Why can no held-out example enter the prompt?
- When is a demo-selector change a program change?
- What identities are required to reproduce provider tokens?
- Why does every patch release still need evaluation?
- What is the rollback unit?
Primary references
Section titled “Primary references”- Semantic Versioning 2.0.0
- OWASP LLM Prompt Injection Prevention Cheat Sheet
- OWASP RAG Security Cheat Sheet
- OWASP AI Agent Security Cheat Sheet
- NIST GenAI evaluation program
- Serde attributes
- RustCrypto SHA-2
Definition of done
Section titled “Definition of done”You are finished when:
- prompt source, immutable release, rendered instance, provider request, and response are distinct;
- release identity binds behavior, schemas, renderer, template, variables, demos, and capabilities;
- semantic version rules refer to a declared public prompt contract;
- releases are immutable and aliases resolve to exact identities at admission;
- variables have explicit trust kind and independent bounds;
- unknown, missing, malformed, unused, duplicate, and oversized values fail closed;
- rendering is single-pass and inserted values are never reparsed as template source;
- rendered variable ranges and digests validate;
- demonstrations bind input/output, provenance, verification, rights basis, and split;
- held-out evaluation data cannot enter a release;
- delimiters are documented as structure, not authorization;
- quality, safety, tokens, latency, cost, and slice regressions gate promotion;
- rollback uses an immutable previous release;
- all 13 module tests, the focused example, and full workspace gates pass.