@openwop/spec-artifacts 2.42.6 → 2.42.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 (69) hide show
  1. package/CORPUS-STAMP.json +70 -67
  2. package/api/seams-v2.yaml +238 -322
  3. package/api/v2/asyncapi.yaml +83 -113
  4. package/api/v2/openapi.yaml +408 -724
  5. package/package.json +1 -1
  6. package/schemas/audit-verify-result.schema.json +2 -2
  7. package/schemas/v2/audit-verify-result.schema.json +2 -2
  8. package/schemas/v2/capabilities.schema.json +4 -7
  9. package/schemas/v2/error-envelope.schema.json +144 -69
  10. package/spec/v1/alias-detectors.json +6 -0
  11. package/spec/v1/core-standard-manifest.json +2 -2
  12. package/spec/v1/deprecations.json +22 -0
  13. package/spec/v1/gaps.json +188 -1
  14. package/spec/v1/migrations.json +18 -0
  15. package/spec/v2/README.md +35 -37
  16. package/spec/v2/core/capabilities.md +186 -119
  17. package/spec/v2/core/conformance.md +78 -27
  18. package/spec/v2/core/connection-packs.md +8 -3
  19. package/spec/v2/core/conversation.md +10 -4
  20. package/spec/v2/core/errors.md +30 -9
  21. package/spec/v2/core/events.md +116 -80
  22. package/spec/v2/core/form-content-packs.md +11 -13
  23. package/spec/v2/core/headers.md +23 -14
  24. package/spec/v2/core/host-services.md +22 -5
  25. package/spec/v2/core/i18n.md +64 -0
  26. package/spec/v2/core/idempotency.md +48 -36
  27. package/spec/v2/core/identity.md +110 -42
  28. package/spec/v2/core/interop.md +82 -21
  29. package/spec/v2/core/interrupt.md +79 -40
  30. package/spec/v2/core/node-pack-runtimes.md +18 -4
  31. package/spec/v2/core/oauth.md +40 -6
  32. package/spec/v2/core/overview.md +61 -33
  33. package/spec/v2/core/packs.md +53 -28
  34. package/spec/v2/core/persistence.md +61 -80
  35. package/spec/v2/core/portability.md +44 -0
  36. package/spec/v2/core/replay.md +56 -42
  37. package/spec/v2/core/runs.md +198 -60
  38. package/spec/v2/core/security-defaults.md +57 -33
  39. package/spec/v2/core/tool-catalog.md +29 -6
  40. package/spec/v2/core/versioning.md +115 -50
  41. package/spec/v2/core/webhooks.md +61 -20
  42. package/spec/v2/core/workflow-chain-packs.md +16 -14
  43. package/spec/v2/corrections.json +17 -1
  44. package/spec/v2/corrections.schema.json +1 -1
  45. package/spec/v2/declaration.json +26 -9
  46. package/spec/v2/declaration.schema.json +10 -0
  47. package/spec/v2/errors.json +121 -4
  48. package/spec/v2/errors.schema.json +38 -0
  49. package/spec/v2/ext/README.md +36 -23
  50. package/spec/v2/ext/a2uiSurface/README.md +75 -89
  51. package/spec/v2/ext/brand/README.md +15 -15
  52. package/spec/v2/ext/canvas/README.md +15 -15
  53. package/spec/v2/ext/chat/README.md +15 -15
  54. package/spec/v2/ext/coordination/README.md +15 -15
  55. package/spec/v2/ext/dataIntegration/README.md +17 -20
  56. package/spec/v2/ext/entities/README.md +15 -15
  57. package/spec/v2/ext/grpc-transport/README.md +6 -8
  58. package/spec/v2/ext/kanban/README.md +15 -15
  59. package/spec/v2/ext/knowledge/README.md +15 -15
  60. package/spec/v2/ext/launchStudio/README.md +16 -16
  61. package/spec/v2/ext/messaging/README.md +15 -15
  62. package/spec/v2/ext/portability/README.md +3 -17
  63. package/spec/v2/ext/provider-idempotency/README.md +7 -10
  64. package/spec/v2/ext/restTransport/README.md +30 -19
  65. package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
  66. package/spec/v2/ext/webResearch/README.md +16 -16
  67. package/spec/v2/profiles.json +6 -2
  68. package/spec/v2/release.json +2 -2
  69. package/spec/v2/surface-baseline.json +544 -2
@@ -1,6 +1,6 @@
1
1
  # Persistence and Coexistence
2
2
 
3
- > **Status: Stable · RFC 0176 (§A–§B, §D–§E), 0171 §A, 0170 §A.3, 0158 §A–§D.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `eventLog`.
5
5
 
6
6
  ## Why this exists
@@ -9,11 +9,16 @@ How a v2 host reads what a v1 host wrote, what happens to a run in flight at the
9
9
 
10
10
  ## The codemap is data
11
11
 
12
- `spec/v2/event-codemap.json`, shipped in `@openwop/spec-artifacts`, is the only authority for the v1→v2 event-type mapping; every row is `decided` (RFC 0171 §A). A host MUST NOT carry a private mapping. A vendor-prefixed v1 type the codemap does not name MUST be read under its own name unchanged, where "vendor-prefixed" means the first segment is an org registered in the `extensions` object of `spec/v2/declaration.json` (events.md §Rules; RFC 0171 §A.1 — `openwop.` is the only reserved prefix). An unregistered first segment is not a vendor prefix and falls to the refusal below. An org is registered by pull request against the corpus and takes effect on the `@openwop/spec-artifacts` release that carries it; a shipped entry is append-only, because deregistering an org would convert every log already written under it from pass-through to refusal (RFC 0180).
12
+ `spec/v2/event-codemap.json`, shipped in `@openwop/spec-artifacts`, is the only authority for the v1→v2 event-type mapping. Every row is `decided`.
13
+
14
+ - A host MUST NOT carry a private mapping.
15
+ - A vendor-prefixed v1 type the codemap does not name MUST be read under its own name, unchanged.
16
+ - "Vendor-prefixed" means the first segment is an org registered in the `extensions` object of `spec/v2/declaration.json` ([events.md §Rules](events.md)). `openwop.` is the only reserved prefix. An unregistered first segment is not a vendor prefix and falls to the refusal in §"The reader rule".
17
+ - An org is registered by pull request against the corpus and takes effect on the `@openwop/spec-artifacts` release that carries it. A shipped entry is append-only: deregistering an org would turn every log already written under it from pass-through into refusal.
13
18
 
