@xema/omni-protocol 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +17 -2
  2. package/guide.md +51 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -59,9 +59,11 @@ assignments, break denial and retry, wrap timeout, browser isolation.
59
59
  > conforming one. A suite that only asserts "this conforming case does not throw" passes unchanged
60
60
  > if the helper is gutted, so pair every positive case with the violating twin.
61
61
 
62
- ## Two things TypeScript will not catch for you
62
+ ## Three things TypeScript will not catch for you
63
63
 
64
- Both found by adapters against this contract, and both produce a green build over a wrong shape.
64
+ All found by adapters against this contract, and all produce a green build over a wrong shape.
65
+ Two share one cause: TypeScript checks extra keys only on a literal that is the thing directly
66
+ assigned. Move the literal anywhere else and the check is gone.
65
67
 
66
68
  **Conditional spreads are the blind spot on a task literal.** A key inside
67
69
  `...(cond ? { … } : {})` is never checked against the task type, and `satisfies Task<C>` on the
@@ -88,6 +90,19 @@ switch (command.action) {
88
90
  }
89
91
  ```
90
92
 
93
+ **A `const` fixture escapes excess-property checking.** Park a literal in a variable and it is no
94
+ longer the thing directly assigned — the same reason the conditional spread escapes — so a shared
95
+ test fixture keeps a field the contract has dropped: `tsc` says nothing and the suite is
96
+ confidently green over a shape that no longer exists. Annotate the `const` or `satisfies` it where
97
+ it is declared; either names the field on the next build.
98
+
99
+ ```ts
100
+ take({ reasonId, requestedAt }); // error: requestedAt
101
+ const request = { reasonId, requestedAt }; take(request); // no error — the hole
102
+ const request: BreakRequest = { reasonId, requestedAt }; // error: requestedAt
103
+ const request = { reasonId, requestedAt } satisfies BreakRequest; // error: requestedAt
104
+ ```
105
+
91
106
  ## Building
92
107
 
93
108
  ```
package/guide.md CHANGED
@@ -914,10 +914,17 @@ is applied.
914
914
 
915
915
  ### 6. An unsettled result is unknown, and unknown is not retried
916
916
 
917
- A settled result is a fact. A promise that rejects with no result the transport died — means
918
- *unknown*: the provider may have done it or not, and neither Omni nor an adapter on the agent's PC
919
- can find out in time to make a repeat safe. Omni does not retry. The next snapshot shows what the
920
- provider actually did, and if the agent acts again it is a new command.
917
+ A settled result is a fact. A promise that rejects with no result means *unknown*: the provider
918
+ may have done it or not, and neither Omni nor an adapter on the agent's PC can find out in time to
919
+ make a repeat safe. Omni does not retry; if the agent acts again it is a new command.
920
+
921
+ On a persistent ordered transport there is only one way a command goes unsettled: the connection
922
+ went away underneath it. **An adapter that cannot settle a command has lost its transport, and
923
+ says so** — `provider-status` `connecting`, reconnect, snapshot — whichever channel the command
924
+ actually travelled on. An unsettled promise is therefore always followed by a snapshot, and that
925
+ snapshot is the answer; Omni waits for it rather than calling `snapshot()` itself. While the
926
+ transport is up, a result says the provider accepted the command, and the event that follows —
927
+ `task-updated`, `break-state` — says what it did.
921
928
 
922
929
  A command therefore carries no key. The provider names its own records — a task, a lead request, a
923
930
  member — and Omni refers to them by those names; **Omni never asks a provider to remember a name
@@ -934,6 +941,11 @@ and the agent is on it; had nothing been placed, nothing arrives and the agent d
934
941
  Omni must not do is dial again on the agent's behalf — the one outcome worse than a lost answer is
935
942
  two phones ringing at the customer.
936
943
 
