@openvtc/trust-tasks 0.16.8 → 0.16.10

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 (206) hide show
  1. package/CHANGELOG.md +285 -0
  2. package/dist/_shared/components.d.ts +206 -7
  3. package/dist/_shared/components.d.ts.map +1 -1
  4. package/dist/device/list/0.1/payload.d.ts +4 -4
  5. package/dist/device/list/0.1/payload.d.ts.map +1 -1
  6. package/dist/device/list/0.1/payload.js +6 -2
  7. package/dist/device/list/0.1/payload.js.map +1 -1
  8. package/dist/device/list/0.2/payload.d.ts +4 -4
  9. package/dist/device/list/0.2/payload.d.ts.map +1 -1
  10. package/dist/device/list/0.2/payload.js +6 -2
  11. package/dist/device/list/0.2/payload.js.map +1 -1
  12. package/dist/device/register/0.1/payload.d.ts +4 -4
  13. package/dist/device/register/0.1/payload.d.ts.map +1 -1
  14. package/dist/device/register/0.1/payload.js +6 -2
  15. package/dist/device/register/0.1/payload.js.map +1 -1
  16. package/dist/device/register/0.2/payload.d.ts +4 -4
  17. package/dist/device/register/0.2/payload.d.ts.map +1 -1
  18. package/dist/device/register/0.2/payload.js +6 -2
  19. package/dist/device/register/0.2/payload.js.map +1 -1
  20. package/dist/index.d.ts +31 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +31 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/persona/_shared/0.1/persona-record.d.ts +22 -0
  25. package/dist/persona/_shared/0.1/persona-record.d.ts.map +1 -0
  26. package/dist/persona/_shared/0.1/persona-record.js +6 -0
  27. package/dist/persona/_shared/0.1/persona-record.js.map +1 -0
  28. package/dist/persona/attribute/delete/1.0/payload.d.ts +351 -0
  29. package/dist/persona/attribute/delete/1.0/payload.d.ts.map +1 -0
  30. package/dist/persona/attribute/delete/1.0/payload.js +188 -0
  31. package/dist/persona/attribute/delete/1.0/payload.js.map +1 -0
  32. package/dist/persona/attribute/list/1.0/payload.d.ts +895 -0
  33. package/dist/persona/attribute/list/1.0/payload.d.ts.map +1 -0
  34. package/dist/persona/attribute/list/1.0/payload.js +532 -0
  35. package/dist/persona/attribute/list/1.0/payload.js.map +1 -0
  36. package/dist/persona/attribute/put/1.0/payload.d.ts +829 -0
  37. package/dist/persona/attribute/put/1.0/payload.d.ts.map +1 -0
  38. package/dist/persona/attribute/put/1.0/payload.js +487 -0
  39. package/dist/persona/attribute/put/1.0/payload.js.map +1 -0
  40. package/dist/persona/binding/get/1.0/payload.d.ts +382 -0
  41. package/dist/persona/binding/get/1.0/payload.d.ts.map +1 -0
  42. package/dist/persona/binding/get/1.0/payload.js +206 -0
  43. package/dist/persona/binding/get/1.0/payload.js.map +1 -0
  44. package/dist/persona/binding/list/1.0/payload.d.ts +387 -0
  45. package/dist/persona/binding/list/1.0/payload.d.ts.map +1 -0
  46. package/dist/persona/binding/list/1.0/payload.js +210 -0
  47. package/dist/persona/binding/list/1.0/payload.js.map +1 -0
  48. package/dist/persona/binding/set/1.0/payload.d.ts +545 -0
  49. package/dist/persona/binding/set/1.0/payload.d.ts.map +1 -0
  50. package/dist/persona/binding/set/1.0/payload.js +302 -0
  51. package/dist/persona/binding/set/1.0/payload.js.map +1 -0
  52. package/dist/persona/contact/delete/1.0/payload.d.ts +313 -0
  53. package/dist/persona/contact/delete/1.0/payload.d.ts.map +1 -0
  54. package/dist/persona/contact/delete/1.0/payload.js +174 -0
  55. package/dist/persona/contact/delete/1.0/payload.js.map +1 -0
  56. package/dist/persona/contact/get/1.0/payload.d.ts +1027 -0
  57. package/dist/persona/contact/get/1.0/payload.d.ts.map +1 -0
  58. package/dist/persona/contact/get/1.0/payload.js +606 -0
  59. package/dist/persona/contact/get/1.0/payload.js.map +1 -0
  60. package/dist/persona/contact/list/1.0/payload.d.ts +490 -0
  61. package/dist/persona/contact/list/1.0/payload.d.ts.map +1 -0
  62. package/dist/persona/contact/list/1.0/payload.js +262 -0
  63. package/dist/persona/contact/list/1.0/payload.js.map +1 -0
  64. package/dist/persona/contact/put/1.0/payload.d.ts +885 -0
  65. package/dist/persona/contact/put/1.0/payload.d.ts.map +1 -0
  66. package/dist/persona/contact/put/1.0/payload.js +518 -0
  67. package/dist/persona/contact/put/1.0/payload.js.map +1 -0
  68. package/dist/persona/correlation/analyze/1.0/payload.d.ts +648 -0
  69. package/dist/persona/correlation/analyze/1.0/payload.d.ts.map +1 -0
  70. package/dist/persona/correlation/analyze/1.0/payload.js +338 -0
  71. package/dist/persona/correlation/analyze/1.0/payload.js.map +1 -0
  72. package/dist/persona/disclosure/history/1.0/payload.d.ts +639 -0
  73. package/dist/persona/disclosure/history/1.0/payload.d.ts.map +1 -0
  74. package/dist/persona/disclosure/history/1.0/payload.js +339 -0
  75. package/dist/persona/disclosure/history/1.0/payload.js.map +1 -0
  76. package/dist/persona/disclosure/present/1.0/payload.d.ts +400 -0
  77. package/dist/persona/disclosure/present/1.0/payload.d.ts.map +1 -0
  78. package/dist/persona/disclosure/present/1.0/payload.js +211 -0
  79. package/dist/persona/disclosure/present/1.0/payload.js.map +1 -0
  80. package/dist/persona/disclosure/preview/1.0/payload.d.ts +858 -0
  81. package/dist/persona/disclosure/preview/1.0/payload.d.ts.map +1 -0
  82. package/dist/persona/disclosure/preview/1.0/payload.js +480 -0
  83. package/dist/persona/disclosure/preview/1.0/payload.js.map +1 -0
  84. package/dist/persona/local/binding/set/1.0/payload.d.ts +394 -0
  85. package/dist/persona/local/binding/set/1.0/payload.d.ts.map +1 -0
  86. package/dist/persona/local/binding/set/1.0/payload.js +226 -0
  87. package/dist/persona/local/binding/set/1.0/payload.js.map +1 -0
  88. package/dist/persona/local/profile/delete/1.0/payload.d.ts +311 -0
  89. package/dist/persona/local/profile/delete/1.0/payload.d.ts.map +1 -0
  90. package/dist/persona/local/profile/delete/1.0/payload.js +173 -0
  91. package/dist/persona/local/profile/delete/1.0/payload.js.map +1 -0
  92. package/dist/persona/local/profile/get/1.0/payload.d.ts +368 -0
  93. package/dist/persona/local/profile/get/1.0/payload.d.ts.map +1 -0
  94. package/dist/persona/local/profile/get/1.0/payload.js +210 -0
  95. package/dist/persona/local/profile/get/1.0/payload.js.map +1 -0
  96. package/dist/persona/local/profile/list/1.0/payload.d.ts +387 -0
  97. package/dist/persona/local/profile/list/1.0/payload.d.ts.map +1 -0
  98. package/dist/persona/local/profile/list/1.0/payload.js +214 -0
  99. package/dist/persona/local/profile/list/1.0/payload.js.map +1 -0
  100. package/dist/persona/local/profile/put/1.0/payload.d.ts +545 -0
  101. package/dist/persona/local/profile/put/1.0/payload.d.ts.map +1 -0
  102. package/dist/persona/local/profile/put/1.0/payload.js +315 -0
  103. package/dist/persona/local/profile/put/1.0/payload.js.map +1 -0
  104. package/dist/persona/profile/delete/1.0/payload.d.ts +350 -0
  105. package/dist/persona/profile/delete/1.0/payload.d.ts.map +1 -0
  106. package/dist/persona/profile/delete/1.0/payload.js +189 -0
  107. package/dist/persona/profile/delete/1.0/payload.js.map +1 -0
  108. package/dist/persona/profile/get/1.0/payload.d.ts +1307 -0
  109. package/dist/persona/profile/get/1.0/payload.d.ts.map +1 -0
  110. package/dist/persona/profile/get/1.0/payload.js +804 -0
  111. package/dist/persona/profile/get/1.0/payload.js.map +1 -0
  112. package/dist/persona/profile/list/1.0/payload.d.ts +1120 -0
  113. package/dist/persona/profile/list/1.0/payload.d.ts.map +1 -0
  114. package/dist/persona/profile/list/1.0/payload.js +680 -0
  115. package/dist/persona/profile/list/1.0/payload.js.map +1 -0
  116. package/dist/persona/profile/put/1.0/payload.d.ts +1115 -0
  117. package/dist/persona/profile/put/1.0/payload.d.ts.map +1 -0
  118. package/dist/persona/profile/put/1.0/payload.js +675 -0
  119. package/dist/persona/profile/put/1.0/payload.js.map +1 -0
  120. package/dist/persona/renderers/list/1.0/payload.d.ts +362 -0
  121. package/dist/persona/renderers/list/1.0/payload.d.ts.map +1 -0
  122. package/dist/persona/renderers/list/1.0/payload.js +197 -0
  123. package/dist/persona/renderers/list/1.0/payload.js.map +1 -0
  124. package/dist/rooms/keys/commit/0.1/payload.d.ts +266 -0
  125. package/dist/rooms/keys/commit/0.1/payload.d.ts.map +1 -0
  126. package/dist/rooms/keys/commit/0.1/payload.js +146 -0
  127. package/dist/rooms/keys/commit/0.1/payload.js.map +1 -0
  128. package/dist/rooms/keys/key-package/0.1/payload.d.ts +272 -0
  129. package/dist/rooms/keys/key-package/0.1/payload.d.ts.map +1 -0
  130. package/dist/rooms/keys/key-package/0.1/payload.js +147 -0
  131. package/dist/rooms/keys/key-package/0.1/payload.js.map +1 -0
  132. package/dist/rooms/keys/welcome/0.1/payload.d.ts +276 -0
  133. package/dist/rooms/keys/welcome/0.1/payload.d.ts.map +1 -0
  134. package/dist/rooms/keys/welcome/0.1/payload.js +148 -0
  135. package/dist/rooms/keys/welcome/0.1/payload.js.map +1 -0
  136. package/dist/rooms/owner/claim/0.1/payload.d.ts +391 -0
  137. package/dist/rooms/owner/claim/0.1/payload.d.ts.map +1 -0
  138. package/dist/rooms/owner/claim/0.1/payload.js +214 -0
  139. package/dist/rooms/owner/claim/0.1/payload.js.map +1 -0
  140. package/dist/rooms/owner/transfer/0.1/payload.d.ts +391 -0
  141. package/dist/rooms/owner/transfer/0.1/payload.d.ts.map +1 -0
  142. package/dist/rooms/owner/transfer/0.1/payload.js +214 -0
  143. package/dist/rooms/owner/transfer/0.1/payload.js.map +1 -0
  144. package/dist/rooms/records/curate/0.1/payload.d.ts +500 -0
  145. package/dist/rooms/records/curate/0.1/payload.d.ts.map +1 -0
  146. package/dist/rooms/records/curate/0.1/payload.js +277 -0
  147. package/dist/rooms/records/curate/0.1/payload.js.map +1 -0
  148. package/dist/vta/_shared/0.1/app-state-record.d.ts +1 -1
  149. package/dist/vta/_shared/0.1/app-state-record.d.ts.map +1 -1
  150. package/dist/vta/app-state/delete/1.0/payload.d.ts +1 -1
  151. package/dist/vta/app-state/delete/1.0/payload.d.ts.map +1 -1
  152. package/dist/vta/app-state/get/1.0/payload.d.ts +1 -1
  153. package/dist/vta/app-state/get/1.0/payload.d.ts.map +1 -1
  154. package/dist/vta/app-state/get-many/1.0/payload.d.ts +1 -1
  155. package/dist/vta/app-state/get-many/1.0/payload.d.ts.map +1 -1
  156. package/dist/vta/app-state/list/1.0/payload.d.ts +1 -1
  157. package/dist/vta/app-state/list/1.0/payload.d.ts.map +1 -1
  158. package/dist/vta/app-state/put/1.0/payload.d.ts +1 -1
  159. package/dist/vta/app-state/put/1.0/payload.d.ts.map +1 -1
  160. package/dist/vta/app-state/put-many/1.0/payload.d.ts +1 -1
  161. package/dist/vta/app-state/put-many/1.0/payload.d.ts.map +1 -1
  162. package/package.json +1 -1
  163. package/src/_shared/components.ts +217 -7
  164. package/src/device/list/0.1/payload.ts +6 -2
  165. package/src/device/list/0.2/payload.ts +6 -2
  166. package/src/device/register/0.1/payload.ts +6 -2
  167. package/src/device/register/0.2/payload.ts +6 -2
  168. package/src/index.ts +31 -0
  169. package/src/persona/_shared/0.1/persona-record.ts +23 -0
  170. package/src/persona/attribute/delete/1.0/payload.ts +238 -0
  171. package/src/persona/attribute/list/1.0/payload.ts +590 -0
  172. package/src/persona/attribute/put/1.0/payload.ts +559 -0
  173. package/src/persona/binding/get/1.0/payload.ts +257 -0
  174. package/src/persona/binding/list/1.0/payload.ts +260 -0
  175. package/src/persona/binding/set/1.0/payload.ts +370 -0
  176. package/src/persona/contact/delete/1.0/payload.ts +216 -0
  177. package/src/persona/contact/get/1.0/payload.ts +662 -0
  178. package/src/persona/contact/list/1.0/payload.ts +323 -0
  179. package/src/persona/contact/put/1.0/payload.ts +580 -0
  180. package/src/persona/correlation/analyze/1.0/payload.ts +466 -0
  181. package/src/persona/disclosure/history/1.0/payload.ts +415 -0
  182. package/src/persona/disclosure/present/1.0/payload.ts +274 -0
  183. package/src/persona/disclosure/preview/1.0/payload.ts +597 -0
  184. package/src/persona/local/binding/set/1.0/payload.ts +267 -0
  185. package/src/persona/local/profile/delete/1.0/payload.ts +215 -0
  186. package/src/persona/local/profile/get/1.0/payload.ts +251 -0
  187. package/src/persona/local/profile/list/1.0/payload.ts +260 -0
  188. package/src/persona/local/profile/put/1.0/payload.ts +373 -0
  189. package/src/persona/profile/delete/1.0/payload.ts +236 -0
  190. package/src/persona/profile/get/1.0/payload.ts +846 -0
  191. package/src/persona/profile/list/1.0/payload.ts +723 -0
  192. package/src/persona/profile/put/1.0/payload.ts +741 -0
  193. package/src/persona/renderers/list/1.0/payload.ts +248 -0
  194. package/src/rooms/keys/commit/0.1/payload.ts +195 -0
  195. package/src/rooms/keys/key-package/0.1/payload.ts +196 -0
  196. package/src/rooms/keys/welcome/0.1/payload.ts +201 -0
  197. package/src/rooms/owner/claim/0.1/payload.ts +268 -0
  198. package/src/rooms/owner/transfer/0.1/payload.ts +268 -0
  199. package/src/rooms/records/curate/0.1/payload.ts +357 -0
  200. package/src/vta/_shared/0.1/app-state-record.ts +1 -1
  201. package/src/vta/app-state/delete/1.0/payload.ts +1 -1
  202. package/src/vta/app-state/get/1.0/payload.ts +1 -1
  203. package/src/vta/app-state/get-many/1.0/payload.ts +1 -1
  204. package/src/vta/app-state/list/1.0/payload.ts +1 -1
  205. package/src/vta/app-state/put/1.0/payload.ts +1 -1
  206. package/src/vta/app-state/put-many/1.0/payload.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -11,6 +11,291 @@ The package versions over **its own API** — what a consumer compiles against
