@cotal-ai/auth 0.12.0 → 0.13.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 (95) hide show
  1. package/dist/admission-mediator.d.ts +319 -0
  2. package/dist/admission-mediator.d.ts.map +1 -0
  3. package/dist/admission-mediator.js +1033 -0
  4. package/dist/admission-mediator.js.map +1 -0
  5. package/dist/auth-admin.d.ts +29 -0
  6. package/dist/auth-admin.d.ts.map +1 -0
  7. package/dist/auth-admin.js +224 -0
  8. package/dist/auth-admin.js.map +1 -0
  9. package/dist/authority-client.d.ts +215 -0
  10. package/dist/authority-client.d.ts.map +1 -0
  11. package/dist/authority-client.js +346 -0
  12. package/dist/authority-client.js.map +1 -0
  13. package/dist/barrier-evict.d.ts +16 -0
  14. package/dist/barrier-evict.d.ts.map +1 -0
  15. package/dist/barrier-evict.js +100 -0
  16. package/dist/barrier-evict.js.map +1 -0
  17. package/dist/connect-reader.d.ts +72 -0
  18. package/dist/connect-reader.d.ts.map +1 -0
  19. package/dist/connect-reader.js +154 -0
  20. package/dist/connect-reader.js.map +1 -0
  21. package/dist/continuity.d.ts.map +1 -1
  22. package/dist/continuity.js +3 -0
  23. package/dist/continuity.js.map +1 -1
  24. package/dist/credential-ledger.d.ts +343 -0
  25. package/dist/credential-ledger.d.ts.map +1 -0
  26. package/dist/credential-ledger.js +732 -0
  27. package/dist/credential-ledger.js.map +1 -0
  28. package/dist/drain-repair.d.ts +95 -0
  29. package/dist/drain-repair.d.ts.map +1 -0
  30. package/dist/drain-repair.js +305 -0
  31. package/dist/drain-repair.js.map +1 -0
  32. package/dist/idp.d.ts +19 -0
  33. package/dist/idp.d.ts.map +1 -1
  34. package/dist/idp.js +17 -0
  35. package/dist/idp.js.map +1 -1
  36. package/dist/index.d.ts +5 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +29 -2
  39. package/dist/index.js.map +1 -1
  40. package/dist/issuer.d.ts +8 -0
  41. package/dist/issuer.d.ts.map +1 -1
  42. package/dist/issuer.js +4 -2
  43. package/dist/issuer.js.map +1 -1
  44. package/dist/ledger-scanner.d.ts +98 -0
  45. package/dist/ledger-scanner.d.ts.map +1 -0
  46. package/dist/ledger-scanner.js +352 -0
  47. package/dist/ledger-scanner.js.map +1 -0
  48. package/dist/ledger.d.ts +7 -0
  49. package/dist/ledger.d.ts.map +1 -1
  50. package/dist/ledger.js +53 -5
  51. package/dist/ledger.js.map +1 -1
  52. package/dist/lifecycle-registry.d.ts +397 -0
  53. package/dist/lifecycle-registry.d.ts.map +1 -0
  54. package/dist/lifecycle-registry.js +819 -0
  55. package/dist/lifecycle-registry.js.map +1 -0
  56. package/dist/permissions.d.ts +5 -1
  57. package/dist/permissions.d.ts.map +1 -1
  58. package/dist/permissions.js +29 -6
  59. package/dist/permissions.js.map +1 -1
  60. package/dist/plane-claim.d.ts +83 -0
  61. package/dist/plane-claim.d.ts.map +1 -0
  62. package/dist/plane-claim.js +290 -0
  63. package/dist/plane-claim.js.map +1 -0
  64. package/dist/provider.d.ts.map +1 -1
  65. package/dist/provider.js +2 -1
  66. package/dist/provider.js.map +1 -1
  67. package/dist/records-scanner.d.ts +78 -0
  68. package/dist/records-scanner.d.ts.map +1 -0
  69. package/dist/records-scanner.js +320 -0
  70. package/dist/records-scanner.js.map +1 -0
  71. package/dist/retirement-barrier.d.ts +217 -0
  72. package/dist/retirement-barrier.d.ts.map +1 -0
  73. package/dist/retirement-barrier.js +624 -0
  74. package/dist/retirement-barrier.js.map +1 -0
  75. package/dist/retirement-cleaner.d.ts +54 -0
  76. package/dist/retirement-cleaner.d.ts.map +1 -0
  77. package/dist/retirement-cleaner.js +158 -0
  78. package/dist/retirement-cleaner.js.map +1 -0
  79. package/dist/root-credential.d.ts +26 -0
  80. package/dist/root-credential.d.ts.map +1 -0
  81. package/dist/root-credential.js +116 -0
  82. package/dist/root-credential.js.map +1 -0
  83. package/dist/service.d.ts +60 -0
  84. package/dist/service.d.ts.map +1 -1
  85. package/dist/service.js +317 -10
  86. package/dist/service.js.map +1 -1
  87. package/dist/session-ledger.d.ts +224 -0
  88. package/dist/session-ledger.d.ts.map +1 -0
  89. package/dist/session-ledger.js +713 -0
  90. package/dist/session-ledger.js.map +1 -0
  91. package/dist/token.d.ts +17 -0
  92. package/dist/token.d.ts.map +1 -1
  93. package/dist/token.js +30 -2
  94. package/dist/token.js.map +1 -1
  95. package/package.json +5 -3