944
+ An agent presses Hold while the provider is reconnecting. Nothing is queued at either end: the
945
+ adapter answers `failed` with `omni.unavailable`, Omni shows the refusal, and the agent presses
946
+ again once the provider is `active` — against the state as it is then, rather than a held press
947
+ fired into a state that has moved on.
948
+
937
949
  ### 7. Work is pulled, never pushed
938
950
 
939
951
  Allocate only within the concurrent capacity Omni has stated for this agent.
@@ -2207,8 +2219,8 @@ answer and ask again when they want to. `decisionReason` may carry the words att
2207
2219
  decision, but `approval` does not remain denied.
2208
2220
 
2209
2221
  A provider reports `starting-after-task` only after Omni commits a `granted` request while
2210
- work is still active. Omni does not retry the original request, because asking again would not move
2211
- it; it retries the commit when its delivery is uncertain.
2222
+ work is still active. Omni does not send the request again, because asking again would not move
2223
+ it; it sends the commit again only from a reconnect snapshot that shows the grant still standing.
2212
2224
 
2213
2225
  `accepting: false` is what lets Omni withdraw the control rather than let an agent ask and be
2214
2226
  refused. A `BreakReason` marked `alwaysAvailable` survives it: a mandatory rest period is not
@@ -2425,10 +2437,13 @@ Omni coordinates one attempt as follows:
2425
2437
  causes Omni to take the cancel path.
2426
2438
  4. If every participant reports `granted`, durably choose commit, enter `committing-break`,
2427
2439
  and send `commitBreak()` to every participant. A provider then stops offering new work
2428
- and reports `starting-after-task` or `in-effect`. Omni enters `on-break` once every participant
2429
- it can still reach reports `in-effect`, and no later than the **commit bound** ten seconds
2430
- from the decision, tunable per deployment. A participant that has not applied the commit by then
2431
- is set aside as unreconciled; the break begins without it.
2440
+ and reports `starting-after-task` or `in-effect`. The **commit bound** ten seconds from the
2441
+ decision, tunable per deployment decides who is kept: a participant that has not applied the
2442
+ commit by then, still `granted` or unreachable, is set aside as unreconciled and the break
2443
+ begins without it. `in-effect` decides `on-break`: Omni enters it once every kept participant
2444
+ reports `in-effect`. A kept participant reporting `starting-after-task` has applied the commit
2445
+ and is finishing a task; the bound is on delivery, not on that task. Omni shows the break as
2446
+ settled and beginning when the task ends, and offers no cancel, because the commit is durable.
2432
2447
  5. If any participant fails or denies the request, cannot be reconciled within the bounded
2433
2448
  decision timeout, or the agent cancels before commit, durably choose cancel and enter
2434
2449
  `cancelling-break`. Send `cancelBreak()` to every participant still reporting
@@ -2467,7 +2482,13 @@ snapshot before anything else, and the snapshot decides:
2467
2482
  against an agent who is already on break elsewhere. **A new login is this case too**: the grant
2468
2483
  belonged to the old session.
2469
2484
 
2470
- The provider must not offer work in the meantime, and the commit is what stops it.
2485
+ The provider must not offer work in the meantime, and the commit is what stops it; Omni gives it
2486
+ no capacity until it is reconciled.
2487
+
2488
+ **If the agent has already ended the break elsewhere, the attempt is over** and the returning
2489
+ provider is reconciled to that instead: still `granted` gets `cancelBreak()`, because committing
2490
+ would stop an agent who is working again; `starting-after-task` or `in-effect` gets `endBreak()`.
2491
+ Never rolling back is about a break that is still on, not one the agent has finished.
2471
2492
 
2472
2493
  Omni may tell the agent which platforms the break has not yet reached, as it already does when a
2473
2494
  break cannot be paired across every provider.
@@ -2833,10 +2854,18 @@ Applies a `TaskCommandRequest` to one provider-local task.
2833
2854
  - `applied` confirms the side effect completed. `failed` confirms it did **not**, with a typed
2834
2855
  `ProtocolFailure`; a provider that will not and one that cannot report the same shape, and `code`
2835
2856
  says which.