14
19
  ## The era key
15
20
 
16
- `eventLogSchemaVersion` is the era key and is required on every run snapshot (`schemas/v2/run-snapshot.schema.json`).
21
+ `eventLogSchemaVersion` is the era key. It is required on every run snapshot (`schemas/v2/run-snapshot.schema.json`).
17
22
 
18
23
  | Value | Meaning |
19
24
  | --- | --- |
@@ -24,112 +29,92 @@ How a v2 host reads what a v1 host wrote, what happens to a run in flight at the
24
29
 
25
30
  Discovery MUST advertise the value the host writes for new runs and nothing else; a host MUST hold one constant for this axis.
26
31
 
27
- **Absent stays era `2` forever; it is never backfilled.** A host MUST NOT rewrite
28
- historical rows to add an explicit `2`, and a reader MUST NOT require one. A host with more than one creation
29
- path MUST begin stamping `3` on **all** of them in the same change: an unstamped
30
- path's runs read as era `2`, a silent wrong read.
32
+ Absent stays era `2` forever; it is never backfilled.
33
+
34
+ - A host MUST NOT rewrite historical rows to add an explicit `2`, and a reader MUST NOT require one.
35
+ - A host with more than one creation path MUST begin stamping `3` on all of them in the same change. An unstamped path's runs read as era `2` — a silent wrong read.
31
36
 
32
- **The snapshot field is required on the wire, and MAY be synthesized.**
33
- For an era-`2` run with nothing stored, the host MUST supply `2` from the
34
- absent-⇒-`2` rule rather than fail the read. The field therefore cannot falsify
35
- era handling on its own; the reader and writer rules below carry the obligation.
37
+ The snapshot field is required on the wire, and MAY be synthesized: for an era-`2` run with nothing stored, the host MUST supply `2` from the absent-⇒-`2` rule rather than fail the read. The field alone therefore cannot falsify era handling; the reader and writer rules carry the obligation.
36
38
 
37
39
  ## The `eventLog` family
38
40
 
39
- `eventLog` is the capability record by which a host advertises the era contract
40
- above. A host that advertises `eventLog` MUST stamp `eventLogSchemaVersion` on
41
- every run it creates, MUST serve the cursor contract in events.md §"Poll" over
42
- that log, and MUST NOT emit an event `type` the codemap does not name. The
43
- record carries no separate storage claim: it asserts that the era key, the
44
- codemap and the poll cursor are implemented as written here, which is why its
45
- floor scenarios are the era-key and cursor witnesses rather than tests of a
46
- surface of its own. Its `crossEngineOrdering` facet is a replay property and is
47
- normed in replay.md §"Cross-engine ordering".
41
+ `eventLog` is the capability record by which a host advertises the era contract above. A host that advertises `eventLog`:
42
+
43
+ - MUST stamp `eventLogSchemaVersion` on every run it creates;
44
+ - MUST serve the cursor contract of [events.md §"Poll"](events.md) over that log;
45
+ - MUST NOT emit an event `type` the codemap does not name.
46
+
47
+ The record carries no separate storage claim: it asserts that the era key, the codemap and the poll cursor are implemented as written here. Its `crossEngineOrdering` facet is a replay property, specified in [replay.md §"Cross-engine ordering"](replay.md).
48
48
 
49
49
  ## The reader rule
50
50
 
51
51
  A v2 host reading a run in era `2` MUST translate every event through the codemap at the storage boundary:
52
52
 
53
- - `type` is mapped; the payload is projected per RFC 0171 §B.
53
+ - `type` is mapped, and the payload projected with it.
54
54
  - `sequence` MUST be preserved verbatim, including `0`.
55
55
  - `eventId`, `timestamp`, `causationId`, and vendor fields pass through.
56
- - A type the codemap does not name and that carries no reserved vendor prefix MUST fail the read with `event_type_unmapped` (`spec/v2/errors.json`, `500`). A malformed row MUST fail the read rather than default any field.
56
+ - A type the codemap does not name and that carries no reserved vendor prefix MUST fail the read with `event_type_unmapped` (`spec/v2/errors.json`, `500`).
57
+ - A malformed row MUST fail the read rather than default any field.
58
+
59
+ The rule binds every reader: poll, SSE, fork, replay divergence, debug bundle, summary memory.
57
60
 
58
- The rule binds every reader: poll, SSE, fork, replay divergence, debug bundle, summary memory. The translation is a read projection. A host MUST NOT rewrite era-`2` rows in place; a background backfill that stamps `3` and rewrites `type` under the same `(runId, sequence)` key is permitted only as an atomic per-run operation with the original preserved, because the fork prefix must stay byte-equivalent to the translated parent (replay.md, RFC 0041 §C).
61
+ The translation is a read projection. A host MUST NOT rewrite era-`2` rows in place. The one exception is a background backfill that stamps `3` and rewrites `type` under the same `(runId, sequence)` key: it is permitted only as an atomic per-run operation with the original preserved, because the fork prefix must stay byte-equivalent to the translated parent ([replay.md](replay.md)).
59
62
 
60
63
  ### The writer rule
61
64
 
