@oatscenter/trellis 0.100.0-rc.1

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 (264) hide show
  1. package/README.md +59 -0
  2. package/auth/authorization/client_context.d.ts +61 -0
  3. package/auth/authorization/client_context.js +266 -0
  4. package/auth/authorization/nats_registry.d.ts +41 -0
  5. package/auth/authorization/nats_registry.js +283 -0
  6. package/auth/authorization/provider_cache.d.ts +90 -0
  7. package/auth/authorization/provider_cache.js +783 -0
  8. package/auth/authorization/refresh.d.ts +46 -0
  9. package/auth/authorization/refresh.js +246 -0
  10. package/auth/authorization/types.d.ts +207 -0
  11. package/auth/authorization/types.js +60 -0
  12. package/auth/authorization_context.d.ts +11 -0
  13. package/auth/authorization_context.js +10 -0
  14. package/auth/browser/flow_types.d.ts +84 -0
  15. package/auth/browser/flow_types.js +70 -0
  16. package/auth/browser/portal.d.ts +21 -0
  17. package/auth/browser/portal.js +307 -0
  18. package/auth/browser/storage.d.ts +26 -0
  19. package/auth/browser/storage.js +224 -0
  20. package/auth/browser.d.ts +11 -0
  21. package/auth/browser.js +10 -0
  22. package/auth/browser_recovery.d.ts +13 -0
  23. package/auth/browser_recovery.js +38 -0
  24. package/auth/device_activation.d.ts +134 -0
  25. package/auth/device_activation.js +376 -0
  26. package/auth/http_error.d.ts +12 -0
  27. package/auth/http_error.js +32 -0
  28. package/auth/keys.d.ts +5 -0
  29. package/auth/keys.js +63 -0
  30. package/auth/mod.d.ts +16 -0
  31. package/auth/mod.js +16 -0
  32. package/auth/proof.d.ts +31 -0
  33. package/auth/proof.js +95 -0
  34. package/auth/protocol_wasm/trellis_protocol_wasm.d.ts +138 -0
  35. package/auth/protocol_wasm/trellis_protocol_wasm.js +605 -0
  36. package/auth/protocol_wasm/trellis_protocol_wasm_bg.wasm +0 -0
  37. package/auth/protocol_wasm/trellis_protocol_wasm_bytes.d.ts +1 -0
  38. package/auth/protocol_wasm/trellis_protocol_wasm_bytes.js +2 -0
  39. package/auth/protocol_wasm.d.ts +179 -0
  40. package/auth/protocol_wasm.js +133 -0
  41. package/auth/session_auth.d.ts +30 -0
  42. package/auth/session_auth.js +77 -0
  43. package/auth/session_proof.d.ts +53 -0
  44. package/auth/session_proof.js +43 -0
  45. package/auth/time.d.ts +6 -0
  46. package/auth/time.js +7 -0
  47. package/auth/trellis_id.d.ts +1 -0
  48. package/auth/trellis_id.js +5 -0
  49. package/auth/utils.d.ts +10 -0
  50. package/auth/utils.js +69 -0
  51. package/auth.d.ts +1 -0
  52. package/auth.js +1 -0
  53. package/caller.d.ts +111 -0
  54. package/caller.js +85 -0
  55. package/client.d.ts +14 -0
  56. package/client.js +1 -0
  57. package/client_connect.d.ts +143 -0
  58. package/client_connect.js +1097 -0
  59. package/codec.d.ts +13 -0
  60. package/codec.js +192 -0
  61. package/connection.d.ts +107 -0
  62. package/connection.js +396 -0
  63. package/device/activation.d.ts +42 -0
  64. package/device/activation.js +319 -0
  65. package/device.d.ts +80 -0
  66. package/device.js +687 -0
  67. package/env.d.ts +2 -0
  68. package/env.js +10 -0
  69. package/errors/AuthError.d.ts +30 -0
  70. package/errors/AuthError.js +34 -0
  71. package/errors/KVError.d.ts +30 -0
  72. package/errors/KVError.js +36 -0
  73. package/errors/OperationLifecycleError.d.ts +110 -0
  74. package/errors/OperationLifecycleError.js +142 -0
  75. package/errors/RemoteError.d.ts +61 -0
  76. package/errors/RemoteError.js +72 -0
  77. package/errors/SchemaValidationError.d.ts +48 -0
  78. package/errors/SchemaValidationError.js +49 -0
  79. package/errors/StoreError.d.ts +21 -0
  80. package/errors/StoreError.js +31 -0
  81. package/errors/TransferError.d.ts +21 -0
  82. package/errors/TransferError.js +31 -0
  83. package/errors/TransportError.d.ts +29 -0
  84. package/errors/TransportError.js +33 -0
  85. package/errors/TrellisError.d.ts +15 -0
  86. package/errors/TrellisError.js +15 -0
  87. package/errors/ValidationError.d.ts +50 -0
  88. package/errors/ValidationError.js +59 -0
  89. package/errors/index.d.ts +254 -0
  90. package/errors/index.js +238 -0
  91. package/generated.d.ts +4 -0
  92. package/generated.js +3 -0
  93. package/generated_support.d.ts +120 -0
  94. package/generated_support.js +359 -0
  95. package/globals.d.ts +9 -0
  96. package/globals.js +16 -0
  97. package/health_transport.d.ts +18 -0
  98. package/health_transport.js +32 -0
  99. package/helpers.d.ts +11 -0
  100. package/helpers.js +53 -0
  101. package/index.d.ts +2 -0
  102. package/index.js +2 -0
  103. package/internal_sdk/generated/apis/auth/mod.d.ts +805 -0
  104. package/internal_sdk/generated/apis/auth/mod.js +958 -0
  105. package/internal_sdk/generated/apis/core/mod.d.ts +79 -0
  106. package/internal_sdk/generated/apis/core/mod.js +93 -0
  107. package/internal_sdk/generated/apis/events/mod.d.ts +224 -0
  108. package/internal_sdk/generated/apis/events/mod.js +313 -0
  109. package/internal_sdk/generated/apis/health/mod.d.ts +106 -0
  110. package/internal_sdk/generated/apis/health/mod.js +132 -0
  111. package/internal_sdk/generated/apis/index.d.ts +7 -0
  112. package/internal_sdk/generated/apis/index.js +8 -0
  113. package/internal_sdk/generated/apis/jobs/mod.d.ts +176 -0
  114. package/internal_sdk/generated/apis/jobs/mod.js +209 -0
  115. package/internal_sdk/generated/apis/state/mod.d.ts +104 -0
  116. package/internal_sdk/generated/apis/state/mod.js +139 -0
  117. package/internal_sdk/generated/index.d.ts +4 -0
  118. package/internal_sdk/generated/index.js +5 -0
  119. package/internal_sdk/generated/package.json +18 -0
  120. package/internal_sdk/generated/participants/cli/mod.d.ts +402 -0
  121. package/internal_sdk/generated/participants/cli/mod.js +460 -0
  122. package/internal_sdk/generated/participants/cli/types.d.ts +2 -0
  123. package/internal_sdk/generated/participants/cli/types.js +3 -0
  124. package/internal_sdk/generated/participants/console/mod.d.ts +489 -0
  125. package/internal_sdk/generated/participants/console/mod.js +579 -0
  126. package/internal_sdk/generated/participants/console/types.d.ts +2 -0
  127. package/internal_sdk/generated/participants/console/types.js +3 -0
  128. package/internal_sdk/generated/participants/eventsRuntime/mod.d.ts +64 -0
  129. package/internal_sdk/generated/participants/eventsRuntime/mod.js +42 -0
  130. package/internal_sdk/generated/participants/eventsRuntime/types.d.ts +2 -0
  131. package/internal_sdk/generated/participants/eventsRuntime/types.js +3 -0
  132. package/internal_sdk/generated/participants/healthRuntime/mod.d.ts +58 -0
  133. package/internal_sdk/generated/participants/healthRuntime/mod.js +36 -0
  134. package/internal_sdk/generated/participants/healthRuntime/types.d.ts +2 -0
  135. package/internal_sdk/generated/participants/healthRuntime/types.js +3 -0
  136. package/internal_sdk/generated/participants/index.d.ts +8 -0
  137. package/internal_sdk/generated/participants/index.js +9 -0
  138. package/internal_sdk/generated/participants/jobsRuntime/mod.d.ts +64 -0
  139. package/internal_sdk/generated/participants/jobsRuntime/mod.js +42 -0
  140. package/internal_sdk/generated/participants/jobsRuntime/types.d.ts +2 -0
  141. package/internal_sdk/generated/participants/jobsRuntime/types.js +3 -0
  142. package/internal_sdk/generated/participants/platform/mod.d.ts +164 -0
  143. package/internal_sdk/generated/participants/platform/mod.js +149 -0
  144. package/internal_sdk/generated/participants/platform/types.d.ts +7 -0
  145. package/internal_sdk/generated/participants/platform/types.js +5 -0
  146. package/internal_sdk/generated/participants/portal/mod.d.ts +131 -0
  147. package/internal_sdk/generated/participants/portal/mod.js +109 -0
  148. package/internal_sdk/generated/participants/portal/types.d.ts +2 -0
  149. package/internal_sdk/generated/participants/portal/types.js +3 -0
  150. package/internal_sdk/generated/types/_internal/p0.d.ts +13511 -0
  151. package/internal_sdk/generated/types/_internal/p0.js +6105 -0
  152. package/internal_sdk/generated/types/index.d.ts +2951 -0
  153. package/internal_sdk/generated/types/index.js +1477 -0
  154. package/jobs.d.ts +309 -0
  155. package/jobs.js +389 -0
  156. package/kv.d.ts +85 -0
  157. package/kv.js +330 -0
  158. package/operations.d.ts +189 -0
  159. package/operations.js +847 -0
  160. package/package.json +73 -0
  161. package/pagination.d.ts +25 -0
  162. package/pagination.js +117 -0
  163. package/participant.d.ts +6 -0
  164. package/participant.js +6 -0
  165. package/participant_runtime/api.d.ts +131 -0
  166. package/participant_runtime/api.js +42 -0
  167. package/participant_runtime/json.d.ts +16 -0
  168. package/participant_runtime/json.js +193 -0
  169. package/participant_runtime/metadata.d.ts +34 -0
  170. package/participant_runtime/metadata.js +4 -0
  171. package/participant_runtime/participant.d.ts +200 -0
  172. package/participant_runtime/participant.js +330 -0
  173. package/participant_runtime/schemas.d.ts +347 -0
  174. package/participant_runtime/schemas.js +211 -0
  175. package/participant_runtime/surface_names.d.ts +16 -0
  176. package/participant_runtime/surface_names.js +34 -0
  177. package/portable.d.ts +30 -0
  178. package/portable.js +14 -0
  179. package/provider.d.ts +69 -0
  180. package/provider.js +68 -0
  181. package/runtime_transport.d.ts +25 -0
  182. package/runtime_transport.js +48 -0
  183. package/service/mod.d.ts +14 -0
  184. package/service/mod.js +13 -0
  185. package/service/outbox_inbox.d.ts +259 -0
  186. package/service/outbox_inbox.js +908 -0
  187. package/service/runtime/bootstrap.d.ts +56 -0
  188. package/service/runtime/bootstrap.js +226 -0
  189. package/service/runtime/core.d.ts +46 -0
  190. package/service/runtime/core.js +1747 -0
  191. package/service/runtime/health.d.ts +97 -0
  192. package/service/runtime/health.js +153 -0
  193. package/service/runtime/health_schemas.d.ts +23 -0
  194. package/service/runtime/health_schemas.js +20 -0
  195. package/service/runtime/internal_jobs/active-job.d.ts +40 -0
  196. package/service/runtime/internal_jobs/active-job.js +91 -0
  197. package/service/runtime/internal_jobs/bindings.d.ts +40 -0
  198. package/service/runtime/internal_jobs/bindings.js +1 -0
  199. package/service/runtime/internal_jobs/cancellation-registry.d.ts +13 -0
  200. package/service/runtime/internal_jobs/cancellation-registry.js +55 -0
  201. package/service/runtime/internal_jobs/heartbeat.d.ts +33 -0
  202. package/service/runtime/internal_jobs/heartbeat.js +65 -0
  203. package/service/runtime/internal_jobs/job-manager.d.ts +126 -0
  204. package/service/runtime/internal_jobs/job-manager.js +821 -0
  205. package/service/runtime/internal_jobs/key-coordinator.d.ts +280 -0
  206. package/service/runtime/internal_jobs/key-coordinator.js +747 -0
  207. package/service/runtime/internal_jobs/projection.d.ts +3 -0
  208. package/service/runtime/internal_jobs/projection.js +42 -0
  209. package/service/runtime/internal_jobs/runtime-worker.d.ts +126 -0
  210. package/service/runtime/internal_jobs/runtime-worker.js +454 -0
  211. package/service/runtime/internal_jobs/types.d.ts +246 -0
  212. package/service/runtime/internal_jobs/types.js +163 -0
  213. package/service/runtime/logger.d.ts +2 -0
  214. package/service/runtime/logger.js +7 -0
  215. package/service/runtime/runtime.d.ts +16 -0
  216. package/service/runtime/runtime.js +1 -0
  217. package/service/runtime/service.d.ts +372 -0
  218. package/service/runtime/service.js +1896 -0
  219. package/service/runtime/transfer/download.d.ts +27 -0
  220. package/service/runtime/transfer/download.js +1 -0
  221. package/service/runtime/transfer/protocol.d.ts +15 -0
  222. package/service/runtime/transfer/protocol.js +46 -0
  223. package/service/runtime/transfer/queue.d.ts +18 -0
  224. package/service/runtime/transfer/queue.js +136 -0
  225. package/service/runtime/transfer/upload.d.ts +40 -0
  226. package/service/runtime/transfer/upload.js +27 -0
  227. package/service/runtime/transfer.d.ts +62 -0
  228. package/service/runtime/transfer.js +763 -0
  229. package/session.d.ts +862 -0
  230. package/session.js +3407 -0
  231. package/store.d.ts +71 -0
  232. package/store.js +412 -0
  233. package/store_entry.d.ts +20 -0
  234. package/store_entry.js +75 -0
  235. package/tasks.d.ts +10 -0
  236. package/tasks.js +28 -0
  237. package/telemetry/carrier.d.ts +8 -0
  238. package/telemetry/carrier.js +20 -0
  239. package/telemetry/core.d.ts +7 -0
  240. package/telemetry/core.js +14 -0
  241. package/telemetry/env.d.ts +2 -0
  242. package/telemetry/env.js +10 -0
  243. package/telemetry/init.d.ts +2 -0
  244. package/telemetry/init.js +7 -0
  245. package/telemetry/metrics.d.ts +81 -0
  246. package/telemetry/metrics.js +267 -0
  247. package/telemetry/mod.d.ts +13 -0
  248. package/telemetry/mod.js +8 -0
  249. package/telemetry/nats.d.ts +6 -0
  250. package/telemetry/nats.js +6 -0
  251. package/telemetry/result.d.ts +1 -0
  252. package/telemetry/result.js +18 -0
  253. package/telemetry/runtime.d.ts +1 -0
  254. package/telemetry/runtime.js +141 -0
  255. package/telemetry/trace.d.ts +4 -0
  256. package/telemetry/trace.js +26 -0
  257. package/telemetry/trellis.d.ts +1 -0
  258. package/telemetry/trellis.js +1 -0
  259. package/telemetry.d.ts +1 -0
  260. package/telemetry.js +1 -0
  261. package/transfer.d.ts +112 -0
  262. package/transfer.js +525 -0
  263. package/transfer_protocol.d.ts +1 -0
  264. package/transfer_protocol.js +13 -0
