Skip to content

ADR-0055: Mission-end economy contract: engine-priced awards, first-class fail/abandon

The economy model (economy-model.md, GWP-708) fixes the rules: contracts pay credits, credits buy capability, and the one write to the pooled wallet is mission resolution. The board already posts the price: mission_board.c computes payout_credits = base_payout × threat × broke_bonus / 100 per instance, and each MissionInstance carries reputation_delta_pass and reputation_delta_fail from its template.

The runtime’s award path inverts that authority:

  1. The cart prices its own completion. nosh_mission_complete(uint32_t credits, int8_t rep_delta) (runtime/src/nosh_runtime.c ~584) takes the award from the caller. All four launch carts self-pay through the Tier-2 award-credits / modify-reputation primitives; ICE Breaker mints its payout in-cart as (+ (* threat 200) (lfsr-range 0 200)) (carts/carts/icebreaker.lsp:257, awarded at :204). The posted board price and the paid price are two unrelated numbers.
  2. Failure exists on the board and nowhere else. reputation_delta_fail is displayed (board_screen.c:292 renders REP +x / -y) and applied by no code path. The event enum (nosh_event_bus.h) has MISSION_COMPLETED (103) and MISSION_ABANDONED (104); there is no MISSION_FAILED.
  3. Fail and abandon are one number in the template and two in the sims. Both career simulators (tools/economy_sim.py, economy-sim.lsp) distinguish rep_loss_fail (1.0 × threat) from rep_loss_abandon (0.5 × threat); the MissionTemplate struct carries a single reputation_delta_fail.
  4. The posted price is dropped at accept. mission_board_select_contract publishes MISSION_ACCEPTED (contract_id, threat, payout) and then removes the instance from the board; nothing retains the accepted contract’s pricing for award time.
  • ADR-0056 makes credits buy capability. The moment purchases persist, cart-priced completion is an unbounded faucet: any cart (or any bug in one) can mint arbitrary credits into the durable wallet.
  • The mission-context lifecycle is being wired now. runtime_mission_begin has zero production callers (tests only), so the completion contract can change before anything depends on its current shape.
  • The kn86-sdk crate pins NoshAPI v1; versioning.md classes a cart-callable-surface removal or signature change as a major bump.
  • ADR-0040 §3: UDS is durable and written only by DeckRunner as the earned consequence of a sanctioned outcome. The wallet is UDS.
  • Cart capability is a binding-set chosen at context creation (ADR-0005, 2026-06-14 amendment); enforcement must use that mechanism, no new runtime permission checks.