62
- The era key is fixed when the run is created and fixes the log's vocabulary for
63
- the run's lifetime. An append to a run in era `2` MUST use v1 vocabulary — the
64
- name the codemap maps *from*, not the v2 name it maps to. A host that upgrades
65
- mid-flight MUST NOT begin writing v2 names into a log the reader translates as
66
- era `2`: the reader would map an already-mapped name a second time, or fail the
67
- read with `event_type_unmapped` on a name the codemap does not carry on its v1
68
- side. A run created after the upgrade is era `3` and is written in v2
69
- vocabulary, untranslated.
65
+ The era key is fixed when the run is created and fixes the log's vocabulary for the run's lifetime.
70
66
 
71
- A writer that emits a property a closed def cannot seat (RFC 0185 §B) MUST
72
- mark the row with what it could not seat, so the refusal names the writer
73
- instead of surfacing as an unexplained read failure (RFC 0187 §D.1).
67
+ - An append to a run in era `2` MUST use v1 vocabulary — the name the codemap maps *from*, not the v2 name it maps to.
68
+ - A host that upgrades mid-flight MUST NOT begin writing v2 names into a log the reader translates as era `2`. The reader would map an already-mapped name a second time, or fail with `event_type_unmapped` on a name absent from the codemap's v1 side.
69
+ - A run created after the upgrade is era `3` and is written in v2 vocabulary, untranslated.
70
+ - A writer that emits a property a closed def cannot seat MUST mark the row with what it could not seat, so the refusal names the writer instead of surfacing as an unexplained read failure.
74
71
 