11
11
  not over `SPEC.md`. Below 1.0 a breaking change bumps the leading non-zero
12
12
  component.
13
13
 
14
+ ## 0.16.10 — 2026-09-05
15
+
16
+
17
+ ### Added
18
+
19
+ - **persona**: A holder's own identity, composed once and disclosed under control (#360)
20
+
21
+ * feat(persona): attribute pool — shared schema and attribute/put
22
+
23
+ First slice of the persona family: the holder's own identity attributes,
24
+ agent-scoped above the context boundary.
25
+
26
+ The shared schema carries the family's load-bearing definitions — the three
27
+ provenance kinds, the four profile-entry forms, the proof-rung ordering, and
28
+ the two-scope rule that keeps a context-scoped caller from reading the pool.
29
+
30
+ attribute/put establishes the pattern: holder-authorized and unscoped,
31
+ value validated against its declared type, credential-backed provenance
32
+ resolved at write time so an attribute cannot read back stale forever, and an
33
+ advisory correlation count returned on the write so a builder can warn while
34
+ the holder is still composing.
35
+
36
+ - **rooms/owner**: Transfer and claim, so a room outlives one person (#359)
37
+
38
+ §10 of the data-rooms design was the last part with no specification.
39
+ Ownership there is load-bearing for liveness, not just administration: a
40
+ room's owner is its sole committer, so a room with no reachable owner
41
+ cannot advance an epoch, cannot be renewed, and lapses to read-only. One
42
+ person becoming unreachable ends a shared space.
43
+
44
+ Two tasks, not one with a flag. They end the same way and differ in
45
+ everything else - who initiates, what authorizes, and whether the room
46
+ has to have lapsed. Collapsing them would mean either a transfer that
47
+ waits for a lapse or a claim that works while the owner is still
48
+ renewing, and both are wrong.
49
+
50
+ There is deliberately no nominate task. A nomination is a credential the
51
+ room issues and the claimant presents, the same shape as an invitation
52
+ and for the same reason: a host keeping a roster of successors would hold
53
+ part of the room's authority structure, and the room could no longer move
54
+ hosts without rebuilding it. The consequence is that 'no successor' and
55
+ 'the successor has not claimed yet' are the same observation to a host -
56
+ which is fine, because both resolve identically when retention runs out.
57
+
58
+ A claim is an ACT, never an automatic promotion, and that is not
59
+ ceremony. An automatic one is an ownership change nobody performed: no
60
+ actor to audit, no moment to point at, and an owner who returns finds the
61
+ room changed hands with no event to examine.
62
+
63
+ Three conditions, all required, each closing a different route to a
64
+ takeover: a nomination the room itself issued naming this claimant; the
65
+ room dormant rather than merely lapsed, so the grace window has passed
66
+ and the owner has had their notice; and the claimant already a member,
67
+ because a successor who cannot commit inherits a room they cannot renew.
68
+
69
+ Renewing cancels a pending claim, and that is the property worth
70
+ noticing: the defence against a hostile claim is the same act as ordinary
71
+ use. An owner who was merely away fixes it by doing what they would have
72
+ done anyway - nothing revoked, no dispute raised.
73
+
74
+ Transfer requires the incoming owner to be a member for the same reason.
75
+ Handing someone a room they cannot renew looks like success and produces
76
+ a room that lapses on schedule a year later with nobody able to save it.
77
+
78
+ Three things the Security sections say plainly. A transfer does NOT
79
+ remove the outgoing owner - they keep whatever their credentials confer,
80
+ usually still admin, so handing over and leaving are two more acts. Every
81
+ credential stays valid because the room's identifier does not change,
82
+ which is the whole reason a room has an identifier of its own rather than
83
+ borrowing its owner's. And the host is not an arbiter: it records what an
84
+ authorized party told it, and ownership of an identifier is settled by
85
+ whoever controls that identifier, not by a service that stores
86
+ ciphertext.
87
+
88
+
89
+
90
+ ### Documentation
91
+
92
+ - Base every example on the registered acl/change-role task (#356)
93
+
94
+ SPEC.md, both READMEs, the loopback example and the framework unit tests
95
+ all illustrated themselves with `kyc-handoff/1.0`, a slug that has never
96
+ been registered. Three consequences, in increasing order of cost:
97
+
98
+ - `https://trusttasks.org/spec/kyc-handoff/1.0` is a live URL in the
99
+ Abstract and in five worked examples. It resolves — the registry site
100
+ serves the SPA for any /spec/ path — and rendered `acl/change-role`,
101
+ the alphabetically first entry, because SpecPage fell back to
102
+ `TT_TASKS[0]` for an unresolvable slug. A reader following the URL in
103
+ §1 got a complete, confident page for a different task.
104
+ - No example could be checked against anything. The build validates every
105
+ fenced JSON block against the framework envelope schema; a payload
106
+ naming a slug with no schema was structurally unverifiable.
107
+ - The `trust-task-error` extended-code example was non-conforming: it
108
+ answered an ACL request with a `kyc-handoff:`-namespaced code, which
109
+ §8.5 forbids in the same breath as it defines namespacing.
110
+
111
+ Everything now names `acl/change-role/0.1`, whose payloads validate
112
+ against its published schema, whose error codes are the two it declares,
113
+ and whose REQUIRED `proof` the examples honour. Appendix A is reproduced
114
+ from that registry entry rather than invented. The framework parser tests
115
+ that used the slug as an arbitrary string now use real ones —
116
+ `trust-task-discovery` and `trust-task-control` where a single-segment
117
+ slug is the point.
118
+
119
+ SpecPage is strict as a result: an unregistered slug, or a known slug at
120
+ an unknown version, renders a not-found page offering the versions that
121
+ do exist, the way BindingSpecPage and CeremonyPage already did. The
122
+ lookup moved into a hook-free wrapper so a route that stops resolving
123
+ unmounts the detail view rather than changing a mounted component's hook
124
+ count.
125
+
126
+ SPEC.md is also maintained canonically at trustoverip/dtgwg-trust-tasks-spec;
127
+ the matching change lands there in parallel.
128
+
129
+
130
+
131
+ ### Fixed
132
+
133
+ - **rooms/owner**: A host cannot see the MLS group, so stop implying it can (#361)
134
+
135
+ Both tasks required a host to check that a party is a member of the
136
+ room's group. A host holds no roster and no group state, so neither check
137
+ was implementable as written. Found while starting the implementation,
138
+ which is what implementation is for.
139
+
140
+ claim now carries a presentation. The host has exactly one membership
141
+ signal available - the VMC the room itself issued - and the claim payload
142
+ did not ask for it, so condition 3 was unimplementable and the claimant's
143
+ own standing went unverified. It is now required, and it is the same
144
+ presentation every other room task carries.
145
+
146
+ The spec is also exact about what that proxy is worth: a party removed
147
+ from the MLS group while still holding an unexpired VMC would pass. That
148
+ gap closes by revoking the credential, which is the room's job, not the
149
+ host's. Better said than implied.
150
+
151
+ transfer moves the obligation to the party who can discharge it. The
152
+ incoming owner is not the one making the request, presents nothing, and
153
+ may be someone the host has never seen - so 'MUST refuse if not a member'
154
+ asked a host to judge a third party from no evidence. The requirement is
155
+ now on the transferring owner, who can see the group; a host that can
156
+ independently establish non-membership MAY still refuse, and one that
157
+ cannot MUST NOT invent a check or treat its own ignorance as evidence.
158
+
159
+ That last point is the same boundary the rest of the family draws,
160
+ arriving from an unfamiliar direction: a host verifies what is presented
161
+ to it, and a claim about a third party is not that.
162
+
163
+ ## 0.16.9 — 2026-09-04
164
+
165
+
166
+ ### Added
167
+
168
+ - **rooms/keys**: How a group reaches a key-holding agent (#355)
169
+
170
+ rooms/keys/open assumes something nothing specified: that a key-holding
171
+ agent HAS the room's MLS group. An oracle that opens records cannot open
172
+ anything until a group arrives, and nothing said how one does. Three
173
+ tasks, because there are three distinct steps with three distinct
174
+ authorization stories.
175
+
176
+ key-package - the joining side mints. Per room, never reused across
177
+ rooms: a KeyPackage is a stable public identifier, so the same one
178
+ offered to two rooms tells anyone who sees both that one party is in
179
+ both, which is the linkage a private room exists to deny arriving
180
+ through the door rather than the wall. The mint is not free either - the
181
+ recipient retains the private half against a Welcome that may never
182
+ come - so a key-holder should require an invitation and bound how long
183
+ it keeps an unused one.
184
+
185
+ welcome - the owner delivers, and the INVITATION is what makes it
186
+ acceptable. A Welcome carries a group's secrets; anyone able to reach a
187
+ key-holder could otherwise push group state into it. Joining a room is
188
+ already a two-party act and the VIC is already the consent artefact, so
189
+ this is where that consent stops being ceremonial. A recipient with no
190
+ matching unconsumed invitation MUST refuse, and MUST consume it on
191
+ success. Joining twice is refused rather than merged: two group states
192
+ for one room is a condition nothing downstream can resolve, and choosing
193
+ wrong returns 'did not open' for a record the member can plainly see.
194
+
195
+ commit - the half that is easy to forget and impossible to omit. A
196
+ Welcome gets an agent in once; commits keep it there, and an agent that
197
+ misses one is stuck at its last epoch and can open nothing sealed after
198
+ it - surfacing as 'this record does not open', which reads like
199
+ corruption rather than a missed message. Strictly in order: a replay is
200
+ success with the epoch unchanged (a retry that failed would make every
201
+ unreliable transport a liveness problem), a gap is refused with the
202
+ recipient's actual epoch so the sender resumes rather than guesses. The
203
+ epoch is in the payload rather than parsed out of the commit so a
204
+ recipient can tell replay from gap before doing cryptographic work on a
205
+ message it may not want.
206
+
207
+ Two things stated rather than left to be discovered.
208
+
209
+ Fan-out is O(n). MLS's logarithmic property is the SIZE of a commit, not
210
+ the number of recipients. On open and attributed a host may carry them;
211
+ on private it must not, and the owner fans out directly - so on that tier
212
+ membership changes need the owner online. That is a real cost of the
213
+ private tier and it belongs in the specification.
214
+
215
+ Routing a Welcome discloses membership. Whoever carries it learns that
216
+ this key-holder is joining this room, which is exactly what a private
217
+ room withholds - so on that tier the host is off the path entirely
218
+ rather than trusted not to look. Same rule, same reason, as the
219
+ invitation itself.
220
+
221
+ Who may commit is decided inside the group: a recipient verifies against
222
+ the group state it already holds, never from an access-control list of
223
+ its own. And none of this is authorization - holding a group's keys lets
224
+ an agent decrypt; what it may DO comes from the room's authority
225
+ credentials, checked separately.
226
+
227
+ - **rooms/records/curate**: A member changes a record's standing (#354)
228
+
229
+ A data room could be written to and read from, and a member who had put
230
+ something in it by mistake had no answer. The rooms family had no way to
231
+ demote, retract, restore or pin a record.
232
+
233
+ Separate from rooms/records/put for two reasons, and the second is the
234
+ load-bearing one.
235
+
236
+ A record's standing is not its content. On an attributed or private room
237
+ a host cannot read what it stores, so 'replace this record with the same
238
+ content marked deprecated' would make the member re-seal and re-upload a
239
+ body the host already holds, for a change that says nothing about the
240
+ body. Curation carries no content in either direction.
241
+
242
+ And curate is its own authority action, deliberately not implied by
243
+ write: deciding what a room's shared knowledge is worth is a different
244
+ grant from the ability to add to it. A community can hand an agent write
245
+ - let it record what it learns - without handing it the standing to
246
+ demote what a person wrote.
247
+
248
+ Retraction is a tombstone, not an erasure. A host MUST drop the body and
249
+ MUST keep the key, version and epoch: dropping the body is what the
250
+ member asked for, and keeping the rest is what makes incremental sync
251
+ converge - a caller that never saw the tombstone resurrects the record on
252
+ its next full rebuild, which is why list returns them. active is refused
253
+ for a retracted record rather than reporting a success that restored
254
+ nothing.
255
+
256
+ Permanent removal is out of scope on purpose. It breaks convergence for
257
+ every caller that has not synchronised past the tombstone, so it belongs
258
+ to a host's retention lifecycle and not to a member's curation verb.
259
+
260
+ Curation assigns a new version. A demotion others are expected to
261
+ converge on is a change like any other, and one that left the version
262
+ alone would be invisible to every sinceVersion watermark in the room.
263
+
264
+ pinned is orthogonal to status - 'what matters here' and 'is this still
265
+ current' are different questions, and a room may well want its superseded
266
+ canonical decision kept in view.
267
+
268
+ The Security section says plainly that a retraction is not a deletion and
269
+ that a surface presenting it as erasure is making a promise the protocol
270
+ does not keep; and that curate is a censorship surface, which is exactly
271
+ why it should not arrive as a side effect of being able to write.
272
+
273
+
274
+
275
+ ### Specifications
276
+
277
+ - **device**: Register roomPresent and roomOpen capabilities (#351)
278
+
279
+ The rooms/keys oracle pair (#349) says an implementation checks whatever
280
+ authorization the principal granted, and leaves the shape of that grant
281
+ to the implementation. Where it is a device capability, there should be a
282
+ registered value rather than a private one per implementation - that is
283
+ what the shared Capability enum is for, and both values are additive, so
284
+ a consumer that does not recognise one ignores it.
285
+
286
+ Two values, not one. Producing a presentation and decrypting a record are
287
+ different powers: an agent that indexes a room should not thereby be able
288
+ to read it.
289
+
290
+ Both are separate from sign. An agent that may ask for a scoped,
291
+ audience-bound presentation is not thereby an agent that may sign
292
+ anything at all with its principal's key - and gating either task on the
293
+ generic signing oracle would grant strictly more than it needs, which is
294
+ the opposite of what an oracle is for.
295
+
296
+ Each spec's Consent/purpose section now names its value, since that is
297
+ where an implementer reading the task will look.
298
+
14
299
  ## 0.16.8
15
300
 
16
301
  ### Added
@@ -22,11 +22,19 @@ export type AuditAction = "setAcl" | "accessListAdd" | "accessListRemove" | "acc
22
22
  /**
23
23
  * Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.
24
24
  */
25
- export type Capability_DeviceV0_1 = "vault-read" | "vault-write" | "proxy-login" | "fill-release" | "policy-admin" | "device-admin" | "sign" | "key-mint" | "sign-trust-task" | "credential-write" | "memory-read" | "memory-write";
25
+ export type Capability_DeviceV0_1 = "vault-read" | "vault-write" | "proxy-login" | "fill-release" | "policy-admin" | "device-admin" | "sign" | "key-mint" | "sign-trust-task" | "credential-write" | "memory-read" | "memory-write" | "room-present" | "room-open";
26
26
  /**
27
27
  * Fine-grained capability flag scoped to the device's allowed contexts. See SPEC.md for the full semantics of each. Capability values are additive: a consumer MUST ignore a value it does not recognise rather than reject the binding, and MUST NOT treat an unrecognised value as conferring anything.
28
28
  */
29
- export type Capability_DeviceV0_2 = "vaultRead" | "vaultWrite" | "proxyLogin" | "fillRelease" | "policyAdmin" | "deviceAdmin" | "sign" | "keyMint" | "signTrustTask" | "credentialWrite" | "memoryRead" | "memoryWrite";
29
+ export type Capability_DeviceV0_2 = "vaultRead" | "vaultWrite" | "proxyLogin" | "fillRelease" | "policyAdmin" | "deviceAdmin" | "sign" | "keyMint" | "signTrustTask" | "credentialWrite" | "memoryRead" | "memoryWrite" | "roomPresent" | "roomOpen";
30
+ /**
31
+ * The vocabulary token naming what a value IS — `name.legal`, `phone.mobile`, `address.postal`, `person.birthDate`. Dotted, most-general segment first, so that a consumer with no knowledge of the specific token can still group by its prefix.
32
+ *
33
+ * The token is the maintainer's own; no external vocabulary is primary. External vocabularies (vCard/jCard, OIDC standard claims, schema.org) are mappings applied at PRESENTATION by a renderer, not at rest, so that a query written in any of them can be matched without the store having to live inside any one of them.
34
+ *
35
+ * The `x:` prefix is an open extension namespace and is not decoration. The closest prior art — Windows CardSpace's self-issued card — supported exactly fifteen predefined claim types with no extensibility, and that is the specific way it failed the requirement a holder actually has. An `x:` attribute stores, composes, binds and discloses exactly like a known one; it renders generically and matches only an explicit query.
36
+ */
37
+ export type ClaimType = string;
30
38
  /**
31
39
  * Discriminator: is this consumer a user-driven Companion or a headless Service?
32
40
  */
@@ -65,10 +73,14 @@ export type DigestMultibase = string;
65
73
  * Whether the subject is permitted. The ABSENCE of any grant is treated as `deny` (default-deny).
66
74
  */
67
75
  export type Effect_ConsentV0_1 = "allow" | "deny";
76
+ /**
77
+ * Optimistic-concurrency precondition. A positive value requires the record's current `version` to equal it exactly; zero means create-only and applies only when no live record exists at the address.
78
+ */
79
+ export type ExpectedVersion_PersonaV0_1 = number;
68
80
  /**
69
81
  * Optimistic-concurrency precondition on a write. A positive value requires that the record's current `version` equals it exactly. Zero means "create only" — the write applies only if no LIVE record exists at the address, which is what makes lease acquisition safe: without it two instances can each read "absent", each write, and each believe it won. A tombstone is not a live record, so `expectedVersion: 0` succeeds over one; the created record takes the namespace's next counter value, which is necessarily greater than the tombstone's.
70
82
  */
71
- export type ExpectedVersion = number;
83
+ export type ExpectedVersion_VtaV0_1 = number;
72
84
  /**
73
85
  * `issued` is outstanding and revocable; `consumed` and `expired` are terminal.
74
86
  */
@@ -97,6 +109,88 @@ export type Kind = "dm" | "group" | "channel";
97
109
  * Scopes one application's records within a context, so several tools can share a context without colliding — `openvtc`, `cnm`, an agent runtime. The maintainer MUST NOT interpret the value; it is an opaque partition name. Namespaces are first-come and unreserved, so an application SHOULD pick a stable, specific one: a future per-namespace ACL would grant on this exact string, which makes renaming a namespace a migration rather than an edit.
98
110
  */
99
111
  export type Namespace = string;
112
+ /**
113
+ * One line of a profile, in exactly one of four forms. Together they are the whole of a profile's flexibility, and each exists for a case the others handle badly.
114
+ *
115
+ * `{ref}` — use the pool attribute, live. Editing the pool updates every profile that references it, which is the point.
116
+ *
117
+ * `{ref, pinVersion}` — use the value as it was at that version. For a profile that must keep presenting the value a counterparty already verified.
118
+ *
119
+ * `{ref, override}` — the same fact, a different value here. ("In the gaming profile my display name is different.")
120
+ *
121
+ * `{inline}` — a value that never enters the pool, and so never leaks into another profile.
122
+ *
123
+ * Omission is exclusion; there is no removal marker.
124
+ */
125
+ export type ProfileEntry = {
126
+ ref: Ulid;
127
+ } | {
128
+ ref: Ulid;
129
+ pinVersion: Version_PersonaV0_1;
130
+ } | {
131
+ ref: Ulid;
132
+ /**
133
+ * Replaces the pool attribute's value for this profile only. `type`, `valueType` and `provenance` are inherited from the referenced attribute and MUST NOT be overridden — an override that changed provenance would let a self-asserted value present as attested.
134
+ */
135
+ override: {
136
+ value: unknown;
137
+ label?: string;
138
+ };
139
+ } | {
140
+ inline: {
141
+ type: ClaimType;
142
+ valueType: ValueType;
143
+ value: unknown;
144
+ label?: string;
145
+ provenance: Provenance;
146
+ };
147
+ };
148
+ /**
149
+ * How strongly a credential-backed claim is hidden when presented, ordered most private first. `predicate` proves a statement over a claim without disclosing the claim. `derived` discloses exactly the claims needed via an unlinkable derived proof, so two presentations cannot be joined. `selectiveDisclosure` discloses exactly the claims needed but carries the issuer's signature unchanged, so two presentations ARE linkable. `whole` discloses the entire credential.
150
+ *
151
+ * The distinction between the first two and the last two is of kind, not degree: only `predicate` and `derived` avoid handing two verifiers a join key. A maintainer MUST default to the highest rung the credential's format supports, and MUST NOT silently fall to a lower one — a request that cannot be satisfied at the rung a producer asked for is refused, because a silent privacy downgrade discloses material the holder believed was hidden.
152
+ */
153
+ export type ProofRung = "predicate" | "derived" | "selectiveDisclosure" | "whole";
154
+ /**
155
+ * Where a value comes from, and the member that makes this family worth building on a trust stack rather than in an address book. It survives to the verifier, so a recipient can tell — per field — what the holder typed from what an issuer attested.
156
+ *
157
+ * `selfAsserted` — the holder supplied it.
158
+ *
159
+ * `credentialBacked` — the value is derived from a credential in the vault at `claimPath`. The stored value is a CACHE FOR DISPLAY; the credential is the truth. A maintainer MUST re-derive it on read and MUST fail closed (never presenting a stale value) when the credential has been revoked, has expired, or has been archived or deleted.
160
+ *
161
+ * `generated` — the value is minted per verifier at disclosure time and recorded against that verifier, so every relying party receives a different one that routes back to the holder. This is the shape of the most widely adopted consumer privacy feature in this space; a maintainer need not operate a relay to conform, but the shape must exist, because retrofitting per-verifier values into a pool-of-values model is a migration rather than an addition.
162
+ */
163
+ export type Provenance = {
164
+ kind: "selfAsserted";
165
+ } | {
166
+ kind: "credentialBacked";
167
+ /**
168
+ * Vault identifier of the backing credential.
169
+ */
170
+ credentialId: string;
171
+ /**
172
+ * RFC 6901 JSON Pointer to the claim within the credential, e.g. `/credentialSubject/familyName`.
173
+ */
174
+ claimPath: string;
175
+ /**
176
+ * Issuer of the backing credential. Advisory: a consumer MUST verify the credential rather than trusting this member.
177
+ */
178
+ issuerDid?: string;
179
+ /**
180
+ * The disclosure rung this claim was, or will be, presented at.
181
+ */
182
+ proof?: ProofRung;
183
+ } | {
184
+ kind: "generated";
185
+ /**
186
+ * Names the minting scheme, e.g. `relayEmail`. Maintainer-defined.
187
+ */
188
+ generator: string;
189
+ /**
190
+ * When true (the default and the only useful setting), a distinct value is minted for each verifier.
191
+ */
192
+ perVerifier?: boolean;
193
+ };
100
194
  /**
101
195
  * A device's platform push channel — the body the device registers with its push GATEWAY (push wake-up binding, https://trusttasks.org/binding/push/0.1; modeled on Aries RFC 0699/0734). The gateway holds this token and returns an opaque WakeHandle in exchange; the token is held by the gateway ONLY, never by the mediator or the maintainer/VTA. The gateway uses it to send a contentless wake-up when an authorized trigger asks — the push payload never carries Trust Task content. Tagged union over the discriminator `platform`.
102
196
  */
@@ -167,6 +261,14 @@ export type SiteTarget_VaultV0_1 = WebOrigin_VaultV0_1 | Did_VaultV0_1 | IosApp_
167
261
  export type SiteTarget_VaultV0_2 = WebOrigin_VaultV0_2 | Did_VaultV0_2 | IosApp_VaultV0_2 | AndroidApp_VaultV0_2;
168
262
  export type SyncEvent_SyncV0_1 = VaultUpsertedEvent_SyncV0_1 | VaultDeletedEvent_SyncV0_1 | AclChangedEvent_SyncV0_1 | PolicyChangedEvent_SyncV0_1;
169
263
  export type SyncEvent_SyncV0_2 = VaultUpsertedEvent_SyncV0_2 | VaultDeletedEvent_SyncV0_2 | AclChangedEvent_SyncV0_2 | PolicyChangedEvent_SyncV0_2;
264
+ /**
265
+ * A ULID in Crockford base32, uppercase. Used for `attributeId` and `profileId`. Chosen over a UUID because the leading 48 bits are a timestamp, so a key-ordered scan of the store is also creation-ordered and a `list` needs no secondary sort. Server-assigned on create; a producer MAY supply one to make a create idempotent, and a maintainer MUST reject a supplied value that already exists rather than silently overwriting.
266
+ */
267
+ export type Ulid = string;
268
+ /**
269
+ * The JSON shape of `value`, declared so that a consumer can render and compare without guessing. The maintainer validates that `value` agrees with this member and does nothing further: it does NOT validate a phone number against a phone-number grammar. That is a producer's affordance, and a store that grows opinions about the contents of its records eventually blocks its consumer's release.
270
+ */
271
+ export type ValueType = "string" | "number" | "boolean" | "date" | "object";
170
272
  /**
171
273
  * What the policy decided.
172
274
  *
@@ -175,10 +277,14 @@ export type SyncEvent_SyncV0_2 = VaultUpsertedEvent_SyncV0_2 | VaultDeletedEvent
175
277
  * The four are not reducible to a pending/decided pair. `refer` and `requestMore` are both 'not decided', but they place the next action with different parties: `refer` waits on the community, `requestMore` waits on the applicant. A consumer that cannot tell them apart cannot tell a user whether to wait or to act.
176
278
  */
177
279
  export type VerdictEffect = "allow" | "deny" | "refer" | "requestMore";
280
+ /**
281
+ * A value of the store's monotonic write counter. Server-assigned; a producer never chooses one.
282
+ */
283
+ export type Version_PersonaV0_1 = number;
178
284
  /**
179
285
  * A value of the namespace's monotonic write counter (see this schema's description). Server-assigned; a producer never chooses one.
180
286
  */
181
- export type Version = number;
287
+ export type Version_VtaV0_1 = number;
182
288
  /**
183
289
  * A Verifiable Identifier (SPEC §4.8). For a mediator-served account this is the account's controlling DID, carried verbatim and compared by exact string equality. For privacy — and because some mediators key accounts by a one-way hash and never hold the full DID — a stable hash of the DID (e.g. its SHA-256 digest) is an equally valid value here: producer and consumer simply agree on the same opaque identifier and compare by exact string equality. The field carries whichever form the issuing mediator uses.
184
290
  */
@@ -353,7 +459,7 @@ export interface AppStateRecord {
353
459
  /**
354
460
  * The namespace counter value this record's most recent write took. Supply it as `expectedVersion` on the next write to make that write conditional on nothing having changed in between.
355
461
  */
356
- version: Version;
462
+ version: Version_VtaV0_1;
357
463
  /**
358
464
  * The stored JSON, in whatever shape the owning application chose. Any JSON value, including `null`. The maintainer neither validates nor interprets it. Absent when this is a tombstone or a metadata-only view — see this definition's description for why that is not the same as a null value.
359
465
  */
@@ -450,6 +556,36 @@ export interface AttachmentRef_VaultV0_3 {
450
556
  */
451
557
  contentType?: string;
452
558
  }
559
+ /**
560
+ * One atomic fact a holder keeps about themselves. Several attributes MAY share a `type` — three phone numbers, a legal name and a preferred name — which is why `attributeId` is the identity of a fact and `type` is not. The pool is flat and unordered; ordering is a profile's concern.
561
+ */
562
+ export interface Attribute {
563
+ attributeId: Ulid;
564
+ type: ClaimType;
565
+ valueType: ValueType;
566
+ /**
567
+ * The fact itself, agreeing with `valueType`. Encrypted at rest by the maintainer. Absent when the caller asked for a metadata-only view, and absent when a credential-backed value could not be re-derived — a consumer MUST NOT conflate the two and MUST read `stale` to tell them apart.
568
+ */
569
+ value: {
570
+ [k: string]: unknown | undefined;
571
+ };
572
+ /**
573
+ * The holder's own words for this attribute, shown in a picker. Never disclosed to a verifier; it is a note to self.
574
+ */
575
+ label?: string;
576
+ provenance: Provenance;
577
+ /**
578
+ * Present and true when a `credentialBacked` value could not be re-derived. `staleReason` says why. A maintainer MUST refuse to disclose a stale attribute.
579
+ */
580
+ stale?: boolean;
581
+ /**
582
+ * Why re-derivation failed. Present only alongside `stale`.
583
+ */
584
+ staleReason?: "revoked" | "expired" | "archived" | "deleted" | "notFound";
585
+ version: Version_PersonaV0_1;
586
+ createdAt?: string;
587
+ updatedAt: string;
588
+ }
453
589
  /**
454
590
  * One record in the mediator's privileged-change audit log: one change, by one actor, at one time.
455
591
  */
@@ -908,6 +1044,46 @@ export interface Service_DeviceV0_2 {
908
1044
  kind: "service";
909
1045
  serviceKind: "mediator" | "aiAgent" | "daemon";
910
1046
  }
1047
+ /**
1048
+ * What a peer disclosed, as received. Structurally the same claim set a holder composes and presents — a profile and a contact card are one schema seen from two sides — which is why a maintainer validates both with the same code and a consumer renders both with the same view.
1049
+ */
1050
+ export interface ContactDocument {
1051
+ /**
1052
+ * The DID that published the document, as it appeared. Normally a pairwise identifier, so it names the relationship rather than the person.
1053
+ */
1054
+ publisher?: string;
1055
+ /**
1056
+ * The publisher's own monotonic counter, if they supplied one. Advisory: it orders the publisher's revisions relative to each other and MUST NOT be trusted to order them against anything else.
1057
+ */
1058
+ cardVersion?: number;
1059
+ /**
1060
+ * @maxItems 256
1061
+ */
1062
+ claims: {
1063
+ type: ClaimType;
1064
+ valueType: ValueType;
1065
+ value: unknown;
1066
+ /**
1067
+ * As asserted by the publisher. A recipient MUST NOT treat a claimed credentialBacked provenance as verified — it is a statement about what the publisher says backs the claim, and verification is a separate act against the credential itself.
1068
+ */
1069
+ provenance?: Provenance;
1070
+ }[];
1071
+ }
1072
+ /**
1073
+ * One received version of a contact's document. Revisions exist because an address book that silently replaces a payment address is a phishing surface, and one that says what changed and when is a defence.
1074
+ */
1075
+ export interface ContactRevision {
1076
+ /**
1077
+ * Monotonic per contact, assigned by the recipient. Not the publisher's cardVersion — a recipient counts what it received, which is the only sequence it can vouch for.
1078
+ */
1079
+ rev: number;
1080
+ receivedAt: string;
1081
+ /**
1082
+ * When a later revision replaced this one. Null for the current revision.
1083
+ */
1084
+ supersededAt?: string | null;
1085
+ document?: ContactDocument;
1086
+ }
911
1087
  /**
912
1088
  * Per-context restrictions the VTA enforces. Every member is optional, and **absence means unrestricted, not empty** — a policy that omits `presentableTypes` permits every type, while one that sets it to `[]` permits none. The two are opposite instructions and a consumer MUST NOT collapse them.
913
1089
  */
@@ -2118,6 +2294,29 @@ export interface PolicyModule {
2118
2294
  updatedAt: string;
2119
2295
  ext?: Ext;
2120
2296
  }
2297
+ /**
2298
+ * A named projection over the pool. Agent-scoped, like the pool it draws from. `entries` is ordered and the order is display order.
2299
+ */
2300
+ export interface Profile {
2301
+ profileId: Ulid;
2302
+ /**
2303
+ * The holder's name for this profile — "Work", "Gaming". Not disclosed.
2304
+ */
2305
+ name: string;
2306
+ /**
2307
+ * @maxItems 256
2308
+ */
2309
+ entries: ProfileEntry[];
2310
+ /**
2311
+ * Vault identifiers of credentials associated with this profile as INVENTORY, distinct from the evidence relationship a `credentialBacked` attribute expresses. The two answer different questions — what can this persona prove, versus what backs this specific claim — and a consumer MUST NOT read one as the other.
2312
+ *
2313
+ * @maxItems 256
2314
+ */
2315
+ credentialRefs?: string[];
2316
+ version: Version_PersonaV0_1;
2317
+ createdAt?: string;
2318
+ updatedAt: string;
2319
+ }
2121
2320
  /**
2122
2321
  * Server-issued options for `navigator.credentials.create({ publicKey: ... })`. Mirrors the WebAuthn Level 2 `PublicKeyCredentialCreationOptions` dictionary; binary fields are base64url-encoded strings (rather than ArrayBuffers) so the value is JSON-safe over the wire.
2123
2322
  */
@@ -3166,7 +3365,7 @@ export interface WriteResult {
3166
3365
  /**
3167
3366
  * The new version, on `written`.
3168
3367
  */
3169
- version?: Version;
3368
+ version?: Version_VtaV0_1;
3170
3369
  /**
3171
3370
  * On `written`: true when no live record existed at the address beforehand.
3172
3371
  */
@@ -3174,7 +3373,7 @@ export interface WriteResult {
3174
3373
  /**
3175
3374
  * On `conflict`: the version the maintainer actually holds. Absent when the conflict is that no record exists (`expectedVersion` was positive and the address is empty).
3176
3375
  */
3177
- currentVersion?: Version;
3376
+ currentVersion?: Version_VtaV0_1;
3178
3377
  /**
3179
3378
  * On `conflict`: the value the maintainer actually holds, returned WITH the rejection rather than left for the caller to re-read. A bare rejection has no fixed point under contention — between the rejection and the re-read the record can change again — so returning the winner's view removes the race rather than narrowing it. Absent when `currentDeleted` is true or no record exists.
3180
3379
  */