Mission completion becomes an outcome report; nOSh owns pricing. Concretely:

  1. complete-mission reports, the engine prices. The Tier-2 surface becomes (complete-mission :outcome sym &key bonuses) with :outcome:success / :partial; the C seam becomes nosh_mission_complete(NoshMissionOutcome outcome). The credits/rep parameters are removed. nOSh computes the award from the accepted contract’s posted payout_credits and reputation_delta_pass, applying any objective-level adjustments per ADR-0043 (banked :on-complete rewards and :on-resolve escrow settle here).
  2. The accepted MissionInstance snapshot is the pricing authority. On NOSH_EVENT_MISSION_ACCEPTED the engine copies the full accepted MissionInstance into mission run-state (the MissionContext gains an accepted snapshot beside active / current_phase_index). Award-time pricing reads that snapshot; later board regeneration cannot reprice a live contract.
  3. Fail and abandon become first-class resolutions.
    • New Tier-2 primitive (mission-fail) (C: nosh_mission_fail): resolves the active contract as failure, pays zero credits, applies reputation_delta_fail from the snapshot, ends the mission context.
    • (abandon-mission) (already enumerated per ADR-0028/0029) resolves as abandonment: zero credits, the abandon fraction of the fail penalty (item 4).
    • New event topic NOSH_EVENT_MISSION_FAILED = 106, appended inside the 1xx mission-lifecycle band (ordinal-stability rule in nosh_event_bus.h honored); it rides the existing mission payload variant. Completion / failure / abandonment each publish their topic with the resolved award in the payload.
  4. Fail-vs-abandon representation: one template field plus an engine ratio. The template and instance keep the single reputation_delta_fail. Abandonment applies reputation_delta_fail × abandon_rep_ratio (rounded toward zero). abandon_rep_ratio is an engine-side balancing variable owned by the career simulators (currently 0.5, matching rep_loss_abandon = 0.5 vs rep_loss_fail = 1.0). The MissionTemplate struct and the future defmission grammar do not widen.
  5. Wallet writes are System-tier only. award-credits and modify-reputation leave the cart binding-set: they are no longer bound into cart-tier Fe contexts, and Tier 2 of ADR-0005 no longer lists them. They remain bound in System-tier contexts (bare-deck bounty claims, debrief flows, the runtime’s own award path via stdlib_credit_add / stdlib_rep_modify). Enforcement test (specified here, built with GWP-719): a cart-tier context evaluating (award-credits 1) must raise unbound symbol (the same pattern as the existing System-tier UI-kit exclusion test); one case each for award-credits and modify-reputation, plus a positive case proving a System-tier context still resolves both.
  6. award-credits disposition: removed from the cart tier entirely (Option B below). No capped or flavored cart-callable variant survives.
  7. Versioning: handled as a pre-freeze correction inside NoshAPI v1. Strictly, items 1 and 5 are major-bump-class changes under versioning.md. They land inside v1 because: no api_version negotiation is implemented yet (versioning.md §Implementation Notes), the indexed vtable does not exist so no slot is tombstoned (tier membership is a binding-set), and every consumer is an in-tree launch cart migrated in lockstep. versioning.md gains a note that v1’s cart surface froze after this ADR.
  8. Scope split. This ADR is the contract. GWP-719 implements the runtime side only. Cart migration (all four launch carts stop self-paying and report outcomes) and regeneration of the committed runtime/assets/*.kn86 bundles (runtime CI loads the committed bundles; stale ones would keep the old carts alive) are separate engineering follow-ons.

Option A: Keep award-credits cart-callable (status quo)

Section titled “Option A: Keep award-credits cart-callable (status quo)”

Carts continue pricing and paying their own completions.

Rejected because it contradicts the economy model’s ruling that mission resolution is the one wallet write, and once ADR-0056 lands it converts every cart into a credit mint. The posted board price becomes decorative.

Option B: Remove wallet writes from the cart tier; engine prices from the accepted snapshot (ACCEPTED)

Section titled “Option B: Remove wallet writes from the cart tier; engine prices from the accepted snapshot (ACCEPTED)”

Chosen because it makes the posted price the paid price, makes the wallet structurally writable only through sanctioned resolution (ADR-0040 §3 realized at the FFI layer), and costs only an in-tree cart migration while runtime_mission_begin still has zero production callers.

Option C: Keep award-credits cart-callable but restricted (per-mission cap, or callable only outside mission context)

Section titled “Option C: Keep award-credits cart-callable but restricted (per-mission cap, or callable only outside mission context)”

Rejected because a capped mint is still a mint; it needs per-cart accounting the runtime otherwise never needs, and no legitimate use case remains: contract payouts route through the snapshot, and bare-deck bounties are runtime-tier code that keeps System-tier access anyway.

A second template field (reputation_delta_abandon) was considered and rejected: the sims express abandonment as a fixed fraction of the fail cost, per-template divergence has no design driver, and a second field widens the template struct, the instance, and the future defmission grammar for a value that is derivable. If a future cart genuinely needs per-template abandon costs, adding the field then is additive.


DimensionA (status quo)B (engine-priced, chosen)C (capped cart writes)
Wallet integrity once credits buy capability✗ open mint✓ one sanctioned write path◐ bounded mint
Posted price == paid price✗ unrelated✓ snapshot is authority✗ still cart-priced
Cart migration cost✓ none◐ four in-tree carts + asset regen◐ same carts, plus cap plumbing
Runtime complexity✓ none◐ snapshot retention + two resolutions✗ per-cart accounting
Matches economy-model / ADR-0040 rulings

Honest cost of B: the four launch carts and their committed .kn86 bundles must migrate before the enforcement test can turn on, and complete-mission’s signature change is a v1-internal break we can only afford because nothing out-of-tree consumes v1 yet.


  • The wallet has exactly one faucet: engine-side resolution pricing from the posted contract. Cart bugs cannot inflate the economy.
  • reputation_delta_fail finally does something; the fail/abandon split the sims model becomes real runtime behavior with one new knob (abandon_rep_ratio).
  • CIPHER and future subscribers get a complete resolution vocabulary (COMPLETED / FAILED / ABANDONED) with awards in the payload.
  • The economy simulators and the runtime now share one pricing shape: posted payout, pass delta, fail delta, abandon ratio.
  • A v1-internal FFI break (signature change + two primitives leaving the cart tier), acceptable only in the current pre-freeze window; the window closes when any out-of-tree cart ships.
  • Four launch carts carry dead self-pay code until the migration follow-on lands; the enforcement test cannot merge before they migrate.
  • The accepted snapshot adds a MissionInstance copy to run-state (small, fixed-size).
  • GWP-719 (runtime): nosh_mission_complete re-signature, nosh_mission_fail, snapshot retention on accept, NOSH_EVENT_MISSION_FAILED, abandon ratio application, cart-context unbinding + the enforcement tests specified in Decision 5.
  • Cart migration (separate task): the four launch carts report outcomes and stop calling award-credits / modify-reputation; regenerate the committed runtime/assets/*.kn86 bundles with kn86cart.
  • Simulator sync (separate task): surface abandon_rep_ratio as a named knob in both sims (values already agree at 0.5).

Documentation Updates (REQUIRED, Spec Hygiene Rule 3)

Section titled “Documentation Updates (REQUIRED, Spec Hygiene Rule 3)”

The board has always told the operator what a job pays; until now the runtime let the cart decide what actually landed in the wallet, and failure cost nothing because no code applied it. This ADR moves pricing to the engine: accepting a contract freezes its posted terms, finishing it reports an outcome, and nOSh pays exactly what was posted, docks reputation on failure, and docks half as much for walking away early. Carts lose the ability to write the wallet at all. The change is cheap today because nothing yet calls the mission lifecycle in production; it would be ruinous to retrofit after credits start buying capability.