@@ -0,0 +1,783 @@
1
+ import { canonicalizeJsonValue } from "../utils.js";
2
+ import { AuthorizationRegistryReader, } from "./nats_registry.js";
3
+ const MAX_CACHED_CONTEXTS = 256;
4
+ /** Internal marker for retryable provider registry or readiness failure. */
5
+ export class AuthorizationProviderUnavailableError extends Error {
6
+ constructor(message, cause) {
7
+ super(message, { cause });
8
+ this.name = "AuthorizationProviderUnavailableError";
9
+ }
10
+ }
11
+ class InvalidIssuerResponseError extends Error {
12
+ }
13
+ /** Connected provider-side authorization verifier. */
14
+ export class AuthorizationProviderCache {
15
+ #registry;
16
+ #cache;
17
+ #now;
18
+ #contexts = new Map();
19
+ #inFlight = new Map();
20
+ #ownIssuer;
21
+ #contextResolves = 0;
22
+ #contextVerifications = 0;
23
+ #revocationRevision = 0;
24
+ #lastUpdateAt = 0;
25
+ #stopped = false;
26
+ #connected = true;
27
+ #started = false;
28
+ #generation = 0;
29
+ #ownEntry;
30
+ #onOwnInvalidated;
31
+ #onOwnResumed;
32
+ #ownRevokedDigest;
33
+ #ownUsable = true;
34
+ #connectedWaiters = new Set();
35
+ constructor(registry, cache, options) {
36
+ this.#registry = registry;
37
+ this.#cache = cache;
38
+ this.#now = options.now ?? cache.correctedNowSeconds.bind(cache);
39
+ this.#ownIssuer = structuredClone(cache.bundle().issuer);
40
+ }
41
+ /** Attach to the bootstrap-selected NATS authorization registry. */
42
+ static async attach(nats, binding, inboxPrefix, cache, options = {}) {
43
+ if (canonicalizeJsonValue(binding) !==
44
+ canonicalizeJsonValue(cache.bundle().authorizationRegistry)) {
45
+ throw new Error("authorization registry binding does not match");
46
+ }
47
+ return new AuthorizationProviderCache(await AuthorizationRegistryReader.open(nats, binding, inboxPrefix), cache, options);
48
+ }
49
+ /** Enable provider verification. */
50
+ start() {
51
+ if (this.#started && !this.#stopped)
52
+ return;
53
+ this.#generation += 1;
54
+ this.#stopped = false;
55
+ this.#started = true;
56
+ }
57
+ /** Stop verification without closing the caller-owned NATS connection. */
58
+ stop() {
59
+ this.#generation += 1;
60
+ this.#stopped = true;
61
+ for (const entry of this.#contexts.values())
62
+ this.#invalidate(entry);
63
+ this.#contexts.clear();
64
+ this.#inFlight.clear();
65
+ if (this.#ownEntry)
66
+ this.#release(this.#ownEntry);
67
+ this.#ownEntry = undefined;
68
+ }
69
+ /** Retain exact revocation coverage for the currently installed own context. */
70
+ async retainOwnContext() {
71
+ const digest = this.#cache.storedContextDigest();
72
+ if (digest === undefined) {
73
+ throw new Error("authorization context is unavailable");
74
+ }
75
+ const generation = this.#generation;
76
+ await this.#retainOwnContext(digest, generation);
77
+ if (!this.#finalizeOwnInstallation("resume", digest, generation)) {
78
+ this.#cache.requestRefresh();
79
+ throw new Error("authorization context coverage changed during resumption");
80
+ }
81
+ }
82
+ /** Retain candidate coverage on the exact admitted connection generation. */
83
+ async retainOwnCandidate(digest, generation) {
84
+ await this.#retainOwnContext(digest, generation);
85
+ }
86
+ /** Promote the candidate only while its admitted coverage remains current. */
87
+ promoteOwnCandidate(digest, generation) {
88
+ if (!this.#finalizeOwnInstallation("promote", digest, generation)) {
89
+ throw new Error("authorization candidate coverage changed before promotion");
90
+ }
91
+ }
92
+ /**
93
+ * One synchronous final own-installation publication boundary.
94
+ *
95
+ * Rechecks the exact retained entry identity, generation, coverage,
96
+ * revocation, current active/candidate digest, and validity immediately
97
+ * before publishing, so invalidation consumed during an awaited retention
98
+ * can never be overwritten by a later continuation.
99
+ */
100
+ #finalizeOwnInstallation(mode, digest, generation) {
101
+ if (!this.#started || this.#stopped || !this.#connected)
102
+ return false;
103
+ if (generation !== this.#generation)
104
+ return false;
105
+ const entry = this.#ownEntry;
106
+ if (!entry || entry.contextDigest !== digest || !entry.covered ||
107
+ entry.disposed || entry.revokedAt !== undefined ||
108
+ entry.generation !== generation || this.#contexts.get(digest) !== entry ||
109
+ this.#ownRevokedDigest === digest) {
110
+ return false;
111
+ }
112
+ let verified;
113
+ if (mode === "promote") {
114
+ if (!this.#cache.hasCandidate() ||
115
+ this.#cache.transportCurrent().contextDigest !== digest)
116
+ return false;
117
+ verified = this.#cache.transportCurrent();
118
+ }
119
+ else {
120
+ if (this.#cache.hasCandidate())
121
+ return false;
122
+ if (this.#cache.storedContextDigest() !== digest)
123
+ return false;
124
+ verified = this.#cache.current();
125
+ }
126
+ const now = this.#now();
127
+ if (verified.context.notBefore > now || verified.context.expiresAt <= now) {
128
+ return false;
129
+ }
130
+ if (mode === "promote") {
131
+ this.#cache.promote(digest);
132
+ }
133
+ this.#ownUsable = true;
134
+ if (mode === "resume")
135
+ this.#onOwnResumed?.();
136
+ return true;
137
+ }
138
+ connectionGeneration() {
139
+ return this.#generation;
140
+ }
141
+ async #retainOwnContext(digest, generation) {
142
+ const entry = await this.#lease(digest, false);
143
+ if (entry.revokedAt !== undefined || !entry.covered ||
144
+ entry.generation !== generation || generation !== this.#generation ||
145
+ !this.#connected) {
146
+ this.#release(entry);
147
+ throw new Error("authorization context coverage changed during installation");
148
+ }
149
+ if (this.#ownEntry)
150
+ this.#release(this.#ownEntry);
151
+ this.#ownEntry = entry;
152
+ }
153
+ /** Returns whether the current own authorization may authenticate transport. */
154
+ ownUsable() {
155
+ return this.#ownUsable;
156
+ }
157
+ /** A verified private candidate or a retained unrevoked installation may authenticate transport. */
158
+ transportUsable() {
159
+ if (!this.#started || this.#stopped)
160
+ return false;
161
+ if (this.#ownUsable)
162
+ return true;
163
+ const digest = this.#cache.hasCandidate()
164
+ ? this.#cache.transportCurrent().contextDigest
165
+ : this.#cache.storedContextDigest();
166
+ return digest !== undefined && digest !== this.#ownRevokedDigest;
167
+ }
168
+ /** Suspend stale transport authentication and request one current context. */
169
+ refreshOwnAuthorization() {
170
+ const revoked = this.#cache.storedContextDigest();
171
+ if (revoked !== undefined)
172
+ this.#ownRevokedDigest = revoked;
173
+ if (this.#ownUsable) {
174
+ this.#ownUsable = false;
175
+ this.#onOwnInvalidated?.();
176
+ }
177
+ this.#cache.requestRefresh();
178
+ }
179
+ /** React to an authoritative NATS authentication rejection during reconnect. */
180
+ observeTransportEvent(event) {
181
+ if (!event || typeof event !== "object")
182
+ return;
183
+ const status = event;
184
+ if (status.type === "error" &&
185
+ String(status.data).toLowerCase().includes("authorization")) {
186
+ this.refreshOwnAuthorization();
187
+ }
188
+ }
189
+ /** Register the connection-owned usability withdrawal for own revocation. */
190
+ onOwnInvalidated(callback) {
191
+ this.#onOwnInvalidated = callback;
192
+ }
193
+ /** Register the connection-owned resumption after same-context coverage initialization. */
194
+ onOwnResumed(callback) {
195
+ this.#onOwnResumed = callback;
196
+ }
197
+ /** Wait until the connected registry is available. */
198
+ waitReady(options = {}) {
199
+ if (!this.#started || this.#stopped) {
200
+ return Promise.reject(new AuthorizationProviderUnavailableError("authorization provider is unavailable"));
201
+ }
202
+ if (options.signal?.aborted)
203
+ return Promise.reject(options.signal.reason);
204
+ if (this.#connected)
205
+ return Promise.resolve();
206
+ return new Promise((resolve, reject) => {
207
+ const ready = () => {
208
+ clearTimeout(timer);
209
+ options.signal?.removeEventListener("abort", aborted);
210
+ resolve();
211
+ };
212
+ const aborted = () => {
213
+ this.#connectedWaiters.delete(ready);
214
+ clearTimeout(timer);
215
+ reject(options.signal?.reason);
216
+ };
217
+ const timer = setTimeout(() => {
218
+ this.#connectedWaiters.delete(ready);
219
+ options.signal?.removeEventListener("abort", aborted);
220
+ reject(new AuthorizationProviderUnavailableError("authorization provider readiness timed out"));
221
+ }, options.timeoutMs ?? 30_000);
222
+ this.#connectedWaiters.add(ready);
223
+ options.signal?.addEventListener("abort", aborted, { once: true });
224
+ });
225
+ }
226
+ /** Return current provider health. */
227
+ health() {
228
+ return {
229
+ revocationRevision: this.#revocationRevision,
230
+ lastUpdateAt: this.#lastUpdateAt,
231
+ healthy: this.#started && !this.#stopped && this.#connected,
232
+ };
233
+ }
234
+ /** Return provider and registry I/O counters. */
235
+ ioCounters() {
236
+ return {
237
+ ...this.#registry.ioCounters(),
238
+ contextResolves: this.#contextResolves,
239
+ contextVerifications: this.#contextVerifications,
240
+ };
241
+ }
242
+ /** Apply framework-level NATS lifecycle state to provider readiness. */
243
+ observeConnectionPhase(phase) {
244
+ if (phase === "error")
245
+ return;
246
+ const wasConnected = this.#connected;
247
+ this.#connected = phase === "connected";
248
+ if (wasConnected && !this.#connected) {
249
+ this.#ownUsable = false;
250
+ this.#onOwnInvalidated?.();
251
+ if (this.#ownEntry)
252
+ this.#release(this.#ownEntry);
253
+ this.#ownEntry = undefined;
254
+ this.#generation += 1;
255
+ for (const entry of this.#contexts.values())
256
+ this.#invalidate(entry);
257
+ this.#contexts.clear();
258
+ this.#inFlight.clear();
259
+ }
260
+ if (!wasConnected && this.#connected) {
261
+ for (const ready of this.#connectedWaiters)
262
+ ready();
263
+ this.#connectedWaiters.clear();
264
+ if (!this.#cache.hasCandidate()) {
265
+ const generation = this.#generation;
266
+ void this.#restoreOwnContext(generation);
267
+ }
268
+ }
269
+ }
270
+ async #restoreOwnContext(generation) {
271
+ while (!this.#stopped && this.#connected && this.#generation === generation) {
272
+ try {
273
+ await this.retainOwnContext();
274
+ return;
275
+ }
276
+ catch {
277
+ await new Promise((resolve) => setTimeout(resolve, 1_000));
278
+ }
279
+ }
280
+ }
281
+ /** Resolve one context digest through the connected registry. */
282
+ async resolveContext(contextDigest) {
283
+ const entry = await this.#lease(contextDigest, false);
284
+ try {
285
+ const state = await this.#verified(entry, false);
286
+ this.#requireEntry(entry);
287
+ const { assertAuthorizationContextHandleCurrentWasm } = await import("../protocol_wasm.js");
288
+ assertAuthorizationContextHandleCurrentWasm(state.handle, this.#policy(this.#now()));
289
+ this.#requireEntry(entry);
290
+ if (entry.revokedAt !== undefined) {
291
+ throw new Error("authorization context is revoked");
292
+ }
293
+ return structuredClone(state.verified);
294
+ }
295
+ finally {
296
+ this.#release(entry);
297
+ }
298
+ }
299
+ /** Verify a presented request proof with exact route permissions. */
300
+ async verifyRequest(request) {
301
+ try {
302
+ const entry = await this.#lease(request.contextDigest, false);
303
+ try {
304
+ this.#requireEntry(entry);
305
+ if (entry.revokedAt !== undefined) {
306
+ return requestFailure("PermissionDenied", "/authorization-context");
307
+ }
308
+ const state = await this.#verified(entry, false);
309
+ this.#requireEntry(entry);
310
+ if (request.sessionKey !== state.verified.context.sessionKey) {
311
+ return requestFailure("InvalidInput", "/session-key");
312
+ }
313
+ const { verifyAuthorizationRequestWasm } = await import("../protocol_wasm.js");
314
+ const result = await verifyAuthorizationRequestWasm({
315
+ contextHandle: state.handle,
316
+ subject: request.subject,
317
+ reply: request.reply,
318
+ payload: request.payload,
319
+ iat: request.iat,
320
+ requestId: request.requestId,
321
+ proof: request.proof,
322
+ requiredPermissions: request.requiredPermissions,
323
+ policy: this.#policy(this.#now()),
324
+ });
325
+ this.#requireEntry(entry);
326
+ if (!result.ok)
327
+ return result;
328
+ if (result.contextDigest !== request.contextDigest ||
329
+ state.verified.contextDigest !== request.contextDigest ||
330
+ entry.revokedAt !== undefined) {
331
+ return requestFailure("PermissionDenied", "/authorization-context");
332
+ }
333
+ return { ...result, context: state.verified.context };
334
+ }
335
+ finally {
336
+ this.#release(entry);
337
+ }
338
+ }
339
+ catch (error) {
340
+ if (error instanceof AuthorizationProviderUnavailableError)
341
+ throw error;
342
+ return requestFailure("InvalidInput", "/authorization-context");
343
+ }
344
+ }
345
+ /** Verify a presented event proof, including historical issuer evidence. */
346
+ async verifyEvent(event) {
347
+ try {
348
+ const entry = await this.#lease(event.contextDigest, true);
349
+ try {
350
+ this.#requireEntry(entry);
351
+ const state = await this.#verified(entry, true);
352
+ this.#requireEntry(entry);
353
+ if (event.sessionKey !== state.verified.context.sessionKey) {
354
+ return eventFailure("InvalidInput", "/session-key");
355
+ }
356
+ const { verifyAuthorizationEventWasm } = await import("../protocol_wasm.js");
357
+ const result = await verifyAuthorizationEventWasm({
358
+ contextHandle: state.handle,
359
+ descriptorIdentity: event.descriptorIdentity,
360
+ subject: event.subject,
361
+ payload: event.payload,
362
+ eventId: event.eventId,
363
+ eventTime: event.eventTime,
364
+ proof: event.proof,
365
+ policy: this.#policy(this.#now()),
366
+ revokedAt: entry.revokedAt ?? null,
367
+ });
368
+ this.#requireEntry(entry);
369
+ if (!result.ok)
370
+ return result;
371
+ if (result.contextDigest !== event.contextDigest ||
372
+ state.verified.contextDigest !== event.contextDigest ||
373
+ entry.revokedAt !== undefined) {
374
+ return eventFailure("EventRevoked", "/authorization-context");
375
+ }
376
+ return { ...result, context: state.verified.context };
377
+ }
378
+ finally {
379
+ this.#release(entry);
380
+ }
381
+ }
382
+ catch (error) {
383
+ if (error instanceof AuthorizationProviderUnavailableError)
384
+ throw error;
385
+ return eventFailure("InvalidInput", "/authorization-context");
386
+ }
387
+ }
388
+ async #entry(contextDigest, historical) {
389
+ assertDigest(contextDigest);
390
+ this.#requireAvailable();
391
+ const existing = this.#contexts.get(contextDigest);
392
+ if (existing) {
393
+ this.#requireEntry(existing);
394
+ this.#contexts.delete(contextDigest);
395
+ this.#contexts.set(contextDigest, existing);
396
+ return existing;
397
+ }
398
+ let pending = this.#inFlight.get(contextDigest);
399
+ if (!pending) {
400
+ this.#makeRoom();
401
+ const generation = this.#generation;
402
+ const owner = Symbol(contextDigest);
403
+ pending = {
404
+ generation,
405
+ owner,
406
+ promise: this.#load(contextDigest, historical, generation, owner),
407
+ };
408
+ this.#inFlight.set(contextDigest, pending);
409
+ }
410
+ try {
411
+ return await pending.promise;
412
+ }
413
+ finally {
414
+ if (this.#inFlight.get(contextDigest) === pending) {
415
+ this.#inFlight.delete(contextDigest);
416
+ }
417
+ }
418
+ }
419
+ async #lease(contextDigest, historical) {
420
+ const entry = await this.#entry(contextDigest, historical);
421
+ this.#requireEntry(entry);
422
+ entry.leases += 1;
423
+ this.#contexts.delete(contextDigest);
424
+ this.#contexts.set(contextDigest, entry);
425
+ return entry;
426
+ }
427
+ #makeRoom() {
428
+ if (this.#contexts.size + this.#inFlight.size < MAX_CACHED_CONTEXTS)
429
+ return;
430
+ const oldest = [...this.#contexts].find(([, entry]) => entry.leases === 0);
431
+ if (!oldest) {
432
+ throw new AuthorizationProviderUnavailableError("provider context cache capacity reached");
433
+ }
434
+ this.#invalidate(oldest[1]);
435
+ }
436
+ async #load(contextDigest, historical, generation, owner) {
437
+ assertDigest(contextDigest);
438
+ this.#contextResolves += 1;
439
+ const watch = await this.#registryIo("authorization revocation watch is unavailable", () => this.#registry.watchRevocation(contextDigest));
440
+ let entry;
441
+ let watchOwned = false;
442
+ try {
443
+ const contextEntry = await this.#registryIo("authorization context registry is unavailable", () => this.#registry.getContext(contextDigest));
444
+ if (!contextEntry) {
445
+ throw new AuthorizationProviderUnavailableError("authorization context is missing");
446
+ }
447
+ let context;
448
+ try {
449
+ context = parseJsonRecord(contextEntry.value);
450
+ }
451
+ catch (error) {
452
+ throw new AuthorizationProviderUnavailableError("authorization context registry entry is malformed", error);
453
+ }
454
+ const issuerKeyId = String(context.issuerKeyId ?? "");
455
+ if (!issuerKeyId) {
456
+ throw new AuthorizationProviderUnavailableError("authorization context issuer is missing");
457
+ }
458
+ const issuer = await this.#issuer(issuerKeyId);
459
+ entry = {
460
+ contextDigest,
461
+ context,
462
+ issuer,
463
+ generation,
464
+ covered: false,
465
+ disposed: false,
466
+ resourcesDisposed: false,
467
+ leases: 0,
468
+ watch: watch.iterator,
469
+ closeWatch: watch.close,
470
+ };
471
+ watchOwned = true;
472
+ while (true) {
473
+ const result = await this.#registryIo("authorization revocation watch is unavailable", () => watch.iterator.next());
474
+ if (result.done) {
475
+ throw new AuthorizationProviderUnavailableError("authorization revocation watch ended during initialization");
476
+ }
477
+ if (result.value.operation === "initialized")
478
+ break;
479
+ this.#applyRevocation(entry, result.value);
480
+ }
481
+ entry.covered = true;
482
+ void this.#watchRevocation(entry, watch.iterator);
483
+ const verified = await this.#verified(entry, historical);
484
+ if (verified.verified.contextDigest !== contextDigest) {
485
+ throw new Error("authorization context digest does not match its registry key");
486
+ }
487
+ this.#requirePendingOwner(contextDigest, generation, owner);
488
+ if (entry.disposed || !entry.covered) {
489
+ throw new AuthorizationProviderUnavailableError("authorization context lost revocation coverage");
490
+ }
491
+ this.#contexts.set(contextDigest, entry);
492
+ return entry;
493
+ }
494
+ catch (error) {
495
+ if (entry) {
496
+ this.#invalidate(entry);
497
+ }
498
+ else if (!watchOwned) {
499
+ await watch.close();
500
+ }
501
+ throw error;
502
+ }
503
+ }
504
+ async #watchRevocation(entry, iterator) {
505
+ try {
506
+ while (entry.covered && !entry.disposed) {
507
+ const result = await iterator.next();
508
+ if (result.done)
509
+ return;
510
+ if (result.value.operation === "initialized")
511
+ continue;
512
+ this.#applyRevocation(entry, result.value);
513
+ }
514
+ }
515
+ catch {
516
+ // Coverage is invalidated below; the next request resynchronizes it.
517
+ }
518
+ finally {
519
+ this.#invalidate(entry);
520
+ try {
521
+ await iterator.return?.();
522
+ }
523
+ catch {
524
+ // The entry is already unusable and will reload through a new watch.
525
+ }
526
+ }
527
+ }
528
+ #applyRevocation(entry, event) {
529
+ this.#revocationRevision = Math.max(this.#revocationRevision, event.revision);
530
+ this.#lastUpdateAt = this.#now();
531
+ if (event.operation !== "put") {
532
+ throw new AuthorizationProviderUnavailableError("authorization revocation registry entry disappeared");
533
+ }
534
+ entry.revokedAt = Math.max(entry.revokedAt ?? 0, parseRevocation(event.value));
535
+ let ownDigest;
536
+ let candidateDigest;
537
+ try {
538
+ ownDigest = this.#cache.storedContextDigest();
539
+ candidateDigest = this.#cache.hasCandidate()
540
+ ? this.#cache.transportCurrent().contextDigest
541
+ : undefined;
542
+ }
543
+ catch {
544
+ // No own installation remains to invalidate.
545
+ }
546
+ if (ownDigest === entry.contextDigest) {
547
+ this.#ownRevokedDigest = entry.contextDigest;
548
+ this.refreshOwnAuthorization();
549
+ }
550
+ else if (candidateDigest === entry.contextDigest) {
551
+ // A revoked candidate is discarded without touching the active predecessor.
552
+ this.#ownRevokedDigest = entry.contextDigest;
553
+ this.#cache.invalidateCandidate(entry.contextDigest);
554
+ this.#cache.requestRefresh();
555
+ }
556
+ }
557
+ async #issuer(keyId) {
558
+ if (this.#ownIssuer.keyId === keyId)
559
+ return this.#ownIssuer;
560
+ for (const entry of this.#contexts.values()) {
561
+ if (!entry.disposed && entry.issuer.keyId === keyId)
562
+ return entry.issuer;
563
+ }
564
+ const url = new URL(`/auth/keys/${encodeURIComponent(keyId)}`, this.#cache.trellisUrl);
565
+ if (url.origin !== new URL(this.#cache.trellisUrl).origin) {
566
+ throw new Error("authorization issuer origin is invalid");
567
+ }
568
+ let response;
569
+ try {
570
+ response = await this.#cache.fetch(url, {
571
+ cache: "no-store",
572
+ redirect: "error",
573
+ });
574
+ }
575
+ catch (error) {
576
+ throw new AuthorizationProviderUnavailableError("authorization issuer is unavailable", error);
577
+ }
578
+ if (!response.ok) {
579
+ throw new AuthorizationProviderUnavailableError(`authorization issuer is unavailable (${response.status})`);
580
+ }
581
+ let bytes;
582
+ try {
583
+ bytes = await readBoundedResponse(response, 16_384);
584
+ }
585
+ catch (error) {
586
+ throw new AuthorizationProviderUnavailableError("authorization issuer response is unavailable", error);
587
+ }
588
+ let record;
589
+ try {
590
+ record = parseRecord(JSON.parse(new TextDecoder().decode(bytes)), "authorization issuer");
591
+ }
592
+ catch (error) {
593
+ throw new AuthorizationProviderUnavailableError("authorization issuer response is unavailable", error);
594
+ }
595
+ try {
596
+ if (record.keyId !== keyId) {
597
+ throw new Error("authorization issuer key mismatch");
598
+ }
599
+ if (typeof record.publicKey !== "string") {
600
+ throw new Error("authorization issuer public key is invalid");
601
+ }
602
+ assertDigest(keyId);
603
+ assertDigest(record.publicKey);
604
+ if (record.state !== "active" && record.state !== "retired" &&
605
+ record.state !== "revoked") {
606
+ throw new Error("authorization issuer state is invalid");
607
+ }
608
+ return {
609
+ keyId,
610
+ publicKey: record.publicKey,
611
+ state: record.state,
612
+ };
613
+ }
614
+ catch (error) {
615
+ throw new AuthorizationProviderUnavailableError("authorization issuer response is unavailable", error);
616
+ }
617
+ }
618
+ #verified(entry, historical) {
619
+ const key = historical ? "historical" : "live";
620
+ let pending = entry[key];
621
+ if (!pending) {
622
+ this.#contextVerifications += 1;
623
+ const created = import("../protocol_wasm.js").then(({ createAuthorizationContextHandleWasm }) => createAuthorizationContextHandleWasm({
624
+ issuer: entry.issuer,
625
+ context: entry.context,
626
+ policy: this.#policy(this.#now()),
627
+ historical,
628
+ })).then((verified) => {
629
+ if (entry.resourcesDisposed) {
630
+ verified.handle.free();
631
+ throw new AuthorizationProviderUnavailableError("authorization context lost revocation coverage");
632
+ }
633
+ return verified;
634
+ });
635
+ pending = created.catch((error) => {
636
+ if (entry[key] === pending)
637
+ entry[key] = undefined;
638
+ throw error;
639
+ });
640
+ entry[key] = pending;
641
+ }
642
+ return pending;
643
+ }
644
+ #policy(nowUnixSeconds) {
645
+ try {
646
+ return this.#cache.verificationPolicy(nowUnixSeconds);
647
+ }
648
+ catch (error) {
649
+ throw new AuthorizationProviderUnavailableError("authorization verification policy is unavailable", error);
650
+ }
651
+ }
652
+ #requirePendingOwner(contextDigest, generation, owner) {
653
+ this.#requireAvailable();
654
+ const pending = this.#inFlight.get(contextDigest);
655
+ if (generation !== this.#generation || pending?.generation !== generation ||
656
+ pending.owner !== owner) {
657
+ throw new AuthorizationProviderUnavailableError("authorization context load became stale");
658
+ }
659
+ }
660
+ #requireEntry(entry) {
661
+ this.#requireAvailable();
662
+ if (entry.generation !== this.#generation || !entry.covered ||
663
+ entry.disposed || this.#contexts.get(entry.contextDigest) !== entry) {
664
+ throw new AuthorizationProviderUnavailableError("authorization context lost revocation coverage");
665
+ }
666
+ }
667
+ #invalidate(entry) {
668
+ const wasOwn = this.#ownEntry === entry;
669
+ if (wasOwn) {
670
+ this.#ownEntry = undefined;
671
+ this.#ownUsable = false;
672
+ this.#onOwnInvalidated?.();
673
+ this.#release(entry);
674
+ }
675
+ entry.covered = false;
676
+ entry.disposed = true;
677
+ void entry.closeWatch?.();
678
+ entry.closeWatch = undefined;
679
+ if (this.#contexts.get(entry.contextDigest) === entry) {
680
+ this.#contexts.delete(entry.contextDigest);
681
+ }
682
+ if (entry.leases === 0)
683
+ this.#disposeResources(entry);
684
+ if (wasOwn && entry.revokedAt === undefined && this.#connected &&
685
+ !this.#cache.hasCandidate()) {
686
+ void this.#restoreOwnContext(this.#generation);
687
+ }
688
+ }
689
+ #release(entry) {
690
+ entry.leases -= 1;
691
+ if (entry.disposed && entry.leases === 0)
692
+ this.#disposeResources(entry);
693
+ }
694
+ #disposeResources(entry) {
695
+ if (entry.resourcesDisposed)
696
+ return;
697
+ entry.resourcesDisposed = true;
698
+ for (const pending of [entry.live, entry.historical]) {
699
+ void pending?.then(({ handle }) => handle.free()).catch(() => { });
700
+ }
701
+ }
702
+ async #registryIo(message, operation) {
703
+ try {
704
+ return await operation();
705
+ }
706
+ catch (error) {
707
+ if (error instanceof AuthorizationProviderUnavailableError)
708
+ throw error;
709
+ throw new AuthorizationProviderUnavailableError(message, error);
710
+ }
711
+ }
712
+ #requireAvailable() {
713
+ if (!this.#started || this.#stopped || !this.#connected) {
714
+ throw new AuthorizationProviderUnavailableError("authorization provider is unavailable");
715
+ }
716
+ }
717
+ }
718
+ function parseJsonRecord(value) {
719
+ return parseRecord(JSON.parse(new TextDecoder().decode(value)), "authorization context");
720
+ }
721
+ async function readBoundedResponse(response, limit) {
722
+ const contentLength = response.headers.get("content-length");
723
+ if (contentLength !== null && Number(contentLength) > limit) {
724
+ throw new InvalidIssuerResponseError("authorization issuer response is too large");
725
+ }
726
+ if (!response.body)
727
+ return new Uint8Array();
728
+ const reader = response.body.getReader();
729
+ const chunks = [];
730
+ let length = 0;
731
+ try {
732
+ for (;;) {
733
+ const { done, value } = await reader.read();
734
+ if (done)
735
+ break;
736
+ length += value.byteLength;
737
+ if (length > limit) {
738
+ throw new InvalidIssuerResponseError("authorization issuer response is too large");
739
+ }
740
+ chunks.push(value);
741
+ }
742
+ }
743
+ finally {
744
+ reader.releaseLock();
745
+ }
746
+ const bytes = new Uint8Array(length);
747
+ let offset = 0;
748
+ for (const chunk of chunks) {
749
+ bytes.set(chunk, offset);
750
+ offset += chunk.byteLength;
751
+ }
752
+ return bytes;
753
+ }
754
+ function parseRevocation(value) {
755
+ try {
756
+ const record = parseJsonRecord(value);
757
+ const revokedAt = record.revokedAt;
758
+ if (!Number.isSafeInteger(revokedAt) || Number(revokedAt) <= 0) {
759
+ throw new Error("authorization revocation is invalid");
760
+ }
761
+ return Number(revokedAt);
762
+ }
763
+ catch (error) {
764
+ throw new AuthorizationProviderUnavailableError("authorization revocation registry is unavailable", error);
765
+ }
766
+ }
767
+ function parseRecord(value, kind) {
768
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
769
+ throw new Error(`${kind} is invalid`);
770
+ }
771
+ return value;
772
+ }
773
+ function assertDigest(value) {
774
+ if (!/^[A-Za-z0-9_-]{43}$/.test(value)) {
775
+ throw new Error("authorization context digest is invalid");
776
+ }
777
+ }
778
+ function requestFailure(code, path) {
779
+ return { ok: false, error: { code, path } };
780
+ }
781
+ function eventFailure(code, path) {
782
+ return { ok: false, error: { code, path } };
783
+ }