Unknown outcome deserves its own state
Z29C stopped translating timeouts into failure when an external effect might already exist, preserving uncertainty until receipts, queries, or review could resolve it.
Z29C's synthetic delivery timed out. The first implementation marked it failed. The second implementation marked it successful because the destination usually accepted before delaying its response.
Both versions made the interface decisive and the system dishonest.
The only established fact was that Z29C had sent a request and had not received a usable answer before its deadline. The delivery might exist. It might not. Treating uncertainty as failure risked duplication. Treating it as success allowed dependant work to proceed without a receipt.
I added outcome-unknown to the step state machine.
Unknown was not a temporary error label. It changed scheduling, user language, recovery actions, metrics, and the evidence required for completion.
A timeout described the observer
Timeouts bound how long Z29C waits for one operation. They do not reach into another system and reverse work when the timer expires.
The destination could accept a request at 12:00:04. Z29C's client deadline could expire at 12:00:05. The response could arrive at 12:00:06 or never arrive. The timeout established that Z29C stopped waiting under that attempt's policy.
It did not prove:
- The request failed to leave the worker.
- The destination did not parse it.
- The destination rejected it.
- A transaction did not commit.
- The result will not appear later.
Some transport failures provided stronger evidence, such as a local validation error before a request was sent. Others remained ambiguous. Adapter code classified the observation conservatively based on its actual boundary.
The state name followed what Z29C knew, not what a generic exception hierarchy suggested.
Unknown sat between invocation and receipt
The step model separated attempt activity from accepted outcome:
ready
→ running
→ succeeded
→ failed-terminal
→ failed-retryable
→ outcome-unknown
→ waiting-for-reviewoutcome-unknown required evidence that an external request may have crossed its side-effect boundary and no sufficient receipt existed. It retained:
- Stable workflow and step identity.
- Attempt and external request identity.
- Input digest and adapter contract version.
- Last confirmed checkpoint.
- Transport observation.
- Earliest useful query time.
- Idempotency-window expiry where relevant.
- Workflow deadline and remaining attempt budget.
The state could transition to succeeded after a query found the effect, retryable after authoritative absence, terminal failure after a durable rejection, or review when automation could not decide.
No automatic scheduler path treated it as ordinary ready work.
Dependants named what evidence they required
The first workflow engine unblocked dependant steps whenever a predecessor was not running or failed. Unknown did not fit the boolean.
Each dependency required a receipt type or field. The completion notification needed a verified delivery reference. A cleanup step might run even if delivery remained unknown, provided it did not destroy evidence. A compensation decision needed to know whether there was an effect to counteract.
Z29C evaluated edges:
generate document receipt → enables delivery
delivery verified receipt → enables final notification
delivery unknown event → enables status-query recovery
workflow deadline event → enables review, not automatic compensationThis prevented the graph from treating “not failed” as success. Unknown could enable recovery work while blocking business progression.
Some workflows allowed a bounded parallel branch to continue independently. The definition had to state that relationship; the engine did not freeze unrelated steps globally.
Evidence, not status color, controlled the next transition.
A queryable adapter appeared to solve unknown outcome. Z29C looked up the stable client reference.
The destination's status index could lag behind acceptance. An immediate not_found might mean no effect or not visible yet. The adapter contract needed a consistency window and authoritative absence semantics.
Z29C modeled query results:
found: result and digest match the request.processing: destination acknowledges request but has no terminal result.not-visible-yet: absence inside the declared visibility window.not-found-authoritative: destination guarantees no effect under the reference.conflict: reference exists with different request identity or input.unavailable: query itself did not produce evidence.
Only authoritative absence could support safe creation under an adapter without idempotent retry. A series of unavailable queries did not slowly become proof of absence.
The distinction made the recovery slower in tests and prevented one fast status endpoint from manufacturing certainty.
Unknown had a deadline, not a blind retry count
The system could not query forever. Every workflow had a useful outcome window, and every adapter had an evidence policy.
An unknown step carried:
- Next eligible query time.
- Backoff and jitter policy.
- Maximum automated query attempts.
- Idempotency or query visibility window.
- Workflow deadline.
- Escalation or review condition.
Rate-limit responses changed the next eligible time. They did not consume a worker lease while waiting. Approaching key expiry increased urgency because the safest recovery option could disappear.
When automated evidence remained insufficient at the deadline, Z29C entered review or a declared terminal state. It did not perform one final desperate create call unless the workflow explicitly authorized duplicate risk.
Attempt budgets controlled resource use. They did not vote on truth. Ten unanswered queries remained unknown.
Time determined when a person needed to decide under uncertainty, not what had happened externally.
The early failed-job view led with an exception string and Retry. Unknown needed a different interface.
The recovery console showed:
- Intended effect: the normalized request and one-effect invariant.
- Known boundary: request identity was persisted and invocation began.
- Missing evidence: no accepted delivery receipt or authoritative absence exists.
- Latest observations: timeout, status queries, adapter availability, and any late worker response.
- Safe actions: query, wait, attach evidence, authorize risky retry, or end with a waiver.
The primary action followed adapter capability. Query came before Retry. If no automatic evidence path existed, the page said so.
Technical detail included bounded redacted request data and external reference. The user-facing workflow page said Delivery status needs review and explained whether any future work was blocked.
The interface did not need a yellow status for aesthetic nuance. It needed to prevent the wrong action.
Manual evidence created a typed resolution
A person could sometimes inspect the synthetic destination and find the delivery reference. The first console had a Mark successful button.
I replaced it with a resolution form requiring:
- Resolution kind: effect verified, absence verified, accepted uncertainty, or duplicate-risk retry authorized.
- External reference and result summary where available.
- Evidence source and observation time.
- Input digest comparison.
- Author and reason.
- Residual uncertainty or required follow-up.
The action committed a typed receipt or waiver. It did not mutate the original timeout observation.
If a later external event contradicted the manual conclusion, Z29C could reopen review with both records. History showed what evidence had been available at the time.
Manual work did not become trustworthy by coloring a step green. It became trustworthy through the same identity and receipt model as automated recovery.
Compensation waited for knowledge when possible
The first saga-like definition scheduled compensation whenever a later step failed. If delivery outcome was unknown, it requested withdrawal immediately.
That could create a second unknown side effect for a delivery that never existed. It could also race with the original request still processing.
Z29C treated unknown outcome as a reason to establish effect presence before compensating where the adapter allowed. If the workflow deadline required containment, the definition could authorize a bounded counteraction whose semantics tolerated absence—for example, “ensure reference is withdrawn if present.” That adapter operation needed idempotent behavior and its own receipt.
Otherwise, compensation entered review with the original uncertainty visible.
The engine never assumed rollback could clean an uncertain past. Counteractions were new external effects and could become unknown themselves.
This prevented the desire for a tidy final state from multiplying ambiguous work.
A worker could lose its lease, enter unknown, and later receive the adapter's success response.
The stale worker could not commit step state under its expired fencing token. It could append a late observation tied to its attempt and request identity. The recovery process validated the external reference and input digest, then produced a current receipt if appropriate.
If a status query had already found the same result, the evidence agreed. If it found a different reference, Z29C raised an integrity conflict instead of choosing the newest message.
This path separated authority from knowledge. The old worker had no authority to advance the step and could still know something valuable about its own request.
Discarding every stale response would lengthen uncertainty. Accepting it directly would defeat lease fencing. Typed late evidence preserved both guarantees.
Unknown could shrink as observations arrived from paths no longer in control.
Unknown and stale were different
An outcome could be known at revision 8 while the interface displayed revision 7. That was stale state, not unknown outcome. A step could also be current and explicitly unknown.
Z29C kept projection freshness separate from domain knowledge:
- Projection current, outcome unknown: the latest durable state says evidence is insufficient.
- Projection stale, prior outcome known: the client has not synchronized recent workflow events.
- Projection stale, prior outcome unknown: the client cannot know whether recovery progressed elsewhere.
The browser showed its last confirmed workflow revision and refreshed before offering consequential actions. A stale Retry button could not remain enabled after another reviewer resolved the step.
This reused D4U7's distinction between connection and current state. “Unknown” described the workflow's evidence. “Stale” described the viewer's relationship to that evidence.
Conflating them would make a refresh appear to solve external uncertainty or make external uncertainty look like a network bug.
Unknown needed retention longer than ordinary logs
Deleting attempt logs or request identities while a step remained unknown would make recovery permanently harder.
Z29C retained the minimum evidence set through resolution and a documented review period: stable identities, digests, adapter version, invocation checkpoint, observations, queries, and resolution. Raw transport logs followed shorter retention and excluded secrets.
If an external status window expired, the record preserved that fact. A later manual discovery could still link its reference. If the workflow itself was removed under a lifecycle policy, unresolved unknown states required explicit waiver or export; they did not vanish through generic cleanup.
This was not a reason to retain full payloads forever. The normalized command followed workflow retention, and receipts stored bounded summaries or references. Sensitive input remained under its own policy.
Unknown outcome created a purpose for specific evidence. That purpose ended after resolution and required its own lifecycle.
A success/failure chart could omit unknown or count it as failure. Both lost useful information.
Z29C measured:
- Entries into unknown by adapter and operation class.
- Age distribution until resolution.
- Resolution path: direct late response, idempotent retry, status query, manual receipt, waiver, or duplicate-risk attempt.
- Unknown states approaching evidence-window or workflow deadlines.
- Reopened resolutions after contradictory evidence.
- Dependants and user outcomes blocked by unknown.
In the personal synthetic environment, scenario assertions mattered more than volume. I did not invent an operational service-level claim.
The metric helped compare adapter contracts. Two destinations could have equal nominal error rates and radically different unknown-outcome recovery.
Unknown was debt because it consumed attention and blocked safe progress. Counting it separately made adapter observability a product capability.
The state had strict entry criteria
If every confusing exception became unknown, the state would become a dumping ground.
Z29C entered outcome-unknown only when:
- The step had a side-effecting boundary.
- Durable evidence showed invocation may have crossed it.
- No sufficient success, rejection, or authoritative absence receipt existed.
- Repetition could change external consequence or violate policy.
Pure internal calculation failures remained retryable or terminal. Validation errors were rejections. Authentication blocked before invocation was an authority state. Rate limits produced scheduled waiting. Projection gaps produced stale client state.
The error taxonomy mapped concrete observations into these states. Generic exception text never decided alone.
Strict entry made the console and scheduler behavior predictable. It also made adapter authors state where their side-effect boundary began.
Unknown had precision because other ambiguities had their own names.
An opaque destination might retain no searchable reference, and its external evidence could expire. After that point, Z29C could no longer expect new automated knowledge.
The step did not stay in an active querying loop forever. It entered review as unresolvable-with-current-evidence. A person could accept one of the bounded outcomes:
- Stop future workflow steps and retain the unresolved history.
- Authorize another attempt with duplicate risk.
- Treat the effect as absent for a low-consequence synthetic scenario, with a waiver.
- Treat it as present for containment, with a compensating action that tolerates absence.
The final projection preserved the waiver. It did not relabel the original outcome known.
This was uncomfortable and realistic. Systems sometimes reach a point where additional evidence no longer exists. A state machine should represent the decision made under that limit instead of manufacturing historical certainty.
Unknowable was an end to investigation, not a rewrite of the event.
The tests asserted absence of action
Many workflow tests asserted what Z29C should do. Unknown scenarios also asserted what it must not do:
- Do not retry an opaque creation automatically.
- Do not release dependant steps without their required receipt.
- Do not compensate merely because the original call timed out.
- Do not let lease expiry convert unknown to ready.
- Do not let a stale worker commit current state.
- Do not send repeated user failure notifications while status queries run.
- Do not delete request identity before resolution.
The deterministic adapter harness accepted and lost responses, delayed query visibility, returned conflicting references, and expired evidence windows. A controllable clock advanced deadlines without real waiting.
The test suite treated restraint as behavior. The absence of a duplicate side effect was one of Z29C's most important outputs.
Z29C's API could not translate unknown outcome into a generic 500 response and expect clients to infer the workflow state. A command submission returned the durable workflow identity once intent was recorded. Later reads exposed the current projection and revision, including unknown outcome as a named state.
An API consumer could subscribe or poll with a cursor, then present its own recovery interface without creating another workflow. Consequential recovery commands required the current workflow revision so a stale client could not authorize Retry after another device attached a receipt.
The representation included a stable problem code, safe next-action links, and a human explanation. It did not expose raw adapter exception strings or credentials. A client could distinguish temporary status-query waiting from review required and terminal resolution.
Webhook-style completion delivery followed the same model. Z29C emitted one transition when the workflow entered unknown and another when evidence resolved it. Repeated status-query attempts did not create a storm of external failure events. Consumers deduplicated by event ID and tracked workflow revision.
This mattered because user experience does not always live in Z29C's own page. The uncertainty contract had to survive API boundaries. A downstream client that collapsed unknown into failed could reintroduce the dangerous Retry button even while the engine behaved correctly.
Named state became part of the product protocol, not an internal database detail.
Unknown made the system more decisive where it could be
Adding a third outcome did not make Z29C vague. It made success and failure stronger.
Succeeded meant a receipt satisfied the step's evidence contract. Rejected or terminal failure meant durable evidence showed the intended outcome could not or should not proceed. Unknown meant neither claim was yet supported.
The recovery path then gathered evidence until one conclusion became justified or a person accepted a bounded limit.
The interface stopped offering false certainty and became more actionable. It could say what was missing, which safe operation could find it, and what downstream work remained blocked.
A timeout is a statement about how long one observer waited. A workflow outcome is a statement about what the system did. Z29C became reliable when it stopped pretending those statements were interchangeable.