2857
+ - A command sent while `provider-status` is not `active` answers `failed` with `omni.unavailable`.
2858
+ Neither Omni nor the adapter queues it.
2859
+ - **A command that asks for a state answers `applied` when that state holds, whoever brought it
2860
+ about; a command that acts answers `failed` when it cannot act.** Declining a lead request
2861
+ already gone is `applied`; joining one already gone is `failed`, since nobody joined. A lead
2862
+ deciding a member's break that another lead has already decided is `applied` when the decisions
2863
+ agree and `failed`, saying so in `message`, when they differ. `commitBreak()` on a break already
2864
+ in effect is `committed` for the same reason.
2836
2865
  - **A settled result is a fact; an unsettled promise is not.** Transport uncertainty may reject the
2837
- promise with no result at all, and that means *unknown*, not *failed*. `failed` must never be
2838
- returned for something the provider is unsure of, because Omni will show the agent it did not
2839
- happen.
2866
+ promise with no result at all, and that means *unknown*, not *failed*, and a snapshot follows —
2867
+ see **An unsettled result is unknown**. `failed` must never be returned for something the
2868
+ provider is unsure of, because Omni will show the agent it did not happen.
2840
2869
 
2841
2870
  ### `ProtocolFailure`
2842
2871
 
@@ -2858,7 +2887,7 @@ react rather than only display the message:
2858
2887
  | `omni.task-not-found` | The provider-local task id is unknown, typically after the task already ended. |
2859
2888
  | `omni.destination-not-permitted` | The dial or transfer destination violates the provider's policy. |
2860
2889
  | `omni.rate-limited` | The action was throttled. Pair with `retryAfterMs`. |
2861
- | `omni.unavailable` | The provider is temporarily unable to serve the action. |
2890
+ | `omni.unavailable` | The provider is temporarily unable to serve the action, including any command sent while `provider-status` is not `active`. |
2862
2891
  | `omni.break-already-committed` | Cancellation lost the commit/cancel race; Omni must finish commit recovery. |
2863
2892
 
2864
2893
  They are published as `OMNI_FAILURE_CODES`.
@@ -2915,9 +2944,10 @@ healthy provider from a dead one.
2915
2944
 
2916
2945
  #### Requesting a resync
2917
2946
 
2918
- Omni may call `snapshot()` at any time, not only at connect, and must do so on any loss of
2919
- confidence in its provider state. `reason: "provider-requested"` covers the
2920
- opposite direction — the provider asking Omni to reconcile and neither replaces the other.
2947
+ Omni calls `snapshot()` at connect; after that, snapshots come to it on reconnect, and when the
2948
+ provider asks. It may still call `snapshot()` at any time, but never to learn a command's fate:
2949
+ the reconnect snapshot already carries it. `reason: "provider-requested"` covers the opposite
2950
+ direction — the provider asking Omni to reconcile — and neither replaces the other.
2921
2951
 
2922
2952
  ### `snapshot`
2923
2953
 
@@ -2959,9 +2989,9 @@ reasons, retry details, and any imposed break.
2959
2989
  For a multi-provider attempt, "every provider" is the participant set frozen when the attempt
2960
2990
  entered `requesting-break`. Omni commits only after every participant reports `granted` —
2961
2991
  that one is unconditional, because nothing has stopped yet and waiting costs only time. It enters
2962
- `on-break` once every participant it can still reach reports `in-effect`, and no later than the
2963
- commit bound: past that a participant is set aside as unreconciled rather than holding a break that
2964
- has already begun elsewhere. Otherwise it follows the two-phase rules under **Coordinating a
2992
+ `on-break` once every kept participant reports `in-effect`; the commit bound decides who is kept,
2993
+ setting aside a participant that has not applied the commit rather than holding a break that has
2994
+ already begun elsewhere. Otherwise it follows the two-phase rules under **Coordinating a
2965
2995
  multi-provider break**.
2966
2996
 
2967
2997
  ### `task-offered`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",