Salvage
salvage · an adoption of Job resolution, version 2
Use in your package
The first button opens the authoring tool with these answers and numbers already filled in. You still add the rules and tuning of your own game.
What is in the zip file
The zip file holds the adoption and its acceptance tests. In the authoring tool, choose Add contract and pick this zip file. If you edit your package outside the authoring tool, unpack the zip file in your package folder. The files of the zip file go into contracts/.
The contract that this adoption uses
This contract covers crafting benches, build queues, research, and cooking. It decides how one job takes its inputs, does its work, stops, fails, and delivers its result. It also covers limits on how many jobs may work or wait at the same time. This contract does not cover what a recipe makes or what it is worth.
Questions
The supplied answer is marked on each question. Pick other answers to see what changes. Nothing is saved here: the zip file and the authoring tool use the supplied answers.
Does this job need inputs? A job claims an input when it reserves the input, so that no other rule can use it. A job spends an input when it uses the input up.
What happens to recorded work when this job stops?
- Asked when
- the work phases list has at least one row.
- If not asked
- The job has no work span before finish. Start and finish settle in one batch, unless an allowed delivery answer keeps the job in the visible state finished. No partial work is recorded. No interruption can stop the job before its finish gate. A recoverable failure while the job waits still moves the job to stopped, and the start gate is its resume point.
What does a listed failure do when its row gives no result of its own?
- Asked when
- the failures list has at least one row.
- If not asked
- No accepted or finished job fails; refusal, stopping, cancellation, and blocked delivery are different outcomes.
What happens to inputs this job still claims when it is canceled?
- Asked when
- the cancellations list has at least one row.
- If not asked
- An accepted job cannot be canceled. A reviewer confirms this before the adoption relies on it.
What happens to later waiting jobs that needed a canceled job's output?
- Asked when
- the dependent cancellations list has at least one row.
- If not asked
- Canceling a job leaves later waiting jobs in place, even when they needed its output.
When do required inputs become unavailable to other rules?
- Asked when
- Job inputs is Required inputs.
- If not asked
- This job claims no input, so there is no claim time to choose.
Does a stopped job keep the inputs it has already claimed?
- Asked when
- Partial work is Keep with job or Reverse while stopped or Reset at stop and Input claim timing is Taken at request or Taken at start or With progress.
- If not asked
- When no pre-finish claim exists there is nothing to hold; otherwise the stopped job keeps its claim.
When is the completed result delivered?
- Asked when
- the delivery cases list has at least one row, or the output destinations list has at least one row, or the collection events list has at least one row, or the readmission events list has at least one row, or the redelivery events list has at least one row.
- If not asked
- A finished output is delivered automatically as soon as its normal destination accepts it.
What happens when the whole output does not fit at its normal destination?
Numbersno numbers
This contract has no numbers to set.
Rules3 rules
A rule is a check between the numbers. Validation reports a rule that fails.
-
Forbidden when Input claim timing is With progress and Partial work is Reset at stop.
-
Forbidden when Input claim timing is With progress and Partial work is Reverse while stopped. (a warning, not an error)
-
Forbidden when Delivery trigger is On collection and Output full is Hold undelivered, or Delivery trigger is On collection and Output full is Try destinations.
Lists17 lists
Each list holds the rows this adoption supplies. A list can be empty.
Job
job
| Job declared in | Normal destination declared in | Output kind |
|---|---|---|
salvage.recovered-gems | salvage.player-backpack | new-thing |
Read first
read-first
This adoption declares no read first.
Work phases
work-phases
This adoption declares no work phases.
Interruptions
interruptions
This adoption declares no interruptions.
Reversal rules
reversal-rules
This adoption declares no reversal rules.
Progress rounding rules
progress-rounding-rules
This adoption declares no progress rounding rules.
Cancel return rules
cancel-return-rules
This adoption declares no cancel return rules.
Collection events
collection-events
This adoption declares no collection events.
Readmission events
readmission-events
This adoption declares no readmission events.
Redelivery events
redelivery-events
| Id | Redelivery declared in |
|---|---|
storage-changed | salvage.storage-capacity-changed |
Work limits
work-limits
This adoption declares no work limits.
Standing orders
standing-orders
This adoption declares no standing orders.
Failures
failures
This adoption declares no failures.
Cancellations
cancellations
This adoption declares no cancellations.
Dependent cancellations
dependent-cancellations
This adoption declares no dependent cancellations.
Delivery cases
delivery-cases
This adoption declares no delivery cases.
Output destinations
output-destinations
| Id | Destination declared in |
|---|---|
mine-cart | salvage.mine-cart-capacity |
Test inputsscope and seeds
Some tests need a scope or seeds from the adoption before they can run.
This adoption supplies no test inputs. Every test uses its default inputs.
Acceptance tests21 tests apply
The contract comes with 66 tests. A test that runs once per row is counted once for each row. Tests that do not apply to these answers are still listed, with the reason.
reports-identify-the-run
reports identify their job run and state change
Applies to salvage.
A request reads the rule cited at job-declared-in once, makes any pool and place assignment, and reports job-requested. Across the runs needed to reach every constructible report, every report in each run keeps that run's id and the same job id and records the cause, work, claim changes, and chosen destination. Every report except job-requested carries old and new state; job-requested carries a new state only. The report job-rejected is not a state. The report destination-refused has finished as both its old and its new state.
Test steps and diagnostics
- Given
the set of
salvagejob runs needed to reach every transition and every report without a state change that the adoption can construct- When
- the report log is read in settlement order
- Then
- the request reads the rule cited at job-declared-in once, assigns exactly one pool and one cited place where those declarations exist, then reports job-requested
- every report in each run carries that run's id and the same job id, plus cause, work before and after, units newly claimed, released, spent, or lost, and the destination when one is chosen
- every report except job-requested carries old and new state; job-requested carries a new state only and is outside any ordered instant batch
- job-rejected ends the run without becoming a lifecycle state, and destination-refused has old and new state both finished
- Diagnostics
salvage-job-rule-read-logsalvage-report-logsalvage-state-tracesalvage-work-before-aftersalvage-claim-ledger
claims-keep-their-source
claims preserve ownership and settle once
Applies to salvage.
A claim makes its units unavailable everywhere else but keeps their source known. Release returns the units that are still claimed to that source. Spending and loss remove units, so neither kind can later be released. An adoption with no inputs has no claim event. In that case this test checks nothing.
Test steps and diagnostics
- Given
each claim, release, spend, and loss that a run of
salvagecan construct; an adoption with no inputs has no claim, and in that case this test checks nothing- When
- another rule tries to use a claimed unit and the run later settles that unit
- Then
- the claimed unit is unavailable to every other rule while its source remains recorded
- release returns each unit that is still claimed to that source
- spending or losing removes the unit, and no removed unit is later released
- Diagnostics
salvage-claim-ledgersalvage-source-recordsalvage-usable-inputs
exact-inputs-required
a request with a missing tested share is refused and changes nothing
Does not apply to salvage: job inputs is no inputs, not required inputs.
no-input-check-is-empty
a no-input job skips the input check
Applies to salvage.
This job needs no input, so its input check and every input settlement are empty. Pool, place, capacity, and other admission gates still apply.
Test steps and diagnostics
- Given
an otherwise eligible
salvagerequest- When
- the request reaches its availability check
- Then
- the input check is skipped and the claim, release, spend, and loss fields remain empty
- non-input admission gates still decide whether the request is admitted
- Diagnostics
salvage-admission-tracesalvage-claim-ledger
request-time-claim-is-atomic
request-time inputs are claimed at admission
Does not apply to salvage: input claim timing is not asked for salvage.
waiting-admission-uses-pool-capacity
Row.id uses a waiting slot for each waiting admission
Does not apply to salvage: work-limits has no rows.
readmission-event-pairs-with-reservation
Row.id is legal exactly for pre-start reservation
Does not apply to salvage: readmission-events has no rows.
reservation-needs-a-readmission-event
pre-start reservation declares a readmission event
Does not apply to salvage: output full is try destinations, not wait before start.
waiting-records-every-current-reason
waiting records reasons but no work
Applies to salvage.
Each waiting shape this adoption can construct reports every current reason and records no work. Only the taken-at-request answer can already hold an input claim there. For an adoption with no waiting shape, this test checks nothing.
Test steps and diagnostics
- Given
each admitted
salvagerequest that the adoption can construct with one or more reasons it cannot start; when the adoption can construct none, this test checks nothing- When
- the admitted request enters waiting
- Then
- job-waiting reports every current reason
- no work is recorded and no input is claimed in waiting except an already-required request-time claim
- Diagnostics
salvage-report-logsalvage-state-tracesalvage-claim-ledger
start-time-claim-is-atomic
start-time inputs are claimed before start
Does not apply to salvage: input claim timing is not asked for salvage.
finish-time-inputs-stay-at-source
finish-time inputs remain usable before the finish gate
Does not apply to salvage: input claim timing is not asked for salvage.
progress-claim-starts-with-rounded-share
progress claiming begins with the initial share
Does not apply to salvage: input claim timing is not asked for salvage.
start-counts-settle-before-report
start frees the waiting slot and takes a working slot
Applies to salvage.
At job-started, the job has freed any waiting slot and taken any working slot. No job stays in started; it is a transition boundary. A pre-start output reservation remains held. For a shape that this adoption cannot construct, this test checks nothing.
Test steps and diagnostics
- Given
each admitted
salvagejob that can start, with and without a pool where those shapes exist- When
- the job crosses the start boundary
- Then
- any waiting slot is freed and any working slot is taken before job-started
- an output reservation, where the selected answer created one, remains held through delivery
- no job is ever observed to stay in started; it is a transition boundary
- Diagnostics
salvage-count-tracesalvage-reservation-ledgersalvage-report-log
work-phase-enters-working
Row.id enters working and reaches the finish gate
Does not apply to salvage: work-phases has no rows.
instant-start-settles-finish-in-one-batch
a job without work settles start and finish together
Applies to salvage.
With no work phase, start and finish settle in one batch unless hold-undelivered, a try-destinations route that every destination refuses, or on-collection keeps the job in the visible state finished, or a finish-gate cause stops it at the gate: an unavailable finish-time claim or a wait-until-space capacity wait. For an adoption with a work phase, this test checks nothing.
Test steps and diagnostics
- Given
the case with no work phase: if
salvagehas no work-phases row, a request that passes its gates; if it has a work phase, this test checks nothing- When
- the job starts
- Then
- start and finish settle in one ordered batch
- the ordered batch ends early only when hold-undelivered, a try-destinations route that every destination refuses, or on-collection keeps the job in the visible state finished, or a finish-gate cause stops it at the gate: an unavailable finish-time claim or a wait-until-space capacity wait
- Diagnostics
salvage-report-logsalvage-state-trace
no-stop-before-the-finish-gate
a job without work does not stop before finish
Applies to salvage.
An adoption with no work-phases row records no partial work and never enters stopped for an interruption of work before its finish gate. A recoverable failure while waiting is a failure, not an interruption: it moves the job to stopped under step 16 with the start gate as its resume point. For an adoption with a work phase, this test checks nothing.
Test steps and diagnostics
- Given
the case where
salvagehas no work-phases row; for an adoption with a work phase, this test checks nothing- When
- an admitted job advances to its finish gate
- Then
- no partial work is recorded
- the job never enters stopped for an interruption of work before its finish gate; a recoverable failure while waiting moves the job to stopped under step 16 with the start gate as its resume point
- Diagnostics
salvage-work-before-aftersalvage-state-trace
progress-rounding-rule-pairs-with-progress
Row.id is legal exactly for progress claiming
Does not apply to salvage: progress-rounding-rules has no rows.
progress-needs-a-rounding-rule
progress claiming declares a rounding rule
Does not apply to salvage: input claim timing is not asked for salvage.
declared-interruption-stops-once
Row.id records an ordinary stop once
Does not apply to salvage: interruptions has no rows.
unnamed-instructions-cannot-stop
only named instructions can stop work
Applies to salvage.
An instruction absent from every interruptions row is not an allowed stop. With an empty list, a job stops only when it is unable to advance. For an adoption with no working state, or with no unnamed instruction, this test checks nothing.
Test steps and diagnostics
- Given
each instruction this adoption can construct that is absent from all interruptions rows; for an adoption with no working state, or with no unnamed instruction, this test checks nothing
- When
- the instruction is offered to a
salvagejob in working, where that state exists
- the instruction is offered to a
- Then
- the instruction does not add a stop cause and does not report job-stopped
- Diagnostics
salvage-stop-causessalvage-report-log
kept-work-stays-with-the-job
an ordinary stop preserves recorded work
Does not apply to salvage: partial work is not asked for salvage.
reversal-rule-pairs-with-reversing-work
Row.id is legal exactly for reversing stopped work
Does not apply to salvage: reversal-rules has no rows.
reversing-work-needs-a-reversal-rule
reversing stopped work declares a reversal rule
Does not apply to salvage: partial work is not asked for salvage.
reset-work-clears-on-ordinary-stop
an ordinary stop resets recorded work
Does not apply to salvage: partial work is not asked for salvage.
stopped-job-holds-claim
a stopped job keeps its current claim
Does not apply to salvage: stopped claims is not asked for salvage.
stopped-job-releases-and-reclaims
a stopped job releases and must reclaim inputs
Does not apply to salvage: stopped claims is not asked for salvage.
resume-waits-for-every-cause
the same job resumes only after every cause clears
Applies to salvage.
The same job resumes only after every active cause clears and every gate passes. It competes for a free working slot through its pool where the adoption declares one, and otherwise proceeds without a pool. If a gate or claim is unavailable, it remains stopped and leaves the working slot free; on success it reports job-resumed and returns to its recorded resume point without creating another request.
Test steps and diagnostics
- Given
each stopped
salvageshape the adoption can construct, including overlapping causes where constructible- When
- causes clear one at a time and the last cause clears
- Then
- no resume occurs while a cause remains
- after the last cause clears, the same job rechecks gates and competes for a free working slot through its pool where the adoption declares one, and otherwise proceeds without a pool; it reports job-resumed only on success and creates no new request
- if a gate or claim is unavailable, the job remains stopped and leaves the working slot free
- a resume after interrupted work returns to its recorded work point, while a resume after a recoverable failure from waiting returns to the start gate
- Diagnostics
salvage-stop-causessalvage-resume-pointsalvage-run-id-tracesalvage-report-log
unasked-stopped-claim-answer-holds
when stopped-claims is not asked, the claim is held
Applies to salvage.
Where stopped-claims was not asked, a request-time claim held by a job stopped from waiting or by a finish-gate cause remains assigned after any progress adjustment. For an adoption that answered stopped-claims, or that cannot reach this case, this test checks nothing.
Test steps and diagnostics
- Given
each
salvagerequest-time claim held while its job is stopped from waiting or by a finish-gate cause and stopped-claims was not asked; for an adoption that answered stopped-claims, or that has no such claim, this test checks nothing- When
- the stop settles
- Then
- the current claim remains assigned to the stopped job after any progress-based adjustment
- Diagnostics
salvage-claim-ledgersalvage-stop-causes
finish-gate-wait-retains-completed-work
a full destination stops the job at the finish gate
Does not apply to salvage: output full is try destinations, not wait until space.
finish-claim-wait-is-slot-retaining
an unavailable finish claim stops the job at the finish gate
Does not apply to salvage: input claim timing is not asked for salvage.
work-limit-offers-room-and-releases-counts
Row.id offers and frees working slots in lifecycle order
Does not apply to salvage: work-limits has no rows.
pool-releases-at-finished
Row.id frees the working slot after finish
Does not apply to salvage: work-limits has no rows.
pool-releases-at-delivered
Row.id frees the working slot after delivery
Does not apply to salvage: work-limits has no rows.
each-place-pool-assigns-one-place
Row.id keeps separate slots for each place
Does not apply to salvage: work-limits has no rows.
shared-pool-uses-one-count
Row.id shares one set of slots
Does not apply to salvage: work-limits has no rows.
request-order-pool-offers-in-admission-order
Row.id offers a free working slot in request order
Does not apply to salvage: work-limits has no rows.
game-rule-pool-offers-in-cited-order
Row.id offers a free working slot in its cited order
Does not apply to salvage: work-limits has no rows.
no-pool-means-every-ready-job-starts
without a pool every ready job starts
Applies to salvage.
An adoption with no work-limits row starts every otherwise-ready job and never reports job-waiting with a pool cause. For an adoption with pools, this test checks nothing.
Test steps and diagnostics
- Given
the case where
salvagehas no work-limits row; for an adoption with pools, this test checks nothing- When
- an otherwise-ready job reaches its start gate
- Then
- every otherwise-ready job starts
- no job reports job-waiting with a pool cause
- Diagnostics
salvage-state-tracesalvage-report-log
standing-order-scans-are-level-triggered
Row.id is scanned in written order until every free working slot is filled
Does not apply to salvage: standing-orders has no rows.
read-first-note-precedes-verification
Row.id is read before the adoption is trusted
Does not apply to salvage: read-first has no rows.
failure-row-fires-from-its-state
Row.id resolves only from Row.from state
Does not apply to salvage: failure result is not asked for salvage.
failure-row-result-recoverable
Row.id applies its recoverable result
Does not apply to salvage: failures has no rows.
failure-row-result-ruined
Row.id applies its ruined result
Does not apply to salvage: failures has no rows.
progress-failure-adjusts-claim-after-loss
Row.id settles progress claims after failure loss
Does not apply to salvage: input claim timing is not asked for salvage.
no-failure-without-a-failure-row
accepted jobs do not fail without a declared failure
Applies to salvage.
An adoption with no failures row never reports job-failed from an accepted or finished job. For an adoption with a failure row, this test checks nothing.
Test steps and diagnostics
- Given
the case where
salvagehas no failures row; for an adoption with a failure row, this test checks nothing- When
- accepted and finished jobs are observed
- Then
- no accepted or finished job reports job-failed
- Diagnostics
salvage-report-log
declared-cancellation-settles-one-job
Row.id takes a claim snapshot and cancels one job
Does not apply to salvage: cancel return is not asked for salvage.
cancel-return-rule-pairs-with-rule-sized-return
Row.id is legal exactly for a cancellation return chosen by a rule
Does not apply to salvage: cancel-return-rules has no rows.
rule-sized-return-needs-a-return-rule
a cancellation return chosen by a rule declares a return rule
Does not apply to salvage: cancel return is not asked for salvage.
no-cancellation-without-a-cancellation-row
accepted jobs cannot be canceled without a declared cancellation
Applies to salvage.
An adoption with no cancellations row never reports job-canceled, and a reviewer confirms that promise before the adoption relies on it. For an adoption with a cancellation row, this test checks nothing.
Test steps and diagnostics
- Given
the case where
salvagehas no cancellations row; for an adoption with a cancellation row, this test checks nothing- When
- accepted jobs are observed and the adoption prepares to rely on this promise
- Then
- no accepted job reports job-canceled
- Diagnostics
salvage-report-logsalvage-review-record
dependent-cancellation-follows-answer
Row.id settles later dependent jobs
Does not apply to salvage: cancel dependents is not asked for salvage.
finish-spends-and-establishes-one-output
the finish gate commits inputs and one output atomically
Applies to salvage.
At the finish gate, required claims are spent and one successful output is established in the same commit before job-finished. A compound result is still one unit. For an in-place job, the state change at the site is the delivery; it has no destination capacity and answers never-full. A new thing follows the selected delivery path.
Test steps and diagnostics
- Given
a job in
salvageat the finish gate with every required claim and capacity gate satisfied- When
- the job finishes
- Then
- required claims are spent and exactly one successful output is established atomically before job-finished
- a compound output remains one delivery unit
- where the job row declares in-place, the state change at the site is the in-place delivery; an in-place output has no destination capacity, so the adoption answers never-full and no capacity branch is taken; where it declares new-thing, the one output follows the selected delivery rules
- Diagnostics
salvage-claim-ledgersalvage-output-identitysalvage-state-tracesalvage-report-log
delivery-case-invokes-selected-answer
Row.id invokes the selected capacity response
Does not apply to salvage: delivery-cases has no rows.
normal-destination-never-blocks
the normal destination accepts every whole output
Does not apply to salvage: output full is try destinations, not never full.
pre-start-reservation-blocks-admission
a job waits before admission for reserved output room
Does not apply to salvage: output full is try destinations, not wait before start.
held-output-retries-only-on-event
a held output remains one inaccessible owned result
Does not apply to salvage: output full is try destinations, not hold undelivered.
held-output-needs-a-redelivery-event
a held output declares a redelivery event
Does not apply to salvage: output full is try destinations, not hold undelivered.
redelivery-event-pairs-with-retry-answer · storage-changed
storage-changed is legal exactly for a refused-output retry
Applies to the storage-changed row.
Row storage-changed is legal exactly when output-full is hold-undelivered or try-destinations. After refusal, only salvage.storage-capacity-changed retries delivery, using the same output without repeating work. When the row is present under another answer, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
redelivery-events row
storage-changedin the adoption- When
- the output-full answer is checked and, under hold-undelivered or try-destinations,
salvage.storage-capacity-changedoccurs after refusal
- the output-full answer is checked and, under hold-undelivered or try-destinations,
- Then
- the row is legal only under hold-undelivered or try-destinations; when the row is present under any other answer, the adoption is invalid, and this test fails
- under either legal answer the lifecycle retries only on the cited event and keeps the same output without repeating work
- Diagnostics
salvage-event-logsalvage-delivery-attemptssalvage-output-identity
fallback-destination-pairs-with-ordered-route · mine-cart
mine-cart is legal exactly for the ordered route
Applies to the mine-cart row.
Fallback row mine-cart at salvage.mine-cart-capacity is legal only when output-full is try-destinations. When the row is present under any other answer, the adoption is invalid, and this test fails.
Test steps and diagnostics
- Given
output-destinations row
mine-cartin the adoption- When
- the output-full answer is checked
- Then
- the row is legal only under try-destinations; when the row is present under any other answer, the adoption is invalid, and this test fails
- Diagnostics
salvage-declaration-record
fallback-destination-keeps-written-order · mine-cart
mine-cart is tried in its written place
Applies to the mine-cart row.
The ordered route tries the normal destination, then fallback mine-cart at salvage.mine-cart-capacity in written order. Each refusal reports destination-refused. When this row accepts, it receives the whole output and no later row is tried. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
one whole output, its normal destination and every fallback before
mine-cartrefusing it, withmine-cartable to accept it- When
- the route reaches
salvage.mine-cart-capacity
- the route reaches
- Then
- each refusal before acceptance reports destination-refused with both states finished
- the whole output is delivered at
mine-cart, no later fallback is tried, and the output is never split
- Diagnostics
salvage-delivery-attemptssalvage-report-logsalvage-output-location
ordered-route-keeps-one-output
the ordered route tries whole-output destinations in order
Applies to salvage.
For a new-thing job, the route tries the normal destination and every fallback in written order, delivers the whole output to the first place that accepts, and stops there. Each refusal is reported as destination-refused. If all refuse, the same output stays finished without polling and cited redelivery events restart the route. Where every destination can refuse, the adoption declares a redelivery event; a route on which at least one destination cannot refuse the output needs none. An in-place job answers never-full, so this test never applies to it.
Test steps and diagnostics
- Given
a finished
salvageoutput whose normal destination refuses it, with each fallback acceptance pattern the adoption can construct; an in-place job answers never-full, so this test never applies to it- When
- the route tries the normal destination and fallback rows in written order
- Then
- every refusal reports destination-refused and the first accepting destination receives the whole output
- no later destination is tried after acceptance and the output is never split
- if all destinations refuse, the same output stays finished without polling; each cited redelivery event retries from the normal destination, and an adoption with no such event never retries
- where every destination on the route can refuse the output, the adoption declares a redelivery event; a route on which at least one destination cannot refuse the output needs none
- Diagnostics
salvage-delivery-attemptssalvage-output-identitysalvage-report-log
automatic-delivery-reports-once
an accepted output is delivered immediately
Applies to salvage.
Under the automatic answer, or where delivery-trigger was never asked and delivery is therefore automatic, an accepted output moves from finished to delivered and reports job-delivered exactly once. For an adoption answering on-collection, this test checks nothing. Where the job row declares in-place, this test checks only the single report.
Test steps and diagnostics
- Given
a finished
salvageoutput accepted by its chosen destination under the automatic answer, or where delivery-trigger was never asked and delivery is therefore automatic; for an adoption answering on-collection, this test checks nothing, and where the job row declares in-place, this test checks only the single report- When
- capacity resolution accepts it
- Then
- the job moves finished to delivered immediately and reports job-delivered exactly once
- Diagnostics
salvage-state-tracesalvage-report-log
collection-event-pairs-with-collection-delivery
Row.id is legal exactly for collection delivery
Does not apply to salvage: collection-events has no rows.
collection-needs-a-collection-event
collection delivery declares a collection event
Does not apply to salvage: delivery trigger is automatic, not on collection.
collection-delivery-waits-for-event
an accepted output remains accessible until collection
Does not apply to salvage: delivery trigger is automatic, not on collection.
rejection-ends-the-requested-run
the lifecycle does not retry a refused request
Applies to salvage.
A refusal ends the run of the refused request. The run does not start again when the reason clears: a later retry is a new run that the requester starts, unless a standing order creates it. Where this adoption declares no standing-orders row, no request repeats automatically.
Test steps and diagnostics
- Given
each
salvagerequest that the adoption can construct as refused- When
- the refusal settles and its cause later clears
- Then
- the run of the refused request remains ended and the lifecycle creates no retry
- a later retry has a new run created by the requester unless a standing-order scan creates it
- where this adoption declares no standing-orders row, no request repeats automatically
- Diagnostics
salvage-run-id-tracesalvage-request-log
lifecycle-holds
the lifecycle and settlement order hold for the whole run
Applies to salvage.
Across sequences of requests, starts, advances, interruptions, resumes, failures, cancellations, finishes, and deliveries over one run, each transition settles with its claim changes and reports before the next begins. Claim changes precede the report unless a numbered step says otherwise. The stated forward and side paths are the only observed transitions. Instant automatic delivery keeps job-started, job-finished, and job-delivered in one batch. Same-moment order comes from Event resolution, not this contract. The adoption supplies the audit seeds through verification inputs.
Test steps and diagnostics
- Holds
one transition, all of its claim changes, and all of its reports settle before another transition begins; claim changes precede the transition report unless a numbered step states otherwise; an instant start with an accepting destination keeps job-started, job-finished, and job-delivered in one ordered batch with no outside event between them; only the forward path requested to waiting to started to working to finished to delivered, with unvisited states skipped without changing order, the stopped and resume side path, the cancellation side path, terminal failure, and recoverable failure into stopped are observed; same-moment events enter one at a time in the order Event resolution supplies, and this contract adds no ordering rule
- Seeds
["audit-a","audit-b"]- Scope
sequences of requests, starts, advances, interruptions, resumes, failures, cancellations, finishes, and deliveries over one run- Diagnostics
salvage-state-tracesalvage-claim-ledgersalvage-report-logsalvage-first-ordering-violation
JSONthe adoption as one file
Answers you try on this page do not change this file. To change an adoption, open it in the authoring tool.
{
"questions": {
"job-inputs": {
"asks": "Does this job need inputs? A job claims an input when it reserves the input, so that no other rule can use it. A job spends an input when it uses the input up.",
"options": {
"required-inputs": {
"meaning": "The job needs one or more inputs. Brewing one potion may need two herbs and one bottle.",
"semantics": "The request checks the required share at the request gate. The lifecycle claims and spends that share at the selected times."
},
"no-inputs": {
"meaning": "The job claims and spends no input. Researching Pottery may need an unlocked lab but no material.",
"semantics": "Input checks, claims, returns, and spending are empty. Eligibility conditions may still prevent the game from offering the request."
}
}
},
"partial-work": {
"asks": "What happens to recorded work when this job stops?",
"when": {
"row-count": {
"work-phases": "non-empty"
}
},
"otherwise": "The job has no work span before finish. Start and finish settle in one batch, unless an allowed delivery answer keeps the job in the visible state finished. No partial work is recorded. No interruption can stop the job before its finish gate. A recoverable failure while the job waits still moves the job to stopped, and the start gate is its resume point.",
"options": {
"keep-with-job": {
"meaning": "Recorded work stays with the same job. A half-built wall remains halfway built after rain stops the work.",
"semantics": "An ordinary stop preserves all recorded work. Only this job can resume from it."
},
"reverse-while-stopped": {
"meaning": "Recorded work falls while the job is stopped. A cooling forge loses heat while no smith works.",
"semantics": "Recorded work decreases under reversal-declared-in and never passes below no work. Claim changes caused by the decrease settle through lifecycle step 12."
},
"reset-at-stop": {
"meaning": "Recorded work returns to none when the job stops. An interrupted lockpick attempt starts again from the beginning.",
"semantics": "The first ordinary stop sets the recorded work to none. On cancellation, the recorded work becomes none after the claim snapshot is taken."
}
}
},
"failure-result": {
"asks": "What does a listed failure do when its row gives no result of its own?",
"when": {
"row-count": {
"failures": "non-empty"
}
},
"otherwise": "No accepted or finished job fails; refusal, stopping, cancellation, and blocked delivery are different outcomes.",
"options": {
"partial-inputs-or-work": {
"meaning": "Failure may lose part of the current inputs, recorded work, or both, whichever exist. A spill may lose one cup of broth while the same soup remains unfinished.",
"semantics": "The cited failure rule may lose a strict subset of claimed inputs, reduce recorded work without passing below no work, or do both. It creates no successful output and leaves the same job stopped."
},
"job-ruined": {
"meaning": "Failure ends this job permanently. An overcooked meal is ruined and cannot finish as the same meal.",
"semantics": "The job enters terminal failed and creates no successful output. A finished but undelivered new output is destroyed."
}
}
},
"cancel-return": {
"asks": "What happens to inputs this job still claims when it is canceled?",
"when": {
"row-count": {
"cancellations": "non-empty"
}
},
"otherwise": "An accepted job cannot be canceled. A reviewer confirms this before the adoption relies on it.",
"options": {
"return-claimed": {
"meaning": "Every input that the job still claims returns to its source. Canceling a queued soldier returns the 50 gold that was reserved for the soldier.",
"semantics": "Release every unit still claimed by the job. Do not add units that were never claimed or were already lost."
},
"return-nothing": {
"meaning": "No claimed input returns. Canceling a fired clay pot spends the clay already placed in the kiln.",
"semantics": "Spend every unit still claimed by the job and release none. Inputs not yet claimed remain at their sources."
},
"return-by-rule": {
"meaning": "A game rule chooses the returned share. Canceling a half-built wall may return unused bricks but not dried mortar.",
"semantics": "Release the subset named by cancel-return-declared-in, never more than the current claim, and spend the rest."
}
}
},
"cancel-dependents": {
"asks": "What happens to later waiting jobs that needed a canceled job's output?",
"when": {
"row-count": {
"dependent-cancellations": "non-empty"
}
},
"otherwise": "Canceling a job leaves later waiting jobs in place, even when they needed its output.",
"options": {
"end-dependents": {
"meaning": "Later jobs that can no longer get the needed output are canceled too. Canceling an iron ingot also cancels the waiting sword that needed it.",
"semantics": "Cancel every transitively dependent later waiting job whose requirement can no longer be met. Settle them in waiting order."
},
"leave-dependents": {
"meaning": "Later jobs remain waiting. A sword order may wait for another iron ingot after its first ingot job is canceled.",
"semantics": "Leave every later dependent job waiting. The wait ends only when the requirement is met again or the job is canceled."
}
}
},
"input-claim-timing": {
"asks": "When do required inputs become unavailable to other rules?",
"when": {
"flag": {
"job-inputs": [
"required-inputs"
]
}
},
"otherwise": "This job claims no input, so there is no claim time to choose.",
"options": {
"taken-at-request": {
"meaning": "All inputs are claimed when the request is accepted. A queued soldier reserves its full gold cost before training starts.",
"semantics": "Atomically claim the full requirement at admission, whether the job waits or starts."
},
"taken-at-start": {
"meaning": "All inputs are claimed when work starts. A workbench leaves wood available until the bench begins the chair.",
"semantics": "Atomically claim the full requirement at start. No other rule can use claimed units."
},
"taken-at-finish": {
"meaning": "Inputs stay available until successful finish. A ritual checks for three crystals only when the casting completes.",
"semantics": "Claim and spend the full requirement at the finish gate. No pre-finish input claim exists."
},
"with-progress": {
"meaning": "Inputs are claimed as work advances. A wall claims half its bricks when it reaches half of its recorded work.",
"semantics": "Before each observable advance, claim the rounded share required at the proposed work point. Release the matching share when work reverses."
}
}
},
"stopped-claims": {
"asks": "Does a stopped job keep the inputs it has already claimed?",
"when": {
"flag": {
"partial-work": [
"keep-with-job",
"reverse-while-stopped",
"reset-at-stop"
],
"input-claim-timing": [
"taken-at-request",
"taken-at-start",
"with-progress"
]
}
},
"otherwise": "When no pre-finish claim exists there is nothing to hold; otherwise the stopped job keeps its claim.",
"options": {
"hold": {
"meaning": "The job keeps the claim while stopped. A paused wall keeps its reserved bricks at the site.",
"semantics": "Retain the current claim while stopped. Work changes may first reduce a progress-based claim."
},
"release-and-reclaim": {
"meaning": "The claim is released while the job is stopped, and the job must claim the inputs again before it resumes. A paused research topic releases its points for another topic.",
"semantics": "Release the current claim while stopped. Reclaim the required share before resuming; remain stopped if that share is unavailable."
}
}
},
"delivery-trigger": {
"asks": "When is the completed result delivered?",
"when": {
"any": [
{
"row-count": {
"delivery-cases": "non-empty"
}
},
{
"row-count": {
"output-destinations": "non-empty"
}
},
{
"row-count": {
"collection-events": "non-empty"
}
},
{
"row-count": {
"readmission-events": "non-empty"
}
},
{
"row-count": {
"redelivery-events": "non-empty"
}
}
]
},
"otherwise": "A finished output is delivered automatically as soon as its normal destination accepts it.",
"options": {
"automatic": {
"meaning": "Delivery happens as soon as a destination accepts the output. A trained unit leaves the barracks for its rally point at once.",
"semantics": "Move from finished to delivered immediately after the capacity policy accepts one destination."
},
"on-collection": {
"meaning": "The output waits at the destination that accepted it until it is collected. A cooked meal stays on the stove until a player picks it up.",
"semantics": "Keep the accessible output in finished until collection-declared-in occurs, then report delivery once."
}
}
},
"output-full": {
"asks": "What happens when the whole output does not fit at its normal destination?",
"options": {
"never-full": {
"meaning": "The normal destination always accepts the output. Completed research always fits in the research ledger.",
"semantics": "No job reaches a capacity-blocked state. A reviewer confirms the capacity promise before the adoption relies on it. An adoption with an in-place output always gives this answer. If the capacity promise is false, the adoption is invalid; there is no fallback lifecycle branch."
},
"wait-before-start": {
"meaning": "The job waits before starting until room is reserved. A bakery starts a loaf only after one shelf space is held for it.",
"semantics": "Keep admission pending until the whole output has reserved room. Hold that reservation through delivery."
},
"wait-until-space": {
"meaning": "The job stops at the finish gate with its completed work until the output fits. An assembler holds a finished engine while its output slot is full.",
"semantics": "The job moves to stopped at its finish gate, whether or not it has a work phase. It retains completed work until the destination has room."
},
"hold-undelivered": {
"meaning": "The job finishes and keeps the output. Nobody can use the output until a new delivery attempt succeeds. A forge holds one finished sword while the rack is full.",
"semantics": "Spend inputs and keep one owned output in finished. Only redelivery-declared-in retries the normal destination; work is not repeated."
},
"try-destinations": {
"meaning": "The job tries named places in order. A mined gem tries the backpack, then the cart, then waits if both refuse it.",
"semantics": "Try the normal destination, then output-destinations in written order. Keep the same output in finished if all refuse; only redelivery-declared-in retries the route."
}
}
}
},
"declares": {
"values": {},
"rows": {
"job": {
"description": "Give the job rule and the normal output destination. One adoption has exactly one row.",
"when-empty": "An empty job list is never a behavior choice: this adoption requires exactly one job row, and a reviewer rejects a file without it.",
"record": {
"job-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what this job needs and what it makes. The rules name the required inputs. They name one unit of successful output. They name the target, when the job changes a thing in place. They state any choices or quality rules that belong to your game."
},
"normal-destination-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what the normal destination of the output is."
},
"output-kind": {
"type": "string",
"required": true,
"options": [
"new-thing",
"in-place"
],
"description": "Whether success creates a new thing or transforms the cited target in place."
}
}
},
"read-first": {
"description": "Optionally point a reviewer to prose that must be read before trusting the adoption.",
"when-empty": "No advance reading is required.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this reviewer note in your game's words, such as firing-is-one-batch."
},
"read-first": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what a reviewer must know before trusting this adoption."
}
}
},
"work-phases": {
"description": "List the work span before finish. One adoption has at most one row.",
"when-empty": "Start and finish settle in one batch.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for the work span in your game's words, such as cooking or wall-building."
},
"duration-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say how long the work takes and which clock measures it, or which event completes the work."
}
}
},
"interruptions": {
"description": "List the game rules allowed to instruct a working job to stop.",
"when-empty": "A job stops only when it is unable to advance. No game rule can instruct it to stop.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this interruption in your game's words, such as fuel-runs-out or smith-called-away."
},
"interruptions-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which rules may instruct a working job to stop."
}
}
},
"reversal-rules": {
"description": "Add a row when the answer to partial-work is reverse-while-stopped. In every other case, leave this list empty. The row points to the rule that says how recorded work decreases.",
"when-empty": "Stopped work does not reverse.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this reversal rule in your game's words, such as forge-cooling."
},
"reversal-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say how recorded work decreases while the job is stopped."
}
}
},
"progress-rounding-rules": {
"description": "Add a row when the answer to input-claim-timing is with-progress. In every other case, leave this list empty. The row points to the rule that rounds the share of each input that the job claims as work advances.",
"when-empty": "Inputs are not claimed with progress.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this rounding rule in your game's words, such as material-by-work."
},
"progress-rounding-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say how to round the share of each divisible input that the job claims as work advances."
}
}
},
"cancel-return-rules": {
"description": "Add a row when the answer to cancel-return is return-by-rule. In every other case, leave this list empty. The row points to the rule that says which part of the claimed inputs returns.",
"when-empty": "Cancellation does not return a share that a game rule chooses.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this return rule in your game's words, such as unused-stock-returns."
},
"cancel-return-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which part of the current claim returns when the job is canceled."
}
}
},
"collection-events": {
"description": "Add a row when the answer to delivery-trigger is on-collection. In every other case, leave this list empty. The row points to the event that collects the finished output.",
"when-empty": "A finished output does not wait for collection.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this collection event in your game's words, such as player-collects-dish."
},
"collection-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which event accepts the finished output and delivers it."
}
}
},
"readmission-events": {
"description": "Add a row when the answer to output-full is wait-before-start. In every other case, leave this list empty. The row points to the rule or event that makes the game try admission again.",
"when-empty": "Output room is not reserved before start.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this readmission event in your game's words, such as shelf-space-freed."
},
"readmission-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which rule or event tries admission again. The game tries again only then. It does not check repeatedly by itself."
}
}
},
"redelivery-events": {
"description": "Add a row when a held output, or a route that can be refused, can try delivery again. The row points to the event that tries delivery again.",
"when-empty": "A held output or a route that can be refused does not retry delivery.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this redelivery event in your game's words, such as rack-space-freed or storage-changed."
},
"redelivery-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which rule or event tries delivery again. The game tries again only then. It does not check repeatedly by itself."
}
}
},
"work-limits": {
"description": "List each pool. A pool is a group of jobs that share one limit on how many may work at the same time. A pool can also limit how many jobs may wait. Each job that works uses one working slot of the pool, and each job that waits uses one waiting slot.",
"when-empty": "Jobs share no work limit and every otherwise-ready job starts.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this pool in your game's words, such as barracks-line or one-burner."
},
"covers-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say exactly which requests use this pool."
},
"scope": {
"type": "string",
"required": true,
"options": [
"each-place",
"shared"
],
"description": "Whether each cited place has its own working slots and waiting slots, or all covered jobs share one set of slots."
},
"places-declared-in": {
"type": "citation",
"when": {
"row": {
"scope": [
"each-place"
]
}
},
"description": "Where your game's rules say which places have their own slots. Fill this in when each place has its own slots. Leave it out when all covered jobs share one set of slots."
},
"place-assignment-declared-in": {
"type": "citation",
"when": {
"row": {
"scope": [
"each-place"
]
}
},
"description": "Where your game's rules say how each request is assigned to one place. Fill this in when each place has its own slots. Leave it out when all covered jobs share one set of slots."
},
"working-limit-key": {
"type": "citation",
"required": true,
"description": "Where your game's rules say how many jobs of the pool may work at the same time. The number is a positive whole number."
},
"waiting-limit-key": {
"type": "citation",
"required": false,
"description": "Where your game's rules say how many jobs of the pool may wait. The number is a whole number, zero or more. When the field is absent, the number of waiting jobs has no limit."
},
"waiting-order": {
"type": "string",
"required": true,
"options": [
"request-order",
"game-rule-order"
],
"description": "Whether a free working slot is offered to jobs in request order, or in the order that a cited rule gives. The cited rule must put all jobs of the pool in one complete order."
},
"order-declared-in": {
"type": "citation",
"when": {
"row": {
"waiting-order": [
"game-rule-order"
]
}
},
"description": "Where your game's rules say in which order the jobs of the pool get a free working slot. The rules must put all jobs in one complete order. Fill this in when the waiting order is game-rule-order. Leave it out when the waiting order is request-order."
},
"releases-working-at": {
"type": "string",
"required": false,
"options": [
"finished",
"delivered"
],
"description": "When a successful job frees its working slot: after it is finished, or after it is delivered. When the field is absent, the job frees the slot after it is finished."
}
}
},
"standing-orders": {
"description": "List each repeating order. A repeating order stays after it creates a job, and can create a job of the same kind again.",
"when-empty": "No request repeats automatically.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this repeating order in your game's words, such as repeat-selected."
},
"work-limit": {
"type": "string",
"required": true,
"description": "The work-limits row for this order. When that pool has a free working slot, the order may create a job."
},
"requests-job-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which kind of job this order requests."
},
"eligible-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say whether this order may create another request."
}
}
},
"failures": {
"description": "List failures that may happen after acceptance.",
"when-empty": "Accepted and finished jobs do not fail.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this failure in your game's words, such as overcooked or storm-damage."
},
"from-state": {
"type": "string",
"required": true,
"options": [
"waiting",
"working",
"stopped",
"finished"
],
"description": "The state from which this failure may happen."
},
"trigger-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what causes this failure: a chance, a condition, or an event. The rules also say what the failure may lose."
},
"result": {
"type": "string",
"required": false,
"options": [
"partial-inputs-or-work",
"job-ruined"
],
"description": "The result of this failure. When the field is absent, the default answer applies."
}
}
},
"cancellations": {
"description": "List controls or events that may cancel an unfinished job.",
"when-empty": "An accepted job cannot be canceled; a reviewer confirms this before the adoption relies on it.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for the cancellation in your game's words, such as cancel-unit or cancel-order."
},
"trigger-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which control or event cancels the unfinished job."
}
}
},
"dependent-cancellations": {
"description": "List static links through which a later waiting job needs an earlier job's output.",
"when-empty": "No later waiting job has a static dependency on an earlier job's output.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this dependency case in your game's words, such as sword-needs-ingot."
},
"cancellation": {
"type": "string",
"required": true,
"description": "The matching cancellation row."
},
"link-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which static link connects the requirement of the later job to the output of the earlier job."
}
}
},
"delivery-cases": {
"description": "List each capacity or delivery condition of your game that no collection, readmission, redelivery, or fallback row already states.",
"when-empty": "When every other delivery row set is also empty, delivery is automatic and the normal destination always accepts the output. A reviewer confirms both promises before the adoption relies on them.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this delivery situation in your game's words, such as full-output-slot."
},
"condition-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which capacity rule or collection rule makes this situation apply."
}
}
},
"output-destinations": {
"description": "List fallback destinations after the normal destination, in the order tried.",
"when-empty": "No fallback destination follows the normal destination.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this fallback destination in your game's words, such as mine-cart."
},
"destination-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what this destination is and how it accepts the whole output."
}
}
}
}
},
"rules": {
"progress-does-not-reset-on-stop": {
"forbid": {
"flag": {
"input-claim-timing": [
"with-progress"
],
"partial-work": [
"reset-at-stop"
]
}
},
"message": "A job that claims inputs with progress cannot also reset all its work at a stop, because the job stops when inputs run out. Keep or reverse the work, or claim inputs at another time."
},
"progress-reversal-warning": {
"forbid": {
"flag": {
"input-claim-timing": [
"with-progress"
],
"partial-work": [
"reverse-while-stopped"
]
}
},
"severity": "warning",
"message": "Reversing work while inputs are claimed with progress can make the same materials leave and return repeatedly."
},
"collection-needs-a-placed-output": {
"forbid": {
"any": [
{
"flag": {
"delivery-trigger": [
"on-collection"
],
"output-full": [
"hold-undelivered"
]
}
},
{
"flag": {
"delivery-trigger": [
"on-collection"
],
"output-full": [
"try-destinations"
]
}
}
]
},
"message": "A result that waits for collection must already be at a destination that accepted it. Choose an answer to output-full that puts the result at a destination first."
}
},
"contract": "job-resolution",
"version": 2,
"summary": "This contract covers crafting benches, build queues, research, and cooking. It decides how one job takes its inputs, does its work, stops, fails, and delivers its result. It also covers limits on how many jobs may work or wait at the same time. This contract does not cover what a recipe makes or what it is worth.",
"origin": "https://opengdd.org/contracts/job-resolution-2",
"mechanism": [
"This text decides the order of the steps for states, events, input claims, and reports. The questions and rows supply choices and cited game rules. They do not change the order.",
"A **job run** begins with one request and ends at refusal, cancellation, terminal failure, or delivery. Every report carries the same job id and run id. It also carries old and new lifecycle state, cause, recorded work before and after, input units newly claimed, released, spent, or lost, and the output destination when chosen. Amounts use units and numbers from the cited game rules. `job-requested` has no old state and is outside the ordered instant batch. `job-rejected` is the report of a refused request. It ends the run but is not a lifecycle state. `destination-refused` changes no state: old and new state are both `finished`.",
"A claim makes inputs unavailable to every other rule while keeping their source known. Releasing a claim returns the units that are still claimed to that source. Spending or losing units removes them; neither can later be released.",
"The forward path is `requested -> waiting -> started -> working -> finished -> delivered`. Side paths are `working -> stopped -> working`, `requested/waiting/working/stopped -> canceled`, and `waiting/working/stopped/finished -> failed`. A recoverable failure reports `job-failed` and moves the job to `stopped` instead.",
"Not every job visits every state. `waiting` and `stopped` can return to the forward path. `canceled`, terminal `failed`, and `delivered` are terminal. `started` is a transition boundary, not a waiting state. A pool limits how many jobs may work, and can limit how many may wait. A job that starts takes one **working slot** of its pool, and a job that waits takes one **waiting slot**. The working limit and the waiting limit give the number of slots. Entry to any terminal state immediately frees every slot that the job holds. `releases-working-at` affects only the success path and never delays the freeing of slots at a terminal state.",
"Every stopped job records all active stop causes and its resume point. It resumes only after every cause clears. Finish-gate causes are `output-full` and `inputs-unavailable-at-finish`; a finish-gate cause keeps the working slot. Every other cause does not keep it. Entry to `stopped` always frees a waiting slot. A stopped job keeps its working slot only while every active cause is a finish-gate cause; the first other cause frees it. `partial-work` applies once when the first ordinary stop cause joins the set, never for a finish-gate cause. `failure-recovery` is neither a finish-gate cause nor an ordinary stop cause: it does not keep the working slot, and `partial-work` never applies to it, because step 16 has already settled the loss.",
"Whenever a pool has a free working slot, it first offers that slot to admitted jobs that are eligible to start or resume, in the pool's waiting order. A job stopped by an instruction joins that order at the stop moment and is never placed before the job that displaced it. A cleared reason also causes a waiting or stopped job to be offered a free working slot again. After those offers, the pool scans standing orders in written order. It also scans when it is created, after a refusal of a request that the pool did not create in this scan, and when cited eligibility becomes true. A scan keeps creating eligible requests until no row is eligible or no working slot is free. A scan may create more than one job from the same row, until every free working slot is filled. Each scan reads the conditions as they are at that time. A row that is still eligible creates requests again in a later scan; it does not act only once, at the moment when it becomes eligible.",
"### Request and accept",
"1. Read `job-declared-in` once. It names required inputs, one successful output unit, any in-place target, and the game's own choice or quality rules. If work limits exist, assign the request to exactly one pool. For a pool with separate slots for each place, assign it to exactly one cited place. Report `job-requested`.",
"2. Check availability without reserving anything. For required inputs, test the full requirement under the three whole-claim timings. Under progress-based claiming, test only the initial rounded share. A game rule may require a stricter offer check. If the tested share is missing, report `job-rejected` with cause `missing-inputs`; claim nothing, record no work, and create no output. Jobs with no inputs skip the check.",
"3. Decide whether the request would enter `waiting`. This includes a pool with a free working slot whose order places an earlier admitted job first. A request that is admitted to `waiting` always takes one waiting slot. If the waiting limit is full, refuse the request: report `job-rejected` with cause `job-limit-full`. A request that starts immediately takes no waiting slot.",
"4. Attempt admission. When room for the output must be reserved before start, reserve room for the whole output atomically. If room is unavailable, the request stays `requested`; `readmission-declared-in` causes another attempt without polling, which means that the game does not check again by itself. Recheck input and waiting limits on delayed admission. On admission, accept the request. Claim the full requirement in the same batch when inputs are taken at request. An admitted request that cannot start enters `waiting`; report `job-waiting` and every current reason. No work is recorded in `waiting`, and no input is claimed there except the claim made by `taken-at-request`.",
"### Start",
"5. Recheck every admission and start gate. Claim the full requirement under start timing. Under progress-based timing, claim the initial share. When the claim fails, the job enters `waiting` and takes one waiting slot. If the waiting limit is full, refuse the request instead: report `job-rejected` with cause `job-limit-full`, and release any output reservation. Otherwise report `job-waiting` with cause `inputs-unavailable`. Inputs taken at request are already held; inputs taken at finish remain at their source.",
"6. Keep any output reservation through delivery; other output answers reserve nothing. Free the waiting slot, take a working slot, and report `job-started`.",
"7. Without a work-phase row, resolve start and finish in one batch. The reports stay in one ordered batch, unless `hold-undelivered`, `try-destinations` with every destination refusing, or `on-collection` keeps the job in the visible state `finished`, as the answer allows, or a finish-gate cause stops the job at the finish gate. With a work phase, enter `working` and report `job-working`.",
"### Work and input claims",
"8. Work completes as `duration-declared-in` says: its clock advances or its completion event occurs. Record exposed work points at full precision. A display meter is outside this contract.",
"9. With progress-based timing, every observable advance first claims the rounded share for the proposed work point. If the share is unavailable, do not record the advance; stop with cause `inputs-unavailable`. Whole-claim timings keep their full claim. Finish timing keeps inputs available elsewhere.",
"10. Reaching the required work moves the job to the finish gate. It does not yet spend inputs or create the successful output.",
"### Stop, resume, and interrupt",
"11. An interruption is a temporary inability to advance, or an instruction to stop, that is not refusal, failure, cancellation, or delivery. It moves `working -> stopped`, adds its cause, applies `partial-work` and the claim policy, then reports `job-stopped`. An instructed stop must be named by `interruptions-declared-in`. A later cause joins the set and reports `stopped -> stopped`; it does not apply the work answer again.",
"12. First adjust a progress-based claim to any reduced work. Then apply `stopped-claims`: hold the claim, or release it to its source. Whole claims taken at request or start follow the same answer. Finish timing has no claim. When the question is not asked, the claim is held.",
"13. When every cause clears, the job competes for a free working slot through its pool, or proceeds without a pool. Recheck every gate and reclaim any released share. If a gate or claim is unavailable, remain stopped and leave the working slot free. Otherwise report `job-resumed` and return to the recorded resume point: after a failure from `waiting`, the job continues at step 5 and takes its working slot at step 6, while interrupted work takes a working slot again and enters `working`. The same job resumes; no new request is created.",
"14. The finish gate tests capacity and any finish-time claim together. The finish-gate wait answer adds `output-full`; an unavailable claim adds `inputs-unavailable-at-finish`. These causes preserve completed work and keep the working slot unless another active cause frees it. They never apply `partial-work`. `stopped-claims` applies to finish-gate stops exactly as in step 12. When every cause clears and any released claim is reclaimed, report `job-resumed` and return directly to the finish gate.",
"### Fail",
"15. A failure row fires when its failure happens. It may fire only from its named state. A failure from `finished` is always terminal; no recoverable result exists there. Resolve its row result, or the default answer to `failure-result` when the row has none, atomically. For a recoverable result, apply the stopped-claim policy before the one `job-failed` report.",
"16. A recoverable result may lose a strict subset of claimed inputs, reduce work without passing below none, or do both. It creates no successful output. Under progress-based timing, apply losses first. If the claim is held, reduce work to the highest point that the remaining claim supports and release the excess claim. If the claim was released, reduce work only by the named loss. The same job moves to `stopped` with cause `failure-recovery`; its recorded resume point is the start gate for a failure from `waiting`, the prior work point for a failure from `working`, or the already-recorded point for a failure from `stopped`. Later recovery follows the normal resume rule.",
"17. A ruined job enters terminal `failed`, can never finish as the same job, and creates no successful output. Failure from `finished` destroys its one undelivered new output. For in-place work, the failure ruins the transformation, but the target still exists. The cited failure rule defines the damaged state. Failure itself returns no claimed input; remaining claims stay assigned to the failed job unless the cited failure rule states another result for them. After the claim changes that the cited rule makes, terminal entry frees every slot and reports `job-failed`.",
"### Cancel",
"18. A declared cancellation may target `requested`, `waiting`, `working`, or `stopped`. Take a snapshot of the current claims, free every slot, apply the return answer when asked, enter `canceled`, and report `job-canceled`. Under the reset answer, terminal work becomes none after the snapshot. Cancellation creates no output and never undoes a finished or delivered output.",
"19. Returning claimed inputs releases every unit that is still claimed. Returning nothing spends them. A return chosen by a rule releases exactly the subset that the rule names and spends the rest. No answer recreates a unit already lost, and unclaimed inputs remain where they are.",
"20. A dependency is the declared static link from a later waiting job's requirement to the canceled job's output. Ending dependents finds every transitive later waiting job whose requirement can no longer be met. After the first cancellation settles, cancel those jobs in waiting order, each with its own claim settlement and report. Leaving dependents keeps them waiting. Completed intermediate jobs and delivered outputs remain.",
"### Finish and deliver",
"21. At the finish gate, finish-time timing tries to claim the full input. If the input is unavailable, follow the finish-gate stop rule. Other timings must hold the required claim. Atomically spend inputs and establish the one successful output: a new thing or a cited target transformed in place. An in-place output is delivered by that state change at its site. A compound output is still one atomic delivery unit. Report `job-finished`.",
"22. Resolve capacity by the selected answer. An always-accepting destination takes the whole output. A pre-start reservation is already held. A finish-gate wait follows the stop rule. A held output remains one owned, inaccessible output until its cited retry. An ordered route tries the normal destination and then fallback rows. Every refusal by any destination, including a held output's first refusal and each refusal on a cited retry, reports `destination-refused`. If all refuse, keep the same output until the cited retry. Delivery never polls. An in-place output has no destination capacity: it is delivered by the state change of step 21, and the adoption answers `never-full`.",
"23. Automatic delivery moves an accepted result `finished -> delivered` immediately. Collection keeps it accessible at its chosen destination until the cited event, then moves it to `delivered`. Report `job-delivered` exactly once.",
"24. On success, free the working slot after the transition named by the pool row and after that transition's report. Then offer the free working slot and scan the standing orders, as described above. A refusal ends the run of the refused request; a later retry is a new run that the requester starts, unless a standing order creates it.",
"### Ordering guarantee",
"One transition, all claim changes, and all reports settle before another transition begins. Claim changes happen before the transition report unless a step says otherwise. Automatic delivery to an accepting destination after an instant start keeps `job-started`, `job-finished`, and `job-delivered` in one ordered batch with no outside event between them. Events offered at the same moment enter this lifecycle one at a time in the order supplied by **Event resolution**. This contract creates no same-moment ordering rule."
],
"pack": "sha256:485313c991e5954a45c583ea1fd9d3f7f0e249252c88a0da3ae689483cd32403",
"answers": {
"job-inputs": "no-inputs",
"delivery-trigger": "automatic",
"output-full": "try-destinations"
},
"values": {},
"rows": {
"job": [
{
"job-declared-in": "salvage.recovered-gems",
"normal-destination-declared-in": "salvage.player-backpack",
"output-kind": "new-thing"
}
],
"redelivery-events": [
{
"id": "storage-changed",
"redelivery-declared-in": "salvage.storage-capacity-changed"
}
],
"output-destinations": [
{
"id": "mine-cart",
"destination-declared-in": "salvage.mine-cart-capacity"
}
],
"read-first": [],
"work-phases": [],
"interruptions": [],
"reversal-rules": [],
"progress-rounding-rules": [],
"cancel-return-rules": [],
"collection-events": [],
"readmission-events": [],
"work-limits": [],
"standing-orders": [],
"failures": [],
"cancellations": [],
"dependent-cancellations": [],
"delivery-cases": []
}
}