@@ -0,0 +1,346 @@
1
+ /**
2
+ * The auth service's SELF-MINTED data-account connections (R1). The service already holds the
3
+ * data account's signing seed (minting scoped users IS its function), so its own infrastructure
4
+ * access is signed from that seed directly — a ledgered `cred.`/`epcred.` row would create no
5
+ * enforcement point (connect never re-reads the reader's own row) and would be enumerated by the
6
+ * lifecycle barriers as if it were an agent's. The revoke story is the D5 class: stop the service
7
+ * or rotate the data-account signing seed. A genuinely LEDGERED infra-credential family is the
8
+ * deferred infra-mint work; migrate onto it there, do not invent it here.
9
+ *
10
+ * Two connections, least-privilege by construction:
11
+ * - the CONNECT READER (`cotal:auth-reader:<space>`): read-only over both authority stores
12
+ * ({@link authConnectReaderGrants}), supervised — shape-proved on EVERY (re)bind, unproved
13
+ * while disconnected, so a reader that cannot currently prove its stores DENIES every connect.
14
+ * - the MINT WRITER (`cotal:auth-mint:<space>`): the exchange-time issuance executor
15
+ * ({@link authorityWriterGrants}) — store ensure + the activation saga + the root-credential
16
+ * mint protocol. Its unfenced reads only feed revision-pinned CASes (a stale read LOSES) or a
17
+ * bearer stamp the leader-served connect arm re-checks, so bind-time proof suffices.
18
+ *
19
+ * RENEWAL IS A FAIL-CLOSED BOUNDARY: each connection authenticates with a SHORT-exp user JWT over
20
+ * a stable nkey, re-minted in-process at half-life; the broker disconnects the connection at JWT
21
+ * expiry and the reconnect presents the freshest mint. A renewal-mint failure fires
22
+ * {@link AuthorityClientOpts.onRenewalFailure} IMMEDIATELY — the supervised reader downs itself
23
+ * on it (unproved + connection closed), so connects DENY from the moment the renewal fails, not
24
+ * only when the old credential eventually expires. A downed reader never comes back without a
25
+ * service restart; there is no file-only fallback.
26
+ */
27
+ import { connect, jwtAuthenticator } from "@nats-io/transport-node";
28
+ import { encodeUser } from "@nats-io/jwt";
29
+ import { fromPublic, fromSeed } from "@nats-io/nkeys";
30
+ import { EpEnvelopeError, assertInboxConnId, endpointToken, epAuthBucket, epfStreamName, newIdentity, recordsBucket, retirementFrontierStreams, spacePrefix, assertPoolToken, principalTags, principalKey } from "@cotal-ai/core";
31
+ import { authConnectReaderGrants, openConnectReader } from "./connect-reader.js";
32
+ /** Self-minted infra-credential TTL (fact-3 pin: SHORT expiry + in-process renewal, a bounded
33
+ * in-memory credential that closes with the service — never an unbounded persisted one). */
34
+ export const AUTHORITY_CLIENT_TTL_SEC = 15 * 60;
35
+ /**
36
+ * The MINT WRITER's scoped credential grant (SPEC 13.9): the exchange-time issuance executor.
37
+ * Exactly the store-ensure + activation-saga + root-mint surface:
38
+ *
39
+ * - `$JS.API.STREAM.CREATE` on both authority KV streams and `STREAM.UPDATE` on records (the
40
+ * boot-time `ensureAuthorityStores`), `STREAM.INFO` on both (ensure verify + §13.12 bind
41
+ * proof), `STREAM.MSG.GET` on both (auth-store KV reads are leader-only by shape; fenced
42
+ * record reads), and records `DIRECT.GET` (the records bucket is Direct-capable and its KV
43
+ * reads here only feed revision-pinned CASes, which a stale read LOSES — fail-closed).
44
+ * - `$KV` writes on EXACTLY the issuance keys: auth `gate.` / `cred.` / `bysrc.` (the gate CAS
45
+ * fence and the mint's rows) and records `lifecycle.` / `uid.` (the alias head and the
46
+ * space-global uid reservation). No `srcgate.`/`stage.`/`session.` writes: root issuance
47
+ * presents no handles and runs no takeover; barriers are NOT this credential's job.
48
+ *
49
+ * Named residuals (D32 class, not pretend-confined): `STREAM.MSG.GET` is body-selected, so this
50
+ * is a stream-wide METADATA read of both stores; the `$KV` prefixes span every uid/alias (broker
51
+ * ACLs cannot scope "only the row you are minting"), so a compromised writer can forge issuance
52
+ * rows — it is the ISSUANCE AUTHORITY, that authority is exactly what it holds; and every raw
53
+ * MSG.GET/DIRECT.GET profile carries the caller-selected-reply injection class.
54
+ */
55
+ export function authorityWriterGrants(space, connId) {
56
+ const auth = `KV_${epAuthBucket(space)}`;
57
+ const records = `KV_${recordsBucket(space)}`;
58
+ // Same connId hygiene as authConnectReaderGrants: the only untrusted subject-forming input goes
59
+ // through the shared inbox grammar so it can never widen the scoped inbox.
60
+ const inbox = assertInboxConnId(connId);
61
+ return {
62
+ publish: [
63
+ "$JS.API.INFO",
64
+ `$JS.API.STREAM.CREATE.${auth}`,
65
+ `$JS.API.STREAM.CREATE.${records}`,
66
+ `$JS.API.STREAM.UPDATE.${records}`,
67
+ `$JS.API.STREAM.INFO.${auth}`,
68
+ `$JS.API.STREAM.INFO.${records}`,
69
+ `$JS.API.STREAM.MSG.GET.${auth}`,
70
+ `$JS.API.STREAM.MSG.GET.${records}`,
71
+ `$JS.API.DIRECT.GET.${records}`,
72
+ `$JS.API.DIRECT.GET.${records}.>`,
73
+ `$KV.${epAuthBucket(space)}.gate.>`,
74
+ `$KV.${epAuthBucket(space)}.cred.>`,
75
+ `$KV.${epAuthBucket(space)}.bysrc.>`,
76
+ `$KV.${recordsBucket(space)}.lifecycle.>`,
77
+ `$KV.${recordsBucket(space)}.uid.>`,
78
+ ],
79
+ subscribe: [`_INBOX_${inbox}.>`],
80
+ };
81
+ }
82
+ /**
83
+ * The BARRIER EXECUTOR's scoped credential grant (SPEC 13.9): the lifecycle-barrier surface —
84
+ * exactly what the takeover barrier (and its retirement sibling) reaches through the sealed
85
+ * registry, on its OWN connection so the latency-sensitive mint writer never carries barrier
86
+ * authority (its own doc pins "barriers are NOT this credential's job"):
87
+ *
88
+ * - `STREAM.INFO` on both stores (the registry's §13.12 bind proof), `STREAM.MSG.GET` on both
89
+ * (leader-served gate/cred/intent and head reads), and records `DIRECT.GET` (reads that only
90
+ * feed revision-pinned CASes — the mint writer's own discipline).
91
+ * - NO auth-stream `CONSUMER.CREATE` at all (46e778f re-verify): a consumer-create BODY is not
92
+ * subject-ACL confinable, so any create grant admits a durable PUSH exporter surviving revoke
93
+ * (nats-server#8274). This profile therefore does NOT enumerate: the barrier's
94
+ * family/intent/lineage LastPerSubject scans run on the SEALED auth-ledger scanner, a SEPARATE
95
+ * dedicated credential that holds the only auth-stream `CONSUMER.CREATE` and never escapes the
96
+ * process (`ledger-scanner.ts`). The `STREAM.MSG.GET` here is only this profile's leader-served
97
+ * POINT reads (a specific gate/cred/intent/head `last_by_subj`), never a family scan. No consumer
98
+ * authority on either stream.
99
+ * - `$KV` writes on EXACTLY the barrier keys: auth `gate.` (the freeze/reopen CAS), `cred.`
100
+ * (the family revokes), `stage.` (the durable operation intent), and records `lifecycle.`
101
+ * (the containment/epoch head CASes). No `bysrc.`/`uid.` (a barrier never mints), no
102
+ * `srcgate.`/`session.` (handle revocation and session reconcile are injected seams, not
103
+ * this credential's job).
104
+ *
105
+ * Named residuals (D32 class, not pretend-confined): the stream-wide body-selected `MSG.GET`
106
+ * metadata read (a ONE-SHOT read to the requester's own inbox while the credential is live — not
107
+ * a durable/push exporter) and the caller-selected-reply injection class ride this profile like
108
+ * its siblings; and the `$KV` prefixes span every uid (broker ACLs cannot scope "only the family
109
+ * you are containing") — the executor IS the barrier authority, and that authority is exactly
110
+ * what it holds.
111
+ */
112
+ export function authorityBarrierGrants(space, connId) {
113
+ const auth = `KV_${epAuthBucket(space)}`;
114
+ const records = `KV_${recordsBucket(space)}`;
115
+ // Same connId hygiene as its siblings: the only untrusted subject-forming input goes through
116
+ // the shared inbox grammar so it can never widen the scoped inbox.
117
+ const inbox = assertInboxConnId(connId);
118
+ // The §13.1 frontier step reads the last sequence of every stream in the retirement's
119
+ // `frontierStreams` (a subset of the CLOSED {@link retirementFrontierStreams} set). Grant
120
+ // STREAM.INFO for exactly that set, from the same source the intent is validated against, so the
121
+ // frontier authority and the frontier-writable set never drift (a real callout broker denies an
122
+ // ungranted INFO and the resume wedges forever). records INFO is already listed; the frontier set
123
+ // adds EPF/EPW/EPE (records dedups below).
124
+ const frontierInfo = retirementFrontierStreams(space).map((s) => `$JS.API.STREAM.INFO.${s}`);
125
+ return {
126
+ publish: [
127
+ "$JS.API.INFO",
128
+ `$JS.API.STREAM.INFO.${auth}`,
129
+ `$JS.API.STREAM.INFO.${records}`,
130
+ ...frontierInfo.filter((r) => r !== `$JS.API.STREAM.INFO.${records}`),
131
+ `$JS.API.STREAM.MSG.GET.${auth}`,
132
+ `$JS.API.STREAM.MSG.GET.${records}`,
133
+ `$JS.API.DIRECT.GET.${records}`,
134
+ `$JS.API.DIRECT.GET.${records}.>`,
135
+ // NO auth-stream `CONSUMER.CREATE` (security/fact/distsys/freelance, 46e778f re-verify): the
136
+ // extended `CONSUMER.CREATE.<auth>.<name>.<filter>` API pins name+filter on the SUBJECT but
137
+ // the request BODY is not subject-ACL confinable — a holder can set `durable_name`
138
+ // (== the subject name token) + PUSH `deliver_subject` (nats-server#8274) and leave a DURABLE
139
+ // exporter of every current/future gate/cred/stage row that SURVIVES this connection and JWT
140
+ // revoke. Subject-token pinning cannot stop it; only holding no create can. The barrier's
141
+ // family/intent/lineage enumerations therefore run on the SEALED auth-ledger scanner — a
142
+ // SEPARATE dedicated credential that holds the only auth-stream `CONSUMER.CREATE` and never
143
+ // escapes the process (ledger-scanner.ts) — NOT on this profile. The `STREAM.MSG.GET` granted
144
+ // above is only this profile's leader-served POINT reads (a specific gate/cred/intent/head
145
+ // `last_by_subj`). Remaining D32 residual (unchanged, accepted): that stream-wide `MSG.GET`
146
+ // while the credential is LIVE is a one-shot read to the requester's own inbox carrying the
147
+ // caller-selected-reply injection class — never a durable/push exporter.
148
+ `$KV.${epAuthBucket(space)}.gate.>`,
149
+ `$KV.${epAuthBucket(space)}.cred.>`,
150
+ // ONE token: the barrier's own durable operation intents `stage.<opId>` — never `stage.>`,
151
+ // which would also reach the session ledger's `stage.session.<sid>.<c|s>` release pins (a
152
+ // family the SESSION writer owns). The documented-future takeover/registration successor
153
+ // artifacts (`stage.<opId>.…`) have no writer yet; compose `stage.*.>` in the slice that
154
+ // introduces them, so forgetting fails loud here instead of widening silently now.
155
+ `$KV.${epAuthBucket(space)}.stage.*`,
156
+ // The PLANE CLAIM (#29 HIGH 3, SPEC 13.13): the ONE exact never-deleted key the plane-open
157
+ // create/CAS protocol writes (`plane` — no `.>`/`.*` widen; the row binds the two sealed
158
+ // scanner connection tuples and its held/released state). Reads ride the leader-served
159
+ // `STREAM.MSG.GET` above. Only this profile writes it; the claim IS the cross-process
160
+ // single-plane exclusion, so a wider row here would hand takeover authority to whatever
161
+ // else composes onto this profile.
162
+ `$KV.${epAuthBucket(space)}.plane`,
163
+ `$KV.${recordsBucket(space)}.lifecycle.>`,
164
+ // The retirement FRONTIER record `frontier.<lifecycleUid>` (§13.7: create-only, never
165
+ // deleted, one key per retired lifecycle, recorded once under its own operation's opId
166
+ // BEFORE the gate/head terminals): the barrier is its one writer, and the exact-arity
167
+ // `frontier.*` row is that authority (#29 piece 4; a barrier without it is broker-denied
168
+ // at step 6 on a real callout broker, masked by super-user smoke brokers).
169
+ `$KV.${recordsBucket(space)}.frontier.*`,
170
+ ],
171
+ subscribe: [`_INBOX_${inbox}.>`],
172
+ };
173
+ }
174
+ /** The RETIREMENT SETTLEMENT EXECUTOR's op-bounded rows (SPEC 13.9 "Retirement settlement",
175
+ * the ceee1a1 authority split): COMPOSED onto {@link authorityBarrierGrants} for ONE retirement
176
+ * operation over its EFFECTIVE INVENTORY endpoint/pools (the target's accepted obligation routes
177
+ * UNIONED with the durable intent's trusted additive hints) — never a standing grant, never
178
+ * minted without a live intent. Per effective-inventory pool: the lease-record CAS write
179
+ * (`lease.<endpoint>.<pool>.…` — create for the worker-less expiry sentinel, revision-pinned
180
+ * update otherwise) and the lease-derived `wrk` terminal create-only publish
181
+ * (`epf.<endpoint>.wrk.<pool>.>`, first terminal wins). Plus the leader-served EPF fencing
182
+ * reads (`STREAM.MSG.GET.EPF_<space>`: the acceptance-decision re-derivation and the
183
+ * terminal-observe/CAS-loser reads); the records-side fencing/CAS reads ride the barrier
184
+ * profile this composes onto. The `epw.>` ENQUEUE row is deliberately absent: the executor's
185
+ * reconcile is reachable only for items at/past their own `workExpiry` (the settlement seam
186
+ * refuses `expired` before the horizon), which structurally never takes the re-enqueue repair
187
+ * branch — if a code change ever reached it, the broker denies and the barrier fails loud.
188
+ *
189
+ * D32 residuals, EXPLICIT (the relocated forge the bounded cleaner does NOT carry): KV subject
190
+ * permissions cannot distinguish CAS from overwrite or DEL/PURGE markers, and the `wrk`
191
+ * publish is payload-blind, so a compromised executor can forge a lease settlement or work
192
+ * terminal WITHIN its EFFECTIVE-INVENTORY pools (never beyond them); the space-wide EPF
193
+ * `STREAM.MSG.GET` exposure and the caller-selected-reply injection ride it like every
194
+ * API-holding profile. Op-bounded, effective-inventory-confined, revoked with the operation. */
195
+ export function barrierExecutorSettlementGrants(space, endpoint, pools) {
196
+ if (!Array.isArray(pools) || pools.length === 0)
197
+ throw new EpEnvelopeError("failed-precondition", "an executor settlement grant lists at least one exact pool (SPEC 13.9: the op intent enumerates them; a poolless settlement authority is none)");
198
+ const e = endpointToken(endpoint);
199
+ const publish = [];
200
+ for (const pool of pools) {
201
+ const p = assertPoolToken(pool);
202
+ publish.push(`${spacePrefix(space)}.epf.${e}.wrk.${p}.>`, `$KV.${recordsBucket(space)}.lease.${e}.${p}.>`);
203
+ }
204
+ publish.push(`$JS.API.STREAM.MSG.GET.${epfStreamName(space)}`);
205
+ return { publish };
206
+ }
207
+ /**
208
+ * Open one self-minted data-account connection: a stable user nkey, a short-exp user JWT signed
209
+ * by the data account's signing seed, re-minted in-process at half-life. The authenticator
210
+ * presents the FRESHEST mint on every (re)connect; see the module header for why a failed
211
+ * renewal fail-closes instead of retrying on a lapsed credential.
212
+ */
213
+ export async function openAuthorityClient(opts) {
214
+ const identity = newIdentity();
215
+ const grants = opts.grants(identity.id);
216
+ const signer = fromSeed(new TextEncoder().encode(opts.dataAccount.signingSeed));
217
+ const mint = async () => encodeUser(opts.label, fromPublic(identity.id), fromPublic(opts.dataAccount.pub), { pub: { allow: grants.publish }, sub: { allow: grants.subscribe },
218
+ ...(opts.principal ? { tags: principalTags(opts.principal.owner, opts.principal.actor) } : {}) },
219
+ // planeCandidate: NON-EXPIRING by design (see the option's doc — the named fact-3 residual);
220
+ // every other authority connection keeps the short-exp + half-life-renewal boundary.
221
+ opts.planeCandidate ? { signer } : { signer, exp: Math.floor(Date.now() / 1000) + AUTHORITY_CLIENT_TTL_SEC });
222
+ let currentJwt = await mint();
223
+ const renew = async () => {
224
+ if (opts.probeRenewal?.fail)
225
+ throw new Error("probe-forced renewal failure");
226
+ return mint();
227
+ };
228
+ // No renewal timer for a planeCandidate: nothing expires, and a fresh mint could only be
229
+ // presented on the reconnect the non-reconnecting shape forbids.
230
+ const renewal = opts.planeCandidate ? undefined : setInterval(() => {
231
+ void renew().then((jwt) => { currentJwt = jwt; }, (e) => {
232
+ opts.log(`${opts.label}: credential renewal FAILED (${e?.message ?? String(e)}) - failing closed NOW (fail-closed renewal boundary)`);
233
+ opts.onRenewalFailure?.();
234
+ });
235
+ }, opts.probeRenewal?.intervalMs ?? (AUTHORITY_CLIENT_TTL_SEC / 2) * 1000);
236
+ let nc;
237
+ try {
238
+ nc = await connect({
239
+ servers: opts.server,
240
+ name: opts.label,
241
+ authenticator: jwtAuthenticator(() => currentJwt, new TextEncoder().encode(identity.seed)),
242
+ inboxPrefix: `_INBOX_${identity.id}`,
243
+ // planeCandidate and the per-call repair connections (#4) never auto-reconnect; every standing
244
+ // authority connection reconnects infinitely so a broker blip never downs it silently.
245
+ ...(opts.planeCandidate || opts.noReconnect ? { reconnect: false, maxReconnectAttempts: 0 } : { maxReconnectAttempts: -1 }),
246
+ });
247
+ }
248
+ catch (e) {
249
+ if (renewal !== undefined)
250
+ clearInterval(renewal);
251
+ throw e;
252
+ }
253
+ let tuple;
254
+ let gone;
255
+ if (opts.planeCandidate) {
256
+ // The claim row pins this exact identity; a connect whose INFO cannot supply it is unusable
257
+ // as an ownership-bearing connection — fail loud, never claim over a hole.
258
+ const info = nc.info;
259
+ if (typeof info?.server_id !== "string" || info.server_id.length === 0 ||
260
+ typeof info.client_id !== "number" || !Number.isSafeInteger(info.client_id) || info.client_id <= 0) {
261
+ await nc.close().catch(() => { });
262
+ throw new EpEnvelopeError("unavailable", `${opts.label}: the broker INFO carries no usable (server_id, client_id) for the plane claim (got server_id=${JSON.stringify(info?.server_id)}, client_id=${JSON.stringify(info?.client_id)}); an ownership-bearing scanner connection must be claim-pinnable (SPEC 13.13)`);
263
+ }
264
+ tuple = { serverId: info.server_id, cid: info.client_id, userNkey: identity.id };
265
+ gone = nc.closed().then(() => undefined, () => undefined);
266
+ }
267
+ return {
268
+ nc,
269
+ connId: identity.id,
270
+ ...(opts.principal ? { principal: principalKey(opts.principal.owner, opts.principal.actor).key } : {}),
271
+ ...(tuple ? { tuple } : {}),
272
+ ...(gone ? { gone } : {}),
273
+ close: async () => {
274
+ if (renewal !== undefined)
275
+ clearInterval(renewal);
276
+ await nc.close().catch(() => { });
277
+ },
278
+ };
279
+ }
280
+ /**
281
+ * Open the supervised connect reader: the self-minted reader connection plus the §13.12 bind
282
+ * discipline ACROSS RECONNECTS. The shape proof re-runs on EVERY (re)bind, not just startup — a
283
+ * dropped reader reconnecting could land on a mirror/follower, so it is UNPROVED from the moment
284
+ * the connection drops until {@link openConnectReader} passes again, and `current()` refuses in
285
+ * between. The initial proof is awaited: this constructor resolving IS the readiness signal.
286
+ */
287
+ export async function openSupervisedConnectReader(opts) {
288
+ const label = `cotal:auth-reader:${opts.space}`;
289
+ let reader;
290
+ let closed = false;
291
+ let client;
292
+ client = await openAuthorityClient({
293
+ ...opts,
294
+ label,
295
+ grants: (connId) => authConnectReaderGrants(opts.space, connId),
296
+ // Renewal failure is a fail-closed boundary NOW: down the reader the instant a remint
297
+ // rejects, so connects DENY immediately instead of riding the old credential to its expiry.
298
+ onRenewalFailure: () => {
299
+ reader = undefined;
300
+ opts.log(`${label}: credential renewal failed - reader downed NOW, connects deny until the service restarts (fail-closed)`);
301
+ if (!closed)
302
+ void client?.close();
303
+ },
304
+ });
305
+ const c = client;
306
+ try {
307
+ reader = await openConnectReader(c.nc, opts.space);
308
+ }
309
+ catch (e) {
310
+ await c.close();
311
+ throw e;
312
+ }
313
+ void (async () => {
314
+ for await (const s of c.nc.status()) {
315
+ if (closed)
316
+ break;
317
+ if (s.type === "disconnect" || s.type === "error") {
318
+ reader = undefined;
319
+ opts.log(`${label}: connection lost (${s.type}) - connects DENY until the rebind shape proof passes`);
320
+ }
321
+ else if (s.type === "reconnect") {
322
+ try {
323
+ reader = await openConnectReader(c.nc, opts.space);
324
+ opts.log(`${label}: rebound and shape-proved; connect reads resume`);
325
+ }
326
+ catch (e) {
327
+ reader = undefined;
328
+ opts.log(`${label}: REBIND SHAPE PROOF FAILED (${e?.message ?? String(e)}) - connects stay denied`);
329
+ }
330
+ }
331
+ }
332
+ })();
333
+ return {
334
+ current: () => {
335
+ if (reader === undefined)
336
+ throw new EpEnvelopeError("unavailable", "the connect-credential reader is not currently bound + shape-proved; a connect is never authorized without the live credential row (deny-new is fail-closed, SPEC 13.1/13.12)");
337
+ return reader;
338
+ },
339
+ close: async () => {
340
+ closed = true;
341
+ reader = undefined; // a closed reader refuses immediately, not on the next status event
342
+ await c.close();
343
+ },
344
+ };
345
+ }
346
+ //# sourceMappingURL=authority-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority-client.js","sourceRoot":"","sources":["../src/authority-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { EvictPrincipal } from "./credential-ledger.js";
2
+ /**
3
+ * Build the barrier executor's `evictPrincipal` capability over the delivery daemon's
4
+ * `ctl.delivery-admin` rail. Per-call connection (an eviction is a rare, heavyweight barrier
5
+ * step; a standing privileged connection would be a wider surface holding nothing).
6
+ */
7
+ export declare function makeDeliveryAdminEvictor(opts: {
8
+ space: string;
9
+ server: string;
10
+ dataAccount: {
11
+ pub: string;
12
+ signingSeed: string;
13
+ };
14
+ log: (line: string) => void;
15
+ }): EvictPrincipal;
16
+ //# sourceMappingURL=barrier-evict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrier-evict.d.ts","sourceRoot":"","sources":["../src/barrier-evict.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAO7D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,GAAG,cAAc,CAkEjB"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The auth service's VERIFIED-EVICTION seam (SPEC 13.1/13.9). The lifecycle barriers require
3
+ * `verifiedGone === true` before any epoch advance or head terminal, and the `$SYS` scan → KICK →
4
+ * verify capability lives with the DELIVERY DAEMON (co-located with the broker) — NEVER in this
5
+ * process: the auth service holds the data-account signing seed, and the D5 rail-split exists
6
+ * precisely so no seed-holding process also holds broker-admin `$SYS` material. This module
7
+ * reaches the daemon over the wire on the privileged `ctl.delivery-admin` control rail (the
8
+ * exact mechanism `cotal actor revoke` uses), with a caller credential self-minted per call from
9
+ * the data-account seed the service already holds.
10
+ *
11
+ * FAIL-CLOSED MAPPING: every failure — no daemon (NoResponders/timeout), a refusal, a garbled
12
+ * reply, or a reply describing a DIFFERENT principal — returns `verifiedGone:false` with an
13
+ * honest note. The calling barrier then throws and its gate stays frozen/resumable; this seam
14
+ * never fabricates success and never throws raw (the barrier owns the canonical error copy).
15
+ *
16
+ * Named residual (panel-reviewable): the caller credential rides the SUPERVISOR profile (the
17
+ * `cotal actor revoke` precedent) — only that profile holds the `ctl.delivery-admin` publish
18
+ * grant. The service holds the account signing seed, so this confers no authority it does not
19
+ * already have; a dedicated narrow delivery-admin infra profile is the cleaner target once the
20
+ * ledgered infra-mint family (#30) lands — migrate there, do not invent it here.
21
+ */
22
+ import { CotalEndpoint, mintCreds, newIdentity } from "@cotal-ai/core";
23
+ /** The per-eviction credential's TTL (Security H3): one delivery-admin call runs in a 15s request
24
+ * budget; 60s covers connect + the call + teardown with margin, and bounds the blast radius of a
25
+ * copied supervisor credential to a minute rather than the profile's default 24h. */
26
+ const EVICTOR_CRED_TTL_SECONDS = 60;
27
+ /**
28
+ * Build the barrier executor's `evictPrincipal` capability over the delivery daemon's
29
+ * `ctl.delivery-admin` rail. Per-call connection (an eviction is a rare, heavyweight barrier
30
+ * step; a standing privileged connection would be a wider surface holding nothing).
31
+ */
32
+ export function makeDeliveryAdminEvictor(opts) {
33
+ // The stripped mint view (core's stripSpaceAuth shape): mintCreds reads ONLY space +
34
+ // account.pub + account.signingSeed; the operator/system material is genuinely absent here.
35
+ const auth = {
36
+ space: opts.space,
37
+ operator: { seed: "", jwt: "" },
38
+ account: { pub: opts.dataAccount.pub, seed: "", jwt: "", signingSeed: opts.dataAccount.signingSeed, signingPub: "" },
39
+ sys: { pub: "", jwt: "" },
40
+ };
41
+ const failClosed = (principal, note) => {
42
+ opts.log(`auth-barrier-evict: ${principal}: ${note}`);
43
+ return { principal, kicked: 0, remaining: 0, verifiedGone: false, scanComplete: false, note };
44
+ };
45
+ return async (principal) => {
46
+ const id = newIdentity();
47
+ let ep;
48
+ try {
49
+ // Security H3: this is a per-eviction credential for ONE ~15s delivery-admin call, not a
50
+ // standing connection. The `supervisor` profile is default standing-renewable (24h); mint it
51
+ // with a tight TTL so a COPIED credential expires almost immediately instead of carrying a
52
+ // day of full manager-control-tier + lease/presence authority. (The dedicated narrow
53
+ // delivery-admin infra profile is the #30 target; until then, bound the lifetime here.)
54
+ const creds = await mintCreds(auth, id, "supervisor", { expiresInSeconds: EVICTOR_CRED_TTL_SECONDS });
55
+ ep = new CotalEndpoint({
56
+ space: opts.space,
57
+ servers: opts.server,
58
+ creds,
59
+ card: { id: id.id, name: "auth-barrier-evict", kind: "endpoint" },
60
+ channels: [],
61
+ consume: false,
62
+ watchChannels: false,
63
+ watchPresence: false,
64
+ registerPresence: false,
65
+ });
66
+ ep.on("error", () => { });
67
+ await ep.start();
68
+ const r = await ep.requestDeliveryAdmin("evictPrincipal", { principal }, 15_000);
69
+ if (!r.ok)
70
+ return failClosed(principal, `the delivery daemon refused the eviction: ${r.error ?? "(no error copy)"}`);
71
+ const d = r.data;
72
+ if (d === undefined || d === null || d.principal !== principal ||
73
+ typeof d.kicked !== "number" || !Number.isSafeInteger(d.kicked) || d.kicked < 0 ||
74
+ typeof d.remaining !== "number" || !Number.isSafeInteger(d.remaining) || d.remaining < 0 ||
75
+ typeof d.verifiedGone !== "boolean" || typeof d.scanComplete !== "boolean")
76
+ return failClosed(principal, `the delivery daemon returned a garbled or foreign eviction result (${JSON.stringify(r.data ?? null)}); a result that does not verifiably describe this principal never authorizes`);
77
+ // An internally CONTRADICTORY success never authorizes: the barriers gate on
78
+ // `verifiedGone === true` alone, so a tuple claiming verified-gone while admitting an
79
+ // incomplete scan or surviving connections would launder a partial answer into an epoch
80
+ // advance / head terminal. Verified-gone is conclusive only as (scan complete, none remain).
81
+ if (d.verifiedGone === true && (d.scanComplete !== true || d.remaining !== 0))
82
+ return failClosed(principal, `the delivery daemon claimed verifiedGone with scanComplete=${String(d.scanComplete)} and remaining=${d.remaining}; an internally contradictory eviction result never authorizes`);
83
+ return {
84
+ principal,
85
+ kicked: d.kicked,
86
+ remaining: d.remaining,
87
+ verifiedGone: d.verifiedGone,
88
+ scanComplete: d.scanComplete,
89
+ ...(typeof d.note === "string" ? { note: d.note } : {}),
90
+ };
91
+ }
92
+ catch (e) {
93
+ return failClosed(principal, `the delivery-admin rail is unreachable (${e instanceof Error ? e.message : String(e)}); eviction is UNKNOWN and the barrier fails closed`);
94
+ }
95
+ finally {
96
+ await ep?.stop().catch(() => { });
97
+ }
98
+ };
99
+ }
100
+ //# sourceMappingURL=barrier-evict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrier-evict.js","sourceRoot":"","sources":["../src/barrier-evict.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The connect-time credential reader — the R1 "deny-new" substrate. At every connect the callout
3
+ * must prove the presented bearer's `credentialId` still names a LIVE `cred.<uid>.<credid>` row,
4
+ * so that revoking that row denies the NEXT connect (the v1 revocation lever that actually bites).
5
+ *
6
+ * The credential rows and the lifecycle head live in the DATA account, but the callout runs on the
7
+ * CALLOUT account, and NATS accounts are isolated — so the auth service opens ONE standing
8
+ * data-account reader connection (self-minted from the `dataAccount` signing seed it already
9
+ * holds; see {@link authConnectReaderGrants}) and this module reads through it.
10
+ *
11
+ * Two authority stores, both leader-served and both shape-proved on EVERY (re)bind (SPEC 13.12):
12
+ * - `cred.<uid>.<credid>` lives in `KV_cotal_auth_<space>` (`allow_direct=false`);
13
+ * - the lifecycle head `lifecycle.<owner>.<actor>` (carrying `currentCredentialId`) lives in
14
+ * `KV_cotal_records_<space>`.
15
+ * A follower/mirror can serve a STALE row that still reads `active` after a revoke, silently
16
+ * defeating deny-new, so reads are leader-served `STREAM.MSG.GET` (never `DIRECT.GET`) AND the
17
+ * store is proved primary/un-mirrored/non-evicting at bind. Any read/shape/parse failure DENIES
18
+ * the connect; there is no file-only fallback.
19
+ */
20
+ import type { NatsConnection } from "@nats-io/transport-node";
21
+ import type { ValidatedUserToken } from "./token.js";
22
+ /**
23
+ * The connect reader's SCOPED credential grant (SPEC 13.9). Read-only over BOTH authority stores,
24
+ * plus the bind-time shape-proof INFO and a connection-scoped reply inbox. It holds NO write, NO
25
+ * consumer create/update/delete, NO `DIRECT.GET`, and no account-wide inbox.
26
+ *
27
+ * D32 residual (named, not pretend-confined): `$JS.API.STREAM.MSG.GET.<stream>` is body-selected —
28
+ * broker ACLs cannot scope it to only `cred.`/head subjects, so this grant is a space-wide read of
29
+ * credential METADATA (holderPrincipal/state/chain/lifecycleUid/exp; NO secret key material rides
30
+ * the row, confirmed against the closed `CredentialLedgerRow` schema) plus the caller-selected-reply
31
+ * injection class every raw `MSG.GET` profile carries. Acceptable for this trusted, bounded-lived,
32
+ * short-exp+renewed infra reader; a genuinely ledgered infra-reader family is the deferred
33
+ * infra-mint work (a self-minted reader is revoked by stopping the service / rotating the seed, not
34
+ * an agent-barrier revoke — it is not a managed-agent or endpoint descendant).
35
+ */
36
+ export declare function authConnectReaderGrants(space: string, connId: string): {
37
+ publish: string[];
38
+ subscribe: string[];
39
+ };
40
+ /** A sealed, brand-checked connect reader (a hand-assembled context never authorizes). */
41
+ export interface ConnectReader {
42
+ readonly space: string;
43
+ }
44
+ /**
45
+ * Open (or re-open, on reconnect) the connect reader on a DATA-account connection, shape-proving
46
+ * BOTH stores at bind (SPEC 13.12) — primary/un-mirrored/non-evicting, and the auth store
47
+ * additionally `allow_direct=false`. Call this on every (re)bind: a dropped reader reconnecting
48
+ * could land on a mirror/follower, so the primary-store proof must re-run before it serves any
49
+ * connect read again. A store that cannot be proved never serves.
50
+ */
51
+ export declare function openConnectReader(nc: NatsConnection, space: string): Promise<ConnectReader>;
52
+ /**
53
+ * The connect-time deny-new check (SPEC 13.1). Assumes the caller (the ledger connect authorizer)
54
+ * has ALREADY run the actor-row + scope + lifecycle-equality checks; this adds the credential-row
55
+ * liveness proof. THROWS to deny; returns on allow. Fail-closed throughout: absent/revoked/expired/
56
+ * mismatched rows and every read failure deny.
57
+ *
58
+ * 1. the bearer MUST carry `act.credentialId` (grammar-valid from validateUserToken). Claimless =>
59
+ * deny (deny-new needs the claim; a pre-R1 bearer re-exchanges). A view bearer is NOT exempt;
60
+ * a bearer merely lacking `act.view` is a normal agent connect and is fine.
61
+ * 2. leader-read `cred.<lifecycleUid>.<credid>`; require it exist, be `active`, be unexpired under
62
+ * the service clock, and bind holderPrincipal `<owner>.<actor>` + lifecycleUid + credentialId
63
+ * with NO normalization slack.
64
+ * 3. root-issued credential (`sourceChain === ["root"]`): ALSO leader-read the lifecycle head and
65
+ * require it `active` with `currentCredentialId === credid`, so a SUPERSEDED root issuance is
66
+ * denied even if its old row still reads active (release-last keeps these consistent; head
67
+ * equality is the belt). A descendant (session/handle) leaf row is sufficient here: revoking an
68
+ * ancestor revokes the whole `cred.<uid>.>` family / `bysrc` chain and verified-evicts before
69
+ * the operation reports success, and after the leaf CAS deny-new holds.
70
+ */
71
+ export declare function authorizeConnectCredential(reader: ConnectReader, t: ValidatedUserToken, now: () => number): Promise<void>;
72
+ //# sourceMappingURL=connect-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-reader.d.ts","sourceRoot":"","sources":["../src/connect-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAK9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAiBjH;AAQD,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmBjG;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC/H"}