75
- This binds every writer for as long as an era-`2` run stays open (§"Runs pinned
76
- to v1"); its witness is `v2-era-2-append-vocabulary`.
72
+ This binds every writer for as long as an era-`2` run stays open (§"Runs pinned to v1"). Its witness is `v2-era-2-append-vocabulary`.
77
73
 
78
74
  ### The v1 wire of an era-`3` log
79
75
 
80
- Through the overlap (`versioning.md` §5) an era-`3` log, stored in v2
81
- vocabulary, must still read on `/v1/…` exactly as before the cut.
76
+ Through the overlap ([versioning.md §5](versioning.md)), an era-`3` log, stored in v2 vocabulary, must still read on `/v1/…` exactly as before the cut. A host serving both majors:
82
77
 
83
- A host serving both majors MUST therefore map an era-`3` log's `type` back to
84
- its v1 spelling on the v1 read path, through the **same codemap row, inverted**.
85
- The inverse is exact only while `spec/v2/event-codemap.json` is a bijection; a
86
- host MUST verify that at load and, if a row folds two v1 names onto one v2 name,
87
- MUST refuse to serve the v1 representation rather than guess a spelling.
88
-
89
- A type with NO codemap row — v2-only vocabulary, anything RFC 0185/0186 seated
90
- — has no v1 spelling to invert to. A host MUST emit it unchanged on the v1 read
91
- path, MUST NOT drop the row, and MUST NOT refuse the read for it (RFC 0187
92
- §B.1).
78
+ - MUST map an era-`3` log's `type` back to its v1 spelling on the v1 read path, through the same codemap row, inverted.
79
+ - MUST verify at load that `spec/v2/event-codemap.json` is a bijection, since only then is the inverse exact. If a row folds two v1 names onto one v2 name, the host MUST refuse to serve the v1 representation rather than guess a spelling.
80
+ - MUST emit a type with no codemap row (v2-only vocabulary, which has no v1 spelling) unchanged on the v1 read path, MUST NOT drop the row, and MUST NOT refuse the read for it.
93
81
 
94
82
  ### The seat
95
83
 
96
- The adapter MUST sit at the storage boundary every reader passes through — the storage interface's event-list method, not a wrapper some call sites bypass. A host leg MUST name its seat in its ADR.
84
+ - The adapter MUST sit at the storage boundary every reader passes through — the storage interface's event-list method, not a wrapper some call sites bypass.
85
+ - A host leg MUST name its seat in its ADR.
97
86
 
98
- The seat is a **claims-check** (conformance.md §Witness class): discharged by that disclosure and by audit, never by the wire. The rule binds **every** reader, including the ones the suite has no name for; `run-event.schema.json` records why three passing legs do not discharge it.
87
+ The seat is a claims-check ([conformance.md §Witness class](conformance.md)): it is discharged by that disclosure and by audit, never by the wire. It binds every reader, including ones the suite has no name for; `run-event.schema.json` records why passing legs do not discharge it.
99
88
 
100
89
  ## Runs pinned to v1
101
90
 
102
91
  A non-terminal run a v2 host inherits carries `version.pinned` events naming change ids. The host MUST continue it or cancel it, never follow a pin silently.
103
92
 
104
- | Condition | Requirement |
105
- | --- | --- |
106
- | Every pinned change id is still implemented | The run MUST continue under the reader rule; the pin is honored verbatim and `version.pinned` is never rewritten. |
107
- | Any pinned change id is no longer implemented | The host MUST cancel the run with `run.cancelled` reason `v1_pin_unsupported` and `cancelledBy: "v2-cutover"`; the certification bundle reports the count. |
108
- | Suspended on an interrupt at the cut | The run continues under the row above; its token drains per §"Everything else a v1 host persisted". |
93
+ - **Every pinned change id is still implemented** — the run MUST continue under the reader rule. The pin is honored verbatim and `version.pinned` is never rewritten.
94
+ - **Any pinned change id is no longer implemented** — the host MUST cancel the run with `run.cancelled` reason `v1_pin_unsupported` and `cancelledBy: "v2-cutover"`. The certification bundle reports the count.
95
+ - **Suspended on an interrupt at the cut** — the run continues under the rules above; its token drains per §"Everything else a v1 host persisted".
109
96
 
110
- "Drain" is retired as the only path. Multi-region skew is read-side only: after the cut a v2 region MUST NOT accept an era-`2` write for a run it has already stamped `3` (RFC 0176 §B.3). Discovery's `minClientVersion` rule is RFC 0172 row `C5.8`.
97
+ Multi-region skew is read-side only: after the cut, a v2 region MUST NOT accept an era-`2` write for a run it has already stamped `3`. Discovery's `minClientVersion` rule is in [versioning.md §1.5](versioning.md).
111
98
 
112
99
  ## Everything else a v1 host persisted
113
100
 
114
- | Artifact | Requirement |
115
- | --- | --- |
116
- | Certification bundles | Never upgraded. A v1 bundle substantiates no new certification after 2026-11-10; every host produces a fresh v2-rc bundle before the cut (conformance.md). |
117
- | Webhook deliveries | Dual-emitted, and queued deliveries drained, per webhooks.md §"Dual emission through the overlap". |
118
- | Interrupt resume tokens | Drained: a token that is **not** `ow2.`-prefixed is a v1 token and resolves under `kid: legacy` until `expiresAt`; new tokens carry the `ow2.` prefix (identity.md). The rule is written over the prefix, never a segment count. |
119
- | Layer-1 and Layer-2 records (idempotency, idempotent responses, invocation claims and logs, effect-escape ledger, dispatch outbox, envelope correlations) | Unchanged; keyed on ids the cut does not rename. `GET /runs/{runId}/effects` and `GET /runs/{runId}/compensation` are new reads over them (security-defaults.md). |
120
- | Owner stamps | A run without a Subject MUST be legacy-stamped at first v2 read and MUST NOT be rewritten later (RFC 0170 §A.3). A host's stored owner fields are projected to the Subject; the projection is the host's to name. |
121
- | Audit log | Never upgraded (RFC 0170). |
101
+ - **Certification bundles** — never upgraded. A v1 bundle substantiates no new certification after 2026-11-10; every host produces a fresh v2-rc bundle before the cut ([conformance.md](conformance.md)).
102
+ - **Webhook deliveries** — dual-emitted, and queued deliveries drained, per [webhooks.md §"Dual emission through the overlap"](webhooks.md).
103
+ - **Interrupt resume tokens** — drained. A token that is not `ow2.`-prefixed is a v1 token and resolves under `kid: legacy` until `expiresAt`; new tokens carry the `ow2.` prefix ([identity.md](identity.md)). The rule is written over the prefix, never a segment count.
104
+ - **Layer-1 and Layer-2 records** (idempotency, idempotent responses, invocation claims and logs, effect-escape ledger, dispatch outbox, envelope correlations) — unchanged; keyed on ids the cut does not rename. `GET /runs/{runId}/effects` and `GET /runs/{runId}/compensation` are new reads over them ([security-defaults.md](security-defaults.md)).
105
+ - **Owner stamps** — a run without a Subject MUST be legacy-stamped at first v2 read and MUST NOT be rewritten later. A host's stored owner fields are projected to the Subject; the projection is the host's to name.
106
+ - **Audit log** — never upgraded.
122
107
 
123
108
  ## Per-store disposition
124
109
 
125
- A host's ADR MUST name every store it persists and give each one disposition from the closed set below.
110
+ A host's ADR MUST name every store it persists and give each one disposition from the closed set below. A host MUST NOT decide a store's disposition during the migration.
126
111
 
127
112
  | Disposition | Meaning |
128
113
  | --- | --- |
129
114
  | `unchanged` | Rows keep their shape and keys; a v2 reader consumes them as they are. |
130
- | `translated` | Rows are read through the codemap at the storage boundary; never rewritten in place except the atomic per-run backfill. |
131
- | `drained` | Rows complete under their own v1 contract until exhausted or expired; no new rows of the v1 shape are written. |
132
- | `legacy-stamped` | A missing v2 field is given its legacy value at first v2 read and never rewritten. |
115
+ | `translated` | Read through the codemap at the storage boundary; rewritten in place only by the atomic per-run backfill. |
116
+ | `drained` | Rows complete under their own v1 contract until exhausted or expired; no new v1-shaped rows are written. |
117
+ | `legacy-stamped` | A missing v2 field gets its legacy value at first v2 read and is never rewritten. |
133
118
  | `never-upgraded` | Rows remain v1 evidence only; v2 evidence is produced fresh. |
134
119
  | `not-persisted` | Nothing to migrate. |
135
120
 
@@ -146,20 +131,16 @@ Template — one row per store:
146
131
  | certification bundles | v1 bundles | `never-upgraded` |
147
132
  | host-internal tables | outside the wire | `unchanged` |
148
133
 
149
- The reference hosts' dispositions are recorded in RFC 0176; a host MUST NOT decide a store's disposition during the migration.
150
-
151
134
  ## Durable acceptance and recovery
152
135
 
153
- RFC 0158 §A–§D.
136
+ - **Acceptance** — a host that returns success for work it accepted MUST have made the intent to perform it durable in the same transaction as the work record. A wakeup, hint, or in-process dispatch MUST NOT be the only record. The host MUST resume accepted-but-unstarted work after the accepting process dies, without client action.
137
+ - **Liveness** — a host MUST distinguish how long a unit of work may legitimately run from the interval in which a live worker demonstrates liveness, and MUST NOT use the duration bound as the sole liveness signal.
138
+ - **Recovery bound** — a host MUST declare the longest interval between an instance ceasing to make progress and another becoming eligible to resume its work. It MUST derive the bound from the mechanism that enforces it: one bound per enforcing mechanism, never a single aggregate. Any length is conformant; an undeclared or unenforced bound is not.
139
+ - **Duplicates** — duplicate delivery of accepted work MUST NOT produce duplicate external effects. The host MUST dedupe on an identity that survives redelivery ([idempotency.md](idempotency.md)).
140
+ - **Poison work** — work that fails deterministically MUST reach a terminal, operator-visible state within a bounded number of attempts.
154
141
 
155
- | Clause | Requirement |
156
- | --- | --- |
157
- | Acceptance (§A) | A host that returns success for work it accepted MUST have made the intent to perform it durable in the same transaction as the work record; a wakeup, hint, or in-process dispatch MUST NOT be the only record. It MUST resume accepted-but-unstarted work after the accepting process dies, without client action. |
158
- | Liveness (§B.3) | A host MUST distinguish how long a unit of work may legitimately run from the interval in which a live worker demonstrates liveness, and MUST NOT use the duration bound as the sole liveness signal. |
159
- | Recovery bound (§B.4–6) | A host MUST declare the longest interval between an instance ceasing to make progress and another becoming eligible to resume its work, and MUST derive it from the mechanism that enforces it — one bound per enforcing mechanism, never a single aggregate. Any length is conformant; an undeclared or unenforced bound is not. |
160
- | Duplicates (§C.7) | Duplicate delivery of accepted work MUST NOT produce duplicate external effects; the host MUST dedupe on an identity that survives redelivery (idempotency.md). |
161
- | Poison work (§C.8) | Work that fails deterministically MUST reach a terminal, operator-visible state within a bounded number of attempts. |
142
+ A host MAY claim a qualification rung (`durable-single-instance`, `durable-multi-instance`, `multi-region-qualified`; cumulative) only with the evidence [RFC 0158 §D](https://github.com/openwop/openwop/blob/main/RFCS/0158-durable-execution-and-disaster-recovery-qualification.md) names for it, and MUST NOT claim one from tests in which no process was terminated. A rung is evidence, not a capability: discovery carries none, and the certification bundle publishes the rung and its bounds ([conformance.md §Bundle v3](conformance.md)).
162
143
 
163
- A host MAY claim a qualification rung (`durable-single-instance`, `durable-multi-instance`, `multi-region-qualified`; cumulative) only with the evidence named for it, and MUST NOT claim one from tests in which no process was terminated (§D.9). A rung is evidence, not a capability: discovery carries none, and the rung and its bounds are published in the certification bundle (conformance.md §Bundle v3).
144
+ See also: [overview.md](overview.md), [events.md](events.md), [replay.md](replay.md), [identity.md](identity.md), [webhooks.md](webhooks.md).
164
145
 
165
- See also: overview.md, events.md, replay.md, identity.md, webhooks.md.
146
+ *Sources: RFC 0041, RFC 0158, RFC 0170, RFC 0171, RFC 0172, RFC 0176, RFC 0180, RFC 0185, RFC 0186, RFC 0187.*
@@ -0,0 +1,44 @@
1
+ # Portability
2
+
3
+ > **Status: Stable.**
4
+ > **Normative home:** `portability`.
5
+
6
+ ## Why this exists
7
+
8
+ A tenant's reusable estate moves between hosts as one export bundle, and lands on the destination under the importing caller's identity. This document states the bundle and the rules an importer follows. An adapter that turns another platform's export into a bundle is host tooling.
9
+
10
+ ## The export bundle
11
+
12
+ An `ExportBundle` (`schemas/v2/export-bundle.schema.json`) carries `bundleVersion: "2"`, a `source` (its origin and an informational `originPrincipal`) and `items[]`. Each item has:
13
+
14
+ - a `kind`: `agent`, `pack`, `prompt-template`, `connection-ref`, `schedule`, `roster` or `org-chart`;
15
+ - a bundle-local `ref` and optional `dependsOn` edges;
16
+ - a `payload` shaped by that kind's own schema.
17
+
18
+ ## The `portability` record
19
+
20
+ - **`export`** — the host emits a bundle for the caller's tenant or workspace, optionally limited to some `kinds`.
21
+ - **`import`** — the host applies a bundle.
22
+ - **`kinds`** — the item kinds the host exports and imports.
23
+ - **`dryRun`** — import offers a plan preview that writes nothing. A host advertising `import` MUST advertise `dryRun: true`.
24
+
25
+ ## Import rules
26
+
27
+ - **No credential material.** A bundle MUST NOT contain credential values: a `connection-ref` item carries only references and provider ids. The importer MUST report unbound references in `secretsToRebind` and MUST NOT invent or transfer secret material. A host MUST reject with `422` an imported bundle whose payload carries a literal credential value.
28
+ - **Dry run.** When `import` is advertised, import MUST offer a dry run. A dry run MUST NOT write, and MUST return the plan it would execute: creates, updates, skips, conflicts and unbound credential references.
29
+ - **Idempotent.** Import MUST be idempotent: re-applying a bundle resolves each item to `skipped` or `updated`, never to a duplicate create.
30
+ - **Ordered.** Items MUST be applied in `dependsOn` topological order. A cycle is a `422`.
31
+ - **Re-owned.** Every imported entity MUST be re-owned to the caller's Subject at the destination ([identity.md](identity.md) §1). `source.originPrincipal` is informational and MUST NOT grant any access. Host-scoped handles are re-minted as [identity.md](identity.md) §5 requires.
32
+ - **Executable behavior.** An imported agent or pack that is executable behavior SHOULD pass through the host's install policy ([RFC 0043](https://github.com/openwop/openwop/blob/main/RFCS/0043-registry-and-extension-policy.md)), and MAY be staged as a proposal ([RFC 0096](https://github.com/openwop/openwop/blob/main/RFCS/0096-reviewable-learning-skill-proposal-lifecycle.md)) instead of being activated.
33
+
34
+ Applying an import returns an outcome per item — `created`, `updated`, `skipped` or `failed` — plus `secretsToRebind`.
35
+
36
+ ## The `import.applied` event
37
+
38
+ An applied import emits `import.applied` (`schemas/v2/run-event-payloads.schema.json`). A host MUST NOT emit it unless it advertises `portability`. It carries counts and references only, never item payloads or secret values.
39
+
40
+ ## Routes
41
+
42
+ No protocol path is defined for export or import: a host serves them on routes of its own. A host-private migration from an anonymous sandbox into a signed-in tenant fits this contract, because its response is a subset of the import outcome.
43
+
44
+ *Sources: RFC 0043, RFC 0096, RFC 0098.*
@@ -1,6 +1,6 @@
1
1
  # Replay and Fork
2
2
 
3
- > **Status: Stable · RFC 0140, 0041, 0173 §C, 0176 §A.5.**
3
+ > **Status: Stable.**
4
4
  > **Normative home:** `eventLog`, `replay`, `nondeterminismPolicy`.
5
5
 
6
6
  ## Why this exists
@@ -9,14 +9,23 @@
9
9
 
10
10
  ## The surface
11
11
 
12
- A host that advertises `replay` (capabilities.md) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`). The `replay` facet (`spec/v2/facets/replay.schema.json`) is `{ modes[], retention?, effectSeamsManifest }`; `modes` enumerates `replay | branch` (the `forkRun` `mode` values); `effectSeamsManifest` is the constant `/host/effect-seams`. There is no `sideEffectSuppression` field: suppression is the only conforming replay behavior (RFC 0173 §B, row `C6.2`) and `none` is not a value.
12
+ A host that advertises `replay` ([capabilities.md](capabilities.md)) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`).
13
13
 
14
- The request body, `fromSeq` defaults and `201` response are runs.md §Fork. Events with `sequence < fromSeq` are fixed history; events `>= fromSeq` are re-executed.
14
+ The `replay` facet (`spec/v2/facets/replay.schema.json`) is `{ modes[], retention?, effectSeamsManifest }`:
15
15
 
16
- | Rule | Requirement |
17
- | --- | --- |
18
- | `fromSeq` out of range | `400`; a sequence absent from the source log is `422`. A `fromSeq` greater than the sequence of the source run's terminal run event MUST be refused `422 fork_point_invalid` — the fork would inherit a terminal event and then execute (RFC 0194 §C; binds only where a compensation tail follows the terminal event). |
19
- | Source run not visible to the caller | `404`. |
16
+ - `modes` enumerates `replay | branch` (the `forkRun` `mode` values);
17
+ - `effectSeamsManifest` is the constant `/host/effect-seams`.
18
+
19
+ Suppression is the only conforming replay behavior; no field turns it off, and `none` is not a value.
20
+
21
+ The request body, `fromSeq` defaults and `201` response are [runs.md §Fork](runs.md). Events with `sequence < fromSeq` are fixed history; events `>= fromSeq` are re-executed.
22
+
23
+ Refusals:
24
+
25
+ - `fromSeq` out of range — `400`.
26
+ - A sequence absent from the source log — `422`.
27
+ - A `fromSeq` greater than the sequence of the source run's terminal run event MUST be refused `422 fork_point_invalid`: the fork would inherit a terminal event and then execute. This binds only where a compensation tail follows the terminal event.
28
+ - Source run not visible to the caller — `404`.
20
29
 
21
30
  ## Modes
22
31
 
@@ -26,13 +35,13 @@ The request body, `fromSeq` defaults and `201` response are runs.md §Fork. Even
26
35
 
27
36
  ## Byte-equivalence of the prefix
28
37
 
29
- The replay contract is observable-output-sequence determinism, not bit-equivalent execution (RFC 0041 §C):
38
+ The replay contract is observable-output-sequence determinism, not bit-equivalent execution:
30
39
 
31
- 1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields (RFC 0036 §E) and ULID time-component entropy when ULIDs are minted fresh. The event at `fromSeq` is governed by §Divergence.
40
+ 1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields and ULID time-component entropy when ULIDs are minted fresh. The event at `fromSeq` is governed by §Divergence.
32
41
  2. `variables`, `channels`, and `status` of the run snapshot at each index in that range MUST be byte-equivalent.
33
42
  3. The bytes on the wire of underlying tool and LLM calls MAY differ, provided the observable state at each index is byte-equivalent.
34
43
 
35
- A host MUST cache the observable result (return value, workflow-state effects, emitted events), not merely the tool-call boundary. The cache key for LLM-calling nodes is the RFC 0041 content-addressed key; for other tool-calling nodes it MUST be content-addressable, never a host-internal sequence number or timestamp.
44
+ A host MUST cache the observable result (return value, workflow-state effects, emitted events), not merely the tool-call boundary. The cache key for LLM-calling nodes is the content-addressed invocation key defined in [RFC 0041](https://github.com/openwop/openwop/blob/main/RFCS/0041-multi-agent-replay-under-nondeterminism.md); for other tool-calling nodes it MUST be content-addressable, never a host-internal sequence number or timestamp.
36
45
 
37
46
  ## Determinism caveats (`replay` mode)
38
47
 
@@ -40,23 +49,27 @@ A host MUST cache the observable result (return value, workflow-state effects, e
40
49
  2. `ctx.interrupt(K)` MUST short-circuit to the persisted `interrupt.resolved` value.
41
50
  3. `ctx.getVersion` pins from the source run are fixed history; the replay MUST take the recorded branch.
42
51
  4. Nodes MUST consume time via `ctx.now()` where available; direct clock reads make replay non-deterministic.
43
- 5. Recorded-fact events such as `memory.written` (RFC 0057) are fixed history. A replay MUST re-emit them verbatim from the log and MUST NOT regenerate their identifiers or timestamps — never a new `memoryId`. A `branch` MAY perform its own memory writes with fresh identifiers.
44
- 6. Approver eligibility recorded on a resume event is fixed history; a host MUST NOT re-resolve membership during replay (RFC 0104).
52
+ 5. Recorded-fact events such as `memory.written` are fixed history. A replay MUST re-emit them verbatim from the log and MUST NOT regenerate their identifiers or timestamps — never a new `memoryId`. A `branch` MAY perform its own memory writes with fresh identifiers.
53
+ 6. Approver eligibility recorded on a resume event is fixed history; a host MUST NOT re-resolve membership during replay.
45
54
 
46
55
  ## Divergence
47
56
 
48
- When a replayed node produces an event different from the source at the same sequence, the host MUST continue, MUST emit `replay.diverged` `{ originalEventId, replayEventId, divergencePoint }`, and MUST surface it in `debug` stream mode and as OTel attribute `openwop.replay.diverged: true`.
57
+ When a replayed node produces an event different from the source at the same sequence, the host:
49
58
 
50
- | Code (`spec/v2/errors.json`) | Where | Condition |
51
- | --- | --- | --- |
52
- | `replay_diverged_at_refusal` | fork fails, `409` | The source obtained a valid envelope and the replay a refusal, or the reverse. The host MUST NOT substitute silently; it MUST emit `replay.diverged-at-refusal` naming the node and both envelope kinds and fail the replay with this code (RFC 0041 §B). |
53
- | `replay_source_missing` | `node.failed` payload; the fork request still returns `201` | A side-effecting node reached with no recorded source outcome for `(nodeId, attempt)` (§Suppression). |
54
- | `replay_memory_snapshot_unavailable` | fork refused, `409` | The host cannot serve memory state as-of `fromSeq`. It MUST refuse rather than substitute current memory; `details.fromSeq` SHOULD name the index (RFC 0039 §B). |
55
- | `replay_context_summary_unavailable` | fork refused, `409` | The host advertises `multiAgent.executionModel.contextBudget.summarization` and cannot serve, as-of `fromSeq`, a summary artifact (`context.summarized.summaryRef`) the replay would reuse. It MUST refuse rather than re-summarize; `details.fromSeq` SHOULD name the index (RFC 0111). |
59
+ - MUST continue;
60
+ - MUST emit `replay.diverged` `{ originalEventId, replayEventId, divergencePoint }`;
61
+ - MUST surface it in `debug` stream mode and as OTel attribute `openwop.replay.diverged: true`.
62
+
63
+ Divergence codes (`spec/v2/errors.json`):
64
+
65
+ - **`replay_diverged_at_refusal`** (fork fails, `409`) — the source obtained a valid envelope and the replay a refusal, or the reverse. The host MUST NOT substitute silently; it MUST emit `replay.diverged-at-refusal` naming the node and both envelope kinds and fail the replay with this code.
66
+ - **`replay_source_missing`** (`node.failed` payload; the fork request still returns `201`) — a side-effecting node reached with no recorded source outcome for `(nodeId, attempt)` (§Suppression).
67
+ - **`replay_memory_snapshot_unavailable`** (fork refused, `409`) — the host cannot serve memory state as-of `fromSeq`. It MUST refuse rather than substitute current memory; `details.fromSeq` SHOULD name the index.
68
+ - **`replay_context_summary_unavailable`** (fork refused, `409`) — the host advertises `multiAgent.executionModel.contextBudget.summarization` and cannot serve, as-of `fromSeq`, a summary artifact (`context.summarized.summaryRef`) the replay would reuse. It MUST refuse rather than re-summarize; `details.fromSeq` SHOULD name the index.
56
69
 
57
70
  ## Suppression
58
71
 
59
- Suppression is an obligation of the `replay` surface (RFC 0173 §A.1, §B): advertising `replay` binds it, and a host that cannot suppress MUST NOT advertise `replay`. A relaxation is an operator setting recorded in the certification bundle (security-defaults.md).
72
+ Suppression is an obligation of the `replay` surface: advertising `replay` binds it, and a host that cannot suppress MUST NOT advertise `replay`. A relaxation is an operator setting recorded in the certification bundle (security-defaults.md).
60
73
 
61
74
  For a fork with `mode: replay`:
62
75
 
@@ -69,53 +82,54 @@ For a fork with `mode: replay`:
69
82
 
70
83
  Pure nodes and LLM calls served from the invocation log MUST re-execute live; otherwise divergence detection is vacuous.
71
84
 
72
- **Fan-out.** A host that projects its log outward — webhook delivery, A2A push, outbound streams, analytics or audit sinks — MUST NOT deliver events a replay re-emits as fixed history, and a fork of either mode MUST NOT inherit its source's A2A push configs. Replay-ness MUST be read from the run, never from the event type; the fork's own log MUST still carry the re-emitted events (webhooks.md).
85
+ **Fan-out.** A host that projects its log outward — webhook delivery, A2A push, outbound streams, analytics or audit sinks — MUST NOT deliver events a replay re-emits as fixed history, and a fork of either mode MUST NOT inherit its source's A2A push configs. Replay-ness MUST be read from the run, never from the event type; the fork's own log MUST still carry the re-emitted events ([webhooks.md](webhooks.md)).
73
86
 
74
87
  **Branch.** A branch re-fires effects for sequences `>= fromSeq`; those are effects the operator asked for. A host MAY suppress branch effects and MUST NOT report that as replay suppression. A host SHOULD surface the re-fire in operator-facing fork UI.
75
88
 
76
89
  ### The effect-seam manifest
77
90
 
78
- A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema.json`-shaped data at `GET /host/effect-seams` (RFC 0173 §C.1): `{ manifestVersion: "1", host: { name, build }, seams[] }`, one row per outbound effect path its node runtime can reach, `{ seam, kind, guarded: true, guardedBy, branchReFires?, note? }`. The host owns the list. The suite drives one seam of each kind it can reach and observes no re-fire (`effect-seam-manifest`, conformance.md).
91
+ A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema.json`-shaped data at `GET /host/effect-seams`: `{ manifestVersion: "1", host: { name, build }, seams[] }`, one row per outbound effect path its node runtime can reach, `{ seam, kind, guarded: true, guardedBy, branchReFires?, note? }`. The host owns the list. The suite drives one seam of each kind it can reach and observes no re-fire (`effect-seam-manifest`, [conformance.md](conformance.md)).
92
+
93
+ `kind` names the outbound wire mechanism the seam leaves the host by — not the suite's driving mechanism, and not the business purpose. It MUST be one of `http`, `smtp`, `queue`, `storage`, `provider-sdk`, `webhook-fanout`, `other`.
79
94
 
80
- `kind` names the outbound **wire mechanism** the seam leaves the host by — not the suite's driving mechanism, and not the business purpose. It MUST be one of `http`, `smtp`, `queue`, `storage`, `provider-sdk`, `webhook-fanout`, `other`. Two seams that a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one. `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK — and a row using it MUST carry `note` naming that mechanism.
95
+ - Two seams a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one.
96
+ - `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK. A row using it MUST carry `note` naming that mechanism.
81
97
 
82
- **Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`), which the scenario records `inapplicable` naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive. The manifest is a self-declaration whose false negatives are found by audit — negative-existence, not a witness.
98
+ **Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`); the scenario records that one `inapplicable`, naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive. The manifest is a self-declaration whose false negatives are found by audit, not a witness.
83
99
 
84
100
  ## Replay-from-event-log internals
85
101
 
86
102
  1. Load the source run's events with `sequence < fromSeq` through the storage boundary, where an era-`2` log is translated (persistence.md).
87
103
  2. Fold them to a projected state.
88
104
  3. Initialize the new run with that state, copy-on-write into its own log.
89
- 4. For `replay`, resolve side-effecting nodes from the source run's recorded outcomes keyed on `(sourceRunId, nodeId, attempt)`; LLM invocations additionally consult the invocation log via the RFC 0041 content-addressed key.
105
+ 4. For `replay`, resolve side-effecting nodes from the source run's recorded outcomes keyed on `(sourceRunId, nodeId, attempt)`; LLM invocations additionally consult the invocation log via the content-addressed invocation key.
90
106
  5. For `branch`, executor invocations create new invocation-log entries keyed on the new `runId`.
91
107
 
92
108
  ## Forking a v1 run
93
109
 
94
- A v2 host MUST fork a run created before the cut (era `2`, persistence.md). The fork's prefix MUST be byte-equivalent to the *translated* parent — the parent as read through the codemap, not its stored bytes — and `run.started` on the fork MUST carry the legacy Subject (`issuer: urn:openwop:legacy`, identity.md) where the parent had none (RFC 0176 §A.5, scenario `fork-a-v1-run`). A backfill of an era-`2` log is permitted only atomically per run with the original preserved, so this obligation stays checkable.
110
+ A v2 host MUST fork a run created before the cut (era `2`, [persistence.md](persistence.md)). The `fork-a-v1-run` scenario witnesses this.
111
+
112
+ - The fork's prefix MUST be byte-equivalent to the *translated* parent — the parent as read through the codemap, not its stored bytes.
113
+ - `run.started` on the fork MUST carry the legacy Subject (`issuer: urn:openwop:legacy`, [identity.md](identity.md)) where the parent had none.
114
+ - A backfill of an era-`2` log is permitted only atomically per run with the original preserved, so this obligation stays checkable.
95
115
 
96
116
  ## Cross-engine ordering
97
117
 
98
- When a host advertises both `idempotency.multiRegion` and
99
- `eventLog.crossEngineOrdering`, a fork served by a region other than the one
100
- that wrote the run MUST produce the same observable state at the `fromSeq`
101
- boundary as a fork served by the writing region: `status`, `variables`, and the
102
- projected event log up to `fromSeq` MUST be byte-equivalent across regions.
103
- Per-region wall-clock and entropy fields in events AFTER the boundary MAY
104
- differ. A host that advertises one of the two and not the other keeps the
105
- single-region contract above; a host that advertises neither is single-region
106
- and the cross-region claim does not apply.
118
+ When a host advertises both `idempotency.multiRegion` and `eventLog.crossEngineOrdering`, a fork served by a region other than the one that wrote the run MUST produce the same observable state at the `fromSeq` boundary as a fork served by the writing region:
119
+
120
+ - `status`, `variables`, and the projected event log up to `fromSeq` MUST be byte-equivalent across regions.
121
+ - Per-region wall-clock and entropy fields in events after the boundary MAY differ.
122
+
123
+ A host that advertises only one of the two keeps the single-region contract above; a host that advertises neither is single-region and the cross-region claim does not apply.
107
124
 
108
125
  ## Retention
109
126
 
110
127
  A host advertising `replay` MUST document retention for source snapshots, source logs, the invocation records replay depends on, and forked runs; `retention.days` MAY advertise the window. When the range `fromSeq` needs has expired, the host MUST reject the fork with `410` or `422`; `details` SHOULD carry `sourceRunId`, `fromSeq`, and the boundary.
111
128
 
112
- See also: events.md, runs.md, persistence.md, security-defaults.md.
113
-
114
129
  ## Declared nondeterminism
115
130
 
116
- `nondeterminismPolicy` is the host's statement of which nondeterministic sources it
117
- declares rather than suppresses. A host advertising `nondeterminismPolicy.declared`
118
- MUST record every declared source in the run's event log at the point it is read, so
119
- a fork replays the recorded value rather than re-drawing it; a source the host neither
120
- declares nor suppresses is a replay defect, not a policy choice.
131
+ `nondeterminismPolicy` is the host's statement of which nondeterministic sources it declares rather than suppresses. A host advertising `nondeterminismPolicy.declared` MUST record every declared source in the run's event log at the point it is read, so a fork replays the recorded value rather than re-drawing it. A source the host neither declares nor suppresses is a replay defect, not a policy choice.
132
+
133
+ See also: events.md, runs.md, persistence.md, security-defaults.md.
121
134
 
135
+ *Sources: RFC 0036, RFC 0039, RFC 0041, RFC 0057, RFC 0104, RFC 0111, RFC 0140, RFC 0173, RFC 0176, RFC 0194.*