ADR-0055: Mission-end economy contract: engine-priced awards, first-class fail/abandon
Context
Section titled “Context”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:
- 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-2award-credits/modify-reputationprimitives; 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. - Failure exists on the board and nowhere else.
reputation_delta_failis displayed (board_screen.c:292rendersREP +x / -y) and applied by no code path. The event enum (nosh_event_bus.h) hasMISSION_COMPLETED(103) andMISSION_ABANDONED(104); there is noMISSION_FAILED. - Fail and abandon are one number in the template and two in the sims. Both career simulators (
tools/economy_sim.py,economy-sim.lsp) distinguishrep_loss_fail(1.0 × threat) fromrep_loss_abandon(0.5 × threat); theMissionTemplatestruct carries a singlereputation_delta_fail. - The posted price is dropped at accept.
mission_board_select_contractpublishesMISSION_ACCEPTED(contract_id, threat, payout) and then removes the instance from the board; nothing retains the accepted contract’s pricing for award time.
Forcing functions
Section titled “Forcing functions”- 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_beginhas zero production callers (tests only), so the completion contract can change before anything depends on its current shape.
Constraints
Section titled “Constraints”- The kn86-sdk crate pins NoshAPI v1;
versioning.mdclasses 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.
Decision
Section titled “Decision”Mission completion becomes an outcome report; nOSh owns pricing. Concretely:
complete-missionreports, the engine prices. The Tier-2 surface becomes(complete-mission :outcome sym &key bonuses)with:outcome∈:success/:partial; the C seam becomesnosh_mission_complete(NoshMissionOutcome outcome). The credits/rep parameters are removed. nOSh computes the award from the accepted contract’s postedpayout_creditsandreputation_delta_pass, applying any objective-level adjustments per ADR-0043 (banked:on-completerewards and:on-resolveescrow settle here).- The accepted
MissionInstancesnapshot is the pricing authority. OnNOSH_EVENT_MISSION_ACCEPTEDthe engine copies the full acceptedMissionInstanceinto mission run-state (theMissionContextgains anacceptedsnapshot besideactive/current_phase_index). Award-time pricing reads that snapshot; later board regeneration cannot reprice a live contract. - 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, appliesreputation_delta_failfrom 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 innosh_event_bus.hhonored); it rides the existingmissionpayload variant. Completion / failure / abandonment each publish their topic with the resolved award in the payload.
- New Tier-2 primitive
- Fail-vs-abandon representation: one template field plus an engine ratio. The template and instance keep the single
reputation_delta_fail. Abandonment appliesreputation_delta_fail × abandon_rep_ratio(rounded toward zero).abandon_rep_ratiois an engine-side balancing variable owned by the career simulators (currently 0.5, matchingrep_loss_abandon= 0.5 vsrep_loss_fail= 1.0). TheMissionTemplatestruct and the futuredefmissiongrammar do not widen. - Wallet writes are System-tier only.
award-creditsandmodify-reputationleave 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 viastdlib_credit_add/stdlib_rep_modify). Enforcement test (specified here, built with GWP-719): a cart-tier context evaluating(award-credits 1)must raiseunbound symbol(the same pattern as the existing System-tier UI-kit exclusion test); one case each foraward-creditsandmodify-reputation, plus a positive case proving a System-tier context still resolves both. award-creditsdisposition: removed from the cart tier entirely (Option B below). No capped or flavored cart-callable variant survives.- 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: noapi_versionnegotiation 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.mdgains a note that v1’s cart surface froze after this ADR. - 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/*.kn86bundles (runtime CI loads the committed bundles; stale ones would keep the old carts alive) are separate engineering follow-ons.
Options Considered
Section titled “Options Considered”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.
On the fail/abandon representation
Section titled “On the fail/abandon representation”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.
Trade-off Analysis
Section titled “Trade-off Analysis”| Dimension | A (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.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- The wallet has exactly one faucet: engine-side resolution pricing from the posted contract. Cart bugs cannot inflate the economy.
reputation_delta_failfinally 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.
Negative / Accepted costs
Section titled “Negative / Accepted costs”- 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
acceptedsnapshot adds aMissionInstancecopy to run-state (small, fixed-size).
Follow-on work this ADR creates
Section titled “Follow-on work this ADR creates”- GWP-719 (runtime):
nosh_mission_completere-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 committedruntime/assets/*.kn86bundles withkn86cart. - Simulator sync (separate task): surface
abandon_rep_ratioas 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)”-
docs/adr/ADR-0055-mission-end-economy-contract.md: this file. -
docs/adr/ADR-0005-ffi-surface.md: Amendment Log entry + header line (this PR). -
docs/adr/README.md: index row (this PR). -
docs/software/runtime/mission-control.md§5.4: completion prose updates to the outcome-report shape (follow-up with GWP-719). -
docs/software/runtime/economy-model.md: knob list gainsabandon_rep_rationaming alignment (follow-up with the simulator sync). -
runtime/src/nosh_event_bus.h/runtime/src/nosh_runtime.h: enum + signature updates (GWP-719).
Narrative
Section titled “Narrative”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.