@cotal-ai/core 0.57.0 → 0.59.0
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.
- package/dist/acls.d.ts +7 -0
- package/dist/acls.d.ts.map +1 -1
- package/dist/acls.js +10 -0
- package/dist/acls.js.map +1 -1
- package/dist/auth-provider.d.ts +25 -1
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/auth-provider.js.map +1 -1
- package/dist/backup-config.d.ts +10 -2
- package/dist/backup-config.d.ts.map +1 -1
- package/dist/backup-config.js +12 -3
- package/dist/backup-config.js.map +1 -1
- package/dist/broker-tls.d.ts +15 -0
- package/dist/broker-tls.d.ts.map +1 -1
- package/dist/broker-tls.js +6 -6
- package/dist/broker-tls.js.map +1 -1
- package/dist/checkpoint-answer.d.ts +24 -0
- package/dist/checkpoint-answer.d.ts.map +1 -1
- package/dist/checkpoint-answer.js +45 -4
- package/dist/checkpoint-answer.js.map +1 -1
- package/dist/command.d.ts +4 -3
- package/dist/command.d.ts.map +1 -1
- package/dist/command.js +20 -4
- package/dist/command.js.map +1 -1
- package/dist/connector-setup.d.ts +27 -0
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/connector.d.ts +12 -0
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js.map +1 -1
- package/dist/endpoint-envelope.js +2 -2
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-grants.d.ts.map +1 -1
- package/dist/endpoint-grants.js +4 -2
- package/dist/endpoint-grants.js.map +1 -1
- package/dist/endpoint-handle.d.ts.map +1 -1
- package/dist/endpoint-handle.js +5 -2
- package/dist/endpoint-handle.js.map +1 -1
- package/dist/endpoint-reconcile.d.ts +5 -1
- package/dist/endpoint-reconcile.d.ts.map +1 -1
- package/dist/endpoint-reconcile.js +43 -21
- package/dist/endpoint-reconcile.js.map +1 -1
- package/dist/endpoint-service.d.ts +25 -0
- package/dist/endpoint-service.d.ts.map +1 -1
- package/dist/endpoint-service.js +25 -25
- package/dist/endpoint-service.js.map +1 -1
- package/dist/endpoint-subjects.d.ts +6 -3
- package/dist/endpoint-subjects.d.ts.map +1 -1
- package/dist/endpoint-subjects.js +5 -5
- package/dist/endpoint-subjects.js.map +1 -1
- package/dist/endpoint-verbs.d.ts +1 -1
- package/dist/endpoint-verbs.d.ts.map +1 -1
- package/dist/endpoint-verbs.js +2 -2
- package/dist/endpoint-verbs.js.map +1 -1
- package/dist/endpoint.d.ts +114 -8
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +393 -45
- package/dist/endpoint.js.map +1 -1
- package/dist/evict.d.ts +27 -0
- package/dist/evict.d.ts.map +1 -1
- package/dist/evict.js +95 -38
- package/dist/evict.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/issued-authority.d.ts +0 -2
- package/dist/issued-authority.d.ts.map +1 -1
- package/dist/issued-authority.js +0 -2
- package/dist/issued-authority.js.map +1 -1
- package/dist/kv-scan.d.ts +5 -2
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js +103 -27
- package/dist/kv-scan.js.map +1 -1
- package/dist/launch-artifacts.d.ts +42 -0
- package/dist/launch-artifacts.d.ts.map +1 -0
- package/dist/launch-artifacts.js +155 -0
- package/dist/launch-artifacts.js.map +1 -0
- package/dist/lease.d.ts +5 -0
- package/dist/lease.d.ts.map +1 -1
- package/dist/lease.js.map +1 -1
- package/dist/liveness.d.ts +180 -0
- package/dist/liveness.d.ts.map +1 -0
- package/dist/liveness.js +114 -0
- package/dist/liveness.js.map +1 -0
- package/dist/members.d.ts +10 -0
- package/dist/members.d.ts.map +1 -1
- package/dist/members.js +28 -1
- package/dist/members.js.map +1 -1
- package/dist/membership-feed.d.ts.map +1 -1
- package/dist/membership-feed.js +19 -6
- package/dist/membership-feed.js.map +1 -1
- package/dist/provision.d.ts +24 -11
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +154 -66
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +193 -3
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js +104 -0
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/run-host.d.ts +66 -2
- package/dist/run-host.d.ts.map +1 -1
- package/dist/run-journal.d.ts +14 -6
- package/dist/run-journal.d.ts.map +1 -1
- package/dist/run-journal.js +14 -14
- package/dist/run-journal.js.map +1 -1
- package/dist/run-record.d.ts +24 -5
- package/dist/run-record.d.ts.map +1 -1
- package/dist/run-record.js +20 -0
- package/dist/run-record.js.map +1 -1
- package/dist/runtime.d.ts +6 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/schema-profile.d.ts.map +1 -1
- package/dist/schema-profile.js +4 -3
- package/dist/schema-profile.js.map +1 -1
- package/dist/streams.d.ts +59 -3
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +226 -21
- package/dist/streams.js.map +1 -1
- package/dist/subjects.d.ts +52 -7
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +62 -3
- package/dist/subjects.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/provision.js
CHANGED
|
@@ -18,7 +18,8 @@ import { TTL_RECONCILE_CANARY_KEY, ttlBuckets } from "./streams.js";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { decode, encodeOperator, encodeAccount, encodeUser, fmtCreds, } from "@nats-io/jwt";
|
|
20
20
|
import { createOperator, createAccount, fromPublic, fromSeed } from "@nats-io/nkeys";
|
|
21
|
-
import { token, spacePrefix, chatSubject, assertValidChannel, channelInAllow, principalKey, parsePrincipalKey, deprovisionTargetPrincipal, principalTags, assertInboxConnId, DEV_OWNER, unicastSubject, anycastSubject, controlServiceSubject, CONTROL_DELIVERY, CONTROL_DELIVERY_ADMIN, artifactBucket, objectStoreStream, chatStream, dmStream, taskStream, dlvStream, inboxStream, chatHistDurable, dmDurable, taskDurable, dlvDurable, presenceBucket, channelBucket, membersBucket, aclBucket, aclKey, assertLifecycleToken, membershipBucket, deliveryBucket, managerBucket, MANAGER_LEASE_KEY, MANAGER_RENEWAL_LEASE_KEY, connzRequestSubject, accountConnectSubject, accountDisconnectSubject, MEMBERSHIP_INBOX_PREFIX, FANOUT_DURABLE, INBOX_READER_DURABLE, } from "./subjects.js";
|
|
21
|
+
import { token, spacePrefix, chatSubject, assertValidChannel, channelInAllow, principalKey, parsePrincipalKey, deprovisionTargetPrincipal, principalTags, assertInboxConnId, DEV_OWNER, unicastSubject, anycastSubject, controlServiceSubject, CONTROL_DELIVERY, CONTROL_DELIVERY_ADMIN, artifactBucket, objectStoreStream, chatStream, dmStream, taskStream, dlvStream, inboxStream, chatHistDurable, dmDurable, taskDurable, dlvDurable, presenceBucket, channelBucket, membersBucket, aclBucket, aclKey, memberKey, assertLifecycleToken, membershipBucket, deliveryBucket, managerBucket, MANAGER_LEASE_KEY, MANAGER_RENEWAL_LEASE_KEY, connzRequestSubject, accountConnectSubject, accountDisconnectSubject, MEMBERSHIP_INBOX_PREFIX, FANOUT_DURABLE, INBOX_READER_DURABLE, livenessSubject, livenessServeFilter, livenessReplyGrant, } from "./subjects.js";
|
|
22
|
+
import { LIVENESS_PLANES } from "./liveness.js";
|
|
22
23
|
import { epCallerGrantRows, epServeGrantRows, epBaselineGrantRows, baselineCallerCapabilities, spawnCallerCapabilities, runCallerCapabilities, epRequestGrantRows, operatorInstrumentCapabilities, instanceOnlyManagerCapabilities, epDescribeAllGrantRow, BASELINE_LIFECYCLE_ENDPOINT, } from "./endpoint-grants.js";
|
|
23
24
|
import { assertServeGrantMintable, finalizeServeIssuance } from "./endpoint-service.js";
|
|
24
25
|
import { effectsBindGrants, poolOwnerBindGrants, goalWriterGrants, sessionLedgerGrants, epAuthBucket, sessionsBucket, epcStreamName, endpointPlaneStreamNames, eptReqStreamName, eptStreamName, hardenedAuthorityStreamNames, timerWriterDurable, timerWriterGrants } from "./endpoint-binding.js";
|
|
@@ -492,7 +493,7 @@ async function releaseIssuance(space, profile, pr, opts, perms, exp, credentialI
|
|
|
492
493
|
export async function mintPublicUserJwt(auth, publicId, profile, opts) {
|
|
493
494
|
if (!/^U[A-Z2-7]{55}$/.test(publicId))
|
|
494
495
|
throw new Error("mintPublicUserJwt: publicId must be a user nkey");
|
|
495
|
-
if (!["remote-manager", "endpoint-serve", "goal-writer", "session-ledger", "session-serving", "retirement-requester"].includes(profile))
|
|
496
|
+
if (!["remote-manager", "endpoint-serve", "goal-writer", "session-ledger", "session-serving", "retirement-requester", "run-driver", "run-mediator", "run-operator"].includes(profile))
|
|
496
497
|
throw new Error(`mintPublicUserJwt: profile "${profile}" is not part of the closed remote manager protocol`);
|
|
497
498
|
const pr = {
|
|
498
499
|
owner: opts.principal?.owner ?? DEV_OWNER,
|
|
@@ -532,6 +533,9 @@ export async function mintPublicUserJwt(auth, publicId, profile, opts) {
|
|
|
532
533
|
}
|
|
533
534
|
return { jwt, exp: validDates.exp };
|
|
534
535
|
}
|
|
536
|
+
/** The profiles whose credential carries caller rails, and can therefore be minted as an
|
|
537
|
+
* issuance (SPEC 13.15). Everything else is infrastructure or a one-shot with no rail to bind. */
|
|
538
|
+
const ISSUABLE_PROFILES = new Set(["agent", "control-caller-privileged", "control-caller-admin", "deployer"]);
|
|
535
539
|
/** Build the NATS user permission object for a profile: a default-deny allow-list scoped to
|
|
536
540
|
* exactly what each profile does. Every profile is now enumerated least-privilege — the former
|
|
537
541
|
* allow-all `manager` is gone (its roles split across supervisor/provisioner/operator/purger and the
|
|
@@ -545,9 +549,6 @@ export async function mintPublicUserJwt(auth, publicId, profile, opts) {
|
|
|
545
549
|
* owner + ledger actor + the per-connection ephemeral nkey; the static/dev mint passes
|
|
546
550
|
* `{owner:"local", actor:<id>, connId:<id>}` via {@link principalOf}. EXPORTED so the callout's injected
|
|
547
551
|
* `permissionsFor` hook can feed a validated principal straight into the same builder. */
|
|
548
|
-
/** The profiles whose credential carries caller rails, and can therefore be minted as an
|
|
549
|
-
* issuance (SPEC 13.15). Everything else is infrastructure or a one-shot with no rail to bind. */
|
|
550
|
-
const ISSUABLE_PROFILES = new Set(["agent", "control-caller-privileged", "control-caller-admin", "deployer"]);
|
|
551
552
|
export function permissionsFor(profile, space, pr, opts) {
|
|
552
553
|
// Guard the connId BEFORE any profile builds `_INBOX_<connId>.>`: in user mode connId is a client-
|
|
553
554
|
// chosen nonce (untrusted), so a metacharacter here would escalate the inbox grant to every inbox.
|
|
@@ -602,33 +603,28 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
602
603
|
throw new Error("permissionsFor: retirement-requester requires opts.retirementRequester.target ({owner, actor, lifecycleUid} of the ONE incarnation this credential may retire) - since #350 the handle target rides the SUBJECT and is grant-pinned, so it can no longer be supplied in the request body");
|
|
603
604
|
const { owner, actor, uid, target } = rr;
|
|
604
605
|
const caller = { owner, actor, uid };
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
//
|
|
612
|
-
// authority. It is used because `handle` is the ONLY mode with arity 3 - every other mode
|
|
613
|
-
// resolves against the CURRENT mapping, which is the wrong semantics for retiring a NAMED
|
|
614
|
-
// incarnation - and because the reader-facing invariant ("the validator re-checks only
|
|
615
|
-
// currency") IS honoured: the auth handler fresh-checks the triple against the lifecycle
|
|
616
|
-
// mapping and refuses a stale incarnation.
|
|
617
|
-
// What is genuinely absent is delegation lineage and artifact revocation. There is no
|
|
618
|
-
// independent issuer/holder boundary on this one-shot path whose revocation would change this
|
|
619
|
-
// requester's authority, which is why the deviation is accepted rather than papered over with
|
|
620
|
-
// a manufactured artifact. NAMED RESIDUAL: Cotal #399 tracks making this genuinely
|
|
621
|
-
// redemption-shaped if real artifact semantics are ever intended.
|
|
622
|
-
// The `handle` target is grant-pinned, so this credential can ask to retire the ONE
|
|
623
|
-
// incarnation it was minted for and nothing else - the same confinement the pre-#350 grant got
|
|
624
|
-
// from naming an exact `ctl` subject, now covering the TARGET as well as the caller. The nonce
|
|
625
|
-
// is the only wildcard token (§13.9): a bounded per-request suffix, not an addressing widening.
|
|
606
|
+
// The row is minted in `exact` mode: a privileged, non-redemption mode where this profile
|
|
607
|
+
// pins the arity-3 target triple directly from the manager's own coordinates under root
|
|
608
|
+
// authority, rather than through `handle`'s issuer-signed redemption provenance. The target is
|
|
609
|
+
// still confinement-pinned, so this credential can ask to retire the ONE incarnation it was
|
|
610
|
+
// minted for and nothing else - the same confinement the pre-#350 grant got from naming an
|
|
611
|
+
// exact `ctl` subject, now covering the TARGET as well as the caller. The nonce is the only
|
|
612
|
+
// wildcard token (§13.9): a bounded per-request suffix, not an addressing widening.
|
|
626
613
|
const rows = epRequestGrantRows(space, {
|
|
627
614
|
endpoint: AUTH_ENDPOINT,
|
|
628
615
|
command: EP_CMD_RETIRE_LIFECYCLE,
|
|
629
|
-
target: { mode: "
|
|
616
|
+
target: { mode: "exact", tOwner: target.owner, tActor: target.actor, tUid: target.lifecycleUid },
|
|
630
617
|
}, caller);
|
|
631
|
-
return {
|
|
618
|
+
return {
|
|
619
|
+
pub: {
|
|
620
|
+
allow: [
|
|
621
|
+
...rows,
|
|
622
|
+
epDescribeAllGrantRow(space, caller),
|
|
623
|
+
`$JS.API.DIRECT.GET.${epcStreamName(space)}.${spacePrefix(space)}.epc.>`,
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
sub: { allow: [epCallerReplyFilter(space, caller), `_INBOX_${pr.connId}.>`] },
|
|
627
|
+
};
|
|
632
628
|
}
|
|
633
629
|
if (profile === "manager-service")
|
|
634
630
|
throw new Error('permissionsFor: "manager-service" is not a generic profile; use the typed remote manager authority protocol');
|
|
@@ -848,6 +844,20 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
848
844
|
// daemon (NOT the manager). The reply rides this same subtree (`ctl.delivery.<o>.<a>.reply.<n>`, in
|
|
849
845
|
// sub.allow below) so the daemon can answer without broad inbox-publish — see CONTROL_DELIVERY.
|
|
850
846
|
controlServiceSubject(space, CONTROL_DELIVERY, pr.owner, pr.actor),
|
|
847
|
+
// THE PEER-READABLE LIVENESS PROBE (#1577) — one publish row per plane, each pinned to THIS
|
|
848
|
+
// agent's own principal slots, so a peer asks as itself and can never forge a probe from
|
|
849
|
+
// another. This is the row that makes liveness askable by a NON-OWNER: without it, the only
|
|
850
|
+
// hypothesis a peer could form for any failure was "my credentials are wrong", because the
|
|
851
|
+
// subjects that answer "is the manager alive" were owner-only.
|
|
852
|
+
//
|
|
853
|
+
// WHAT THIS DOES NOT GRANT, which is the half that keeps it from being a back door. It is
|
|
854
|
+
// PUBLISH ON A REQUEST SUBJECT, not a read of anything. The agent gains NO grant on the manager
|
|
855
|
+
// bucket (still absent by deliberate omission, below) and NO new read of the delivery bucket.
|
|
856
|
+
// The answer it can obtain is one enum per plane, because the responder reduces the lease row
|
|
857
|
+
// before replying and the row never crosses the wire. It cannot subscribe the SERVE filter
|
|
858
|
+
// either: `live.<plane>.*.*` is not in `sub.allow`, so an agent cannot impersonate a responder
|
|
859
|
+
// and answer a peer's probe with a comforting lie.
|
|
860
|
+
...LIVENESS_PLANES.map((plane) => livenessSubject(space, plane, pr.owner, pr.actor)),
|
|
851
861
|
// JetStream control plane — scoped to this agent's own streams/durables.
|
|
852
862
|
"$JS.API.INFO",
|
|
853
863
|
// STREAM.INFO: CHAT (join watermark, recall drop-marker, channel-list counts — a documented
|
|
@@ -1022,9 +1032,15 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
1022
1032
|
// Replies to this agent's durable join/leave/list requests ride `ctl.delivery.<o>.<a>.>` (NOT the
|
|
1023
1033
|
// per-id _INBOX), so the scoped delivery daemon can answer without broad inbox-publish.
|
|
1024
1034
|
const deliveryReplies = `${controlServiceSubject(space, CONTROL_DELIVERY, pr.owner, pr.actor)}.>`;
|
|
1035
|
+
// Liveness answers (#1577) ride the probe's OWN request subtree, exactly like the delivery
|
|
1036
|
+
// control replies above and for the same reason: the responder answers without needing broad
|
|
1037
|
+
// inbox-publish. One row per plane, pinned to this agent's principal, so it can hear answers to
|
|
1038
|
+
// ITS OWN probes and nothing else — it cannot subscribe a peer's reply lane and harvest answers
|
|
1039
|
+
// addressed elsewhere.
|
|
1040
|
+
const livenessReplies = LIVENESS_PLANES.map((plane) => `${livenessSubject(space, plane, pr.owner, pr.actor)}.>`);
|
|
1025
1041
|
// Manager control replies ride the v0.4 ep reply rail (in `epSub`, keyed on the caller triple) —
|
|
1026
1042
|
// the `ctl.<tier>.<id>.reply.>` subtrees are gone with the ctl rail (1d).
|
|
1027
|
-
return { pub: { allow: pubAllow, deny: pubDeny }, sub: { allow: [inbox, deliveryReplies, ...subChat, ...epSub] } };
|
|
1043
|
+
return { pub: { allow: pubAllow, deny: pubDeny }, sub: { allow: [inbox, deliveryReplies, ...livenessReplies, ...subChat, ...epSub] } };
|
|
1028
1044
|
}
|
|
1029
1045
|
/** One seat's manager control view. It carries no agent messaging or delivery surface, and every
|
|
1030
1046
|
* manager request, including describe, is pinned to one exact registered instance. */
|
|
@@ -1103,6 +1119,15 @@ function supervisorPermissions(space, pr) {
|
|
|
1103
1119
|
// files it requests `reloadCreds` here so adoption is an explicit, auditable event. Self-scoped
|
|
1104
1120
|
// request subject (its own owner+actor slots), bounded reply subtree in sub.allow below.
|
|
1105
1121
|
controlServiceSubject(space, CONTROL_DELIVERY_ADMIN, pr.owner, pr.actor),
|
|
1122
|
+
// LIVENESS REPLIES for the MANAGER plane (#1577), bounded to the `.reply.` leaf under a
|
|
1123
|
+
// caller's own request subject. The supervisor is the natural responder for this plane: it
|
|
1124
|
+
// is the process that HOLDS the manager lease, so it is the one that can answer the
|
|
1125
|
+
// question honestly without anyone else being granted a read of the lease row.
|
|
1126
|
+
//
|
|
1127
|
+
// REPLIES ONLY, exactly like the delivery daemon's `ctl.delivery.*.*.reply.>`. The row stops
|
|
1128
|
+
// at the leaf, so the supervisor cannot publish to the liveness REQUEST subjects themselves
|
|
1129
|
+
// and therefore cannot forge a probe from a peer.
|
|
1130
|
+
livenessReplyGrant(space, "manager"),
|
|
1106
1131
|
// #1694: a POINT READ of the delivery lease row, so the store-identity challenge can verify
|
|
1107
1132
|
// that the process which answered the queue-grouped admin rail is the holder that actually
|
|
1108
1133
|
// reloads the standing credentials. Without it the challenge can only report what some
|
|
@@ -1121,7 +1146,16 @@ function supervisorPermissions(space, pr) {
|
|
|
1121
1146
|
// Own reply inbox + the delivery-admin reply subtree for its OWN requests. NO chat/inst/dlv
|
|
1122
1147
|
// native sub (the supervisor reads no feed), NO manager control-tier serve (1d: that moved to
|
|
1123
1148
|
// the endpoint-serve credential), NO broad `$JS.>`/`$KV.>` (the residual-2 read/admin path is gone).
|
|
1124
|
-
|
|
1149
|
+
//
|
|
1150
|
+
// PLUS the manager-plane liveness SERVE filter (#1577): `live.manager.*.*`, queue-grouped, so
|
|
1151
|
+
// any credentialed peer's presence probe reaches the lease holder. This is a serve
|
|
1152
|
+
// subscription on a presence-only rail, not a widening of what the supervisor may read: the
|
|
1153
|
+
// request body is empty and the subject carries only the caller's own principal.
|
|
1154
|
+
allow: [
|
|
1155
|
+
`_INBOX_${pr.connId}.>`,
|
|
1156
|
+
`${controlServiceSubject(space, CONTROL_DELIVERY_ADMIN, pr.owner, pr.actor)}.>`,
|
|
1157
|
+
livenessServeFilter(space, "manager"),
|
|
1158
|
+
],
|
|
1125
1159
|
},
|
|
1126
1160
|
};
|
|
1127
1161
|
}
|
|
@@ -1148,6 +1182,12 @@ function remoteManagerPermissions(space, pr, pin) {
|
|
|
1148
1182
|
const gateKey = epgateKey("manager", iid);
|
|
1149
1183
|
const credPrefix = epcredFamilyPrefix("manager", iid);
|
|
1150
1184
|
const repairKey = eprepairKey("manager", iid);
|
|
1185
|
+
// The SUPERVISOR actor of the pair, which is the one that runs `Manager.start()` and therefore
|
|
1186
|
+
// the only one that binds the manager plane's liveness responder (`service.ts` hands
|
|
1187
|
+
// `credentials.supervisor` to the manager; the executor gets the instance-scoped registration
|
|
1188
|
+
// surface). The liveness rows below are pinned to it so the executor does not carry authority to
|
|
1189
|
+
// answer a probe it never serves.
|
|
1190
|
+
const isSupervisorActor = pin.actor === `manager_${iid}`;
|
|
1151
1191
|
const recordKeys = [
|
|
1152
1192
|
recordSpecKey(RECORD_KINDS.svc, ["manager", iid]),
|
|
1153
1193
|
recordStatusKey(RECORD_KINDS.svc, ["manager", iid]),
|
|
@@ -1182,9 +1222,35 @@ function remoteManagerPermissions(space, pr, pin) {
|
|
|
1182
1222
|
`$JS.API.CONSUMER.DELETE.KV_${AUTH}.>`,
|
|
1183
1223
|
...recordKeys.map((key) => `$JS.API.DIRECT.GET.KV_${REC}.$KV.${REC}.${key}`),
|
|
1184
1224
|
`$JS.API.STREAM.MSG.GET.KV_${REC}`,
|
|
1225
|
+
// LIVENESS REPLIES for the MANAGER plane (#1577), bounded to the `.reply.` leaf under a
|
|
1226
|
+
// caller's own request subject — the same row and the same bound as the local supervisor
|
|
1227
|
+
// profile holds.
|
|
1228
|
+
//
|
|
1229
|
+
// WHY THIS PROFILE NEEDS IT AT ALL. `Manager.start()` binds the manager plane's liveness
|
|
1230
|
+
// responder unconditionally, and a remote manager runs the same `start()` under this
|
|
1231
|
+
// credential. Without the serve subscription below the bind is denied, and a peer probing
|
|
1232
|
+
// the manager plane then gets the broker's own no-responders answer, which this surface
|
|
1233
|
+
// grades `unbound` — a definite verdict about the plane, produced by a gap in a credential,
|
|
1234
|
+
// while the remote manager is bound and serving. A surface that exists to stop a failure to
|
|
1235
|
+
// find out being rendered as a finding must not render its own missing grant as one.
|
|
1236
|
+
//
|
|
1237
|
+
// REPLIES ONLY. The row stops at the leaf, so a remote manager cannot publish to the
|
|
1238
|
+
// liveness REQUEST subjects and therefore cannot forge a probe that appears to come from a
|
|
1239
|
+
// peer, exactly as the supervisor row cannot.
|
|
1240
|
+
...(isSupervisorActor ? [livenessReplyGrant(space, "manager")] : []),
|
|
1241
|
+
],
|
|
1242
|
+
},
|
|
1243
|
+
sub: {
|
|
1244
|
+
allow: [
|
|
1245
|
+
`_INBOX_${pr.connId}.>`,
|
|
1246
|
+
// The manager-plane liveness SERVE filter (#1577): `live.manager.*.*`, queue-grouped, so a
|
|
1247
|
+
// credentialed peer's presence probe reaches this instance. A serve subscription on a
|
|
1248
|
+
// presence-only rail and not a widening of what a remote manager may read: the request body
|
|
1249
|
+
// is empty and the subject carries only the caller's own principal. The MANAGER plane only —
|
|
1250
|
+
// this credential cannot serve `delivery`, so each plane still answers for itself.
|
|
1251
|
+
...(isSupervisorActor ? [livenessServeFilter(space, "manager")] : []),
|
|
1185
1252
|
],
|
|
1186
1253
|
},
|
|
1187
|
-
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1188
1254
|
};
|
|
1189
1255
|
}
|
|
1190
1256
|
/** The human-CLI OPERATOR permission set (closure (ii), residual 2) — the ephemeral key the headless
|
|
@@ -1723,17 +1789,6 @@ function provisionerPermissions(space, pr) {
|
|
|
1723
1789
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1724
1790
|
};
|
|
1725
1791
|
}
|
|
1726
|
-
/** The ephemeral, LIFECYCLE-PINNED §13.1 state-write permission set for the STATIC manager's
|
|
1727
|
-
* lifecycle executor (Unit B). One credential per lifecycle OPERATION (activation, terminal,
|
|
1728
|
-
* renewal ledger append): every grant names exactly ONE incarnation's keys — the alias head,
|
|
1729
|
-
* the uid reservation, the manager slot row, the issuance gate, and the `cred.<uid>.>` ledger
|
|
1730
|
-
* family — so a leaked executor cred can move one incarnation's state machine and nothing else.
|
|
1731
|
-
*
|
|
1732
|
-
* Reads: records reads ride the keyed Direct Get form (the key is ON the subject, so the read
|
|
1733
|
-
* grant stays key-pinned); the auth store is leader-served (`allow_direct=false`), so its reads
|
|
1734
|
-
* are body-selected `STREAM.MSG.GET` — stream-scoped, NOT key-scoped (the requested key rides
|
|
1735
|
-
* the PAYLOAD, which a subject grant cannot see). NAMED RESIDUAL: for its one-shot lifetime the
|
|
1736
|
-
* executor can READ (never write) other rows in the auth store. */
|
|
1737
1792
|
/** The ISSUER permission set (SPEC 13.15): value-writes on the evidence store (evidence, attempt,
|
|
1738
1793
|
* source index: create-only and CAS rows, keyed by generation) and on the accepted-row store
|
|
1739
1794
|
* (create-only, keyed by the client's token); the leader-served point read on the evidence
|
|
@@ -1780,6 +1835,17 @@ function runAdmitterPermissions(space, pr, pin) {
|
|
|
1780
1835
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1781
1836
|
};
|
|
1782
1837
|
}
|
|
1838
|
+
/** The ephemeral, LIFECYCLE-PINNED §13.1 state-write permission set for the STATIC manager's
|
|
1839
|
+
* lifecycle executor (Unit B). One credential per lifecycle OPERATION (activation, terminal,
|
|
1840
|
+
* renewal ledger append): every grant names exactly ONE incarnation's keys — the alias head,
|
|
1841
|
+
* the uid reservation, the manager slot row, the issuance gate, and the `cred.<uid>.>` ledger
|
|
1842
|
+
* family — so a leaked executor cred can move one incarnation's state machine and nothing else.
|
|
1843
|
+
*
|
|
1844
|
+
* Reads: records reads ride the keyed Direct Get form (the key is ON the subject, so the read
|
|
1845
|
+
* grant stays key-pinned); the auth store is leader-served (`allow_direct=false`), so its reads
|
|
1846
|
+
* are body-selected `STREAM.MSG.GET` — stream-scoped, NOT key-scoped (the requested key rides
|
|
1847
|
+
* the PAYLOAD, which a subject grant cannot see). NAMED RESIDUAL: for its one-shot lifetime the
|
|
1848
|
+
* executor can READ (never write) other rows in the auth store. */
|
|
1783
1849
|
function lifecycleExecutorPermissions(space, pr, pin) {
|
|
1784
1850
|
const REC = recordsBucket(space), AUTH = epAuthBucket(space);
|
|
1785
1851
|
// ALL keys DERIVED here from the pin coordinates — the slot key is `staticSlotKey(owner, alias)`,
|
|
@@ -1815,19 +1881,6 @@ function lifecycleExecutorPermissions(space, pr, pin) {
|
|
|
1815
1881
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1816
1882
|
};
|
|
1817
1883
|
}
|
|
1818
|
-
/** The ephemeral, ENDPOINT-INSTANCE-PINNED endpoint-serve executor permission set (P2 item 1,
|
|
1819
|
-
* 1a-serve): the manager mints this per registration/serve-mint op and drives the endpoint
|
|
1820
|
-
* registration barrier's `epgate` CAS + the mint fence's `epcred` stage/revoke THROUGH it — never
|
|
1821
|
-
* its standing seed/supervisor connection (critic #1's manager-specific "no seed shortcut"). Every
|
|
1822
|
-
* WRITE is key-pinned to exactly ONE (endpoint, instanceId): the gate `epgate.<ep>.<iid>`, its
|
|
1823
|
-
* serving ledger family `epcred.<ep>.<iid>.>`, and the registration's two records keys (the
|
|
1824
|
-
* instance's `svc` spec + the endpoint's governance head — `registerServiceInstance` drives the
|
|
1825
|
-
* slot-take/promote over this same connection). A leaked/mis-constructed executor can move exactly
|
|
1826
|
-
* one endpoint instance's serve state and nothing else. The auth store is `allow_direct=false`, so
|
|
1827
|
-
* reads are leader-served `STREAM.MSG.GET` (stream-scoped, NOT key-scoped — the key rides the
|
|
1828
|
-
* payload); enumeration of the epcred family rides an ordered `keys()` consumer. NAMED RESIDUAL:
|
|
1829
|
-
* for its one-shot lifetime the executor can READ (never write) other auth rows — endpoint/
|
|
1830
|
-
* credential metadata, no bearer bytes; every WRITE stays key-pinned. */
|
|
1831
1884
|
/** The SELF-MEDIATED goal-writer profile (P2 item 2, spawn-as-action): exactly
|
|
1832
1885
|
* {@link goalWriterGrants} for ITS endpoint — the goal bind + terminal facts, the goal-record KV
|
|
1833
1886
|
* writes, and the leader-served fencing reads — plus the connection-scoped reply inbox. Disjoint
|
|
@@ -1885,6 +1938,19 @@ function sessionLedgerPermissions(space, pr) {
|
|
|
1885
1938
|
const g = sessionLedgerGrants(space, pr.connId);
|
|
1886
1939
|
return { pub: { allow: g.publish }, sub: { allow: g.subscribe } };
|
|
1887
1940
|
}
|
|
1941
|
+
/** The ephemeral, ENDPOINT-INSTANCE-PINNED endpoint-serve executor permission set (P2 item 1,
|
|
1942
|
+
* 1a-serve): the manager mints this per registration/serve-mint op and drives the endpoint
|
|
1943
|
+
* registration barrier's `epgate` CAS + the mint fence's `epcred` stage/revoke THROUGH it — never
|
|
1944
|
+
* its standing seed/supervisor connection (critic #1's manager-specific "no seed shortcut"). Every
|
|
1945
|
+
* WRITE is key-pinned to exactly ONE (endpoint, instanceId): the gate `epgate.<ep>.<iid>`, its
|
|
1946
|
+
* serving ledger family `epcred.<ep>.<iid>.>`, and the registration's two records keys (the
|
|
1947
|
+
* instance's `svc` spec + the endpoint's governance head — `registerServiceInstance` drives the
|
|
1948
|
+
* slot-take/promote over this same connection). A leaked/mis-constructed executor can move exactly
|
|
1949
|
+
* one endpoint instance's serve state and nothing else. The auth store is `allow_direct=false`, so
|
|
1950
|
+
* reads are leader-served `STREAM.MSG.GET` (stream-scoped, NOT key-scoped — the key rides the
|
|
1951
|
+
* payload); enumeration of the epcred family rides an ordered `keys()` consumer. NAMED RESIDUAL:
|
|
1952
|
+
* for its one-shot lifetime the executor can READ (never write) other auth rows — endpoint/
|
|
1953
|
+
* credential metadata, no bearer bytes; every WRITE stays key-pinned. */
|
|
1888
1954
|
function endpointServeExecutorPermissions(space, pr, pin) {
|
|
1889
1955
|
const AUTH = epAuthBucket(space), REC = recordsBucket(space);
|
|
1890
1956
|
// DERIVED from (endpoint, instanceId) via the core builders — never a caller literal.
|
|
@@ -1987,11 +2053,19 @@ function deprovisionerPermissions(space, pr, deprovisionTarget) {
|
|
|
1987
2053
|
// replayed teardown is broker-DENIED there (SPEC 13.1 / Appendix "deprovisioner").
|
|
1988
2054
|
`$JS.API.CONSUMER.DELETE.${DM}.${dmDurable(t.owner, t.actor, t.lifecycleUid)}`,
|
|
1989
2055
|
`$JS.API.CONSUMER.DELETE.${DLV}.${dlvDurable(t.owner, t.actor, t.lifecycleUid)}`,
|
|
2056
|
+
`$JS.API.CONSUMER.INFO.${DM}.${dmDurable(t.owner, t.actor, t.lifecycleUid)}`,
|
|
2057
|
+
`$JS.API.CONSUMER.INFO.${DLV}.${dlvDurable(t.owner, t.actor, t.lifecycleUid)}`,
|
|
1990
2058
|
// Purge the target lifecycle's read-ACL row (own-target exact key only — the reader then treats
|
|
1991
2059
|
// it as an unknown owner). `kvm.open` binds the pre-created bucket; the purge rides
|
|
1992
2060
|
// `$KV.<aclBucket>.<key>`.
|
|
1993
2061
|
`$JS.API.STREAM.INFO.KV_${aclBucket(space)}`,
|
|
2062
|
+
`$JS.API.DIRECT.GET.KV_${aclBucket(space)}.$KV.${aclBucket(space)}.${aclKey(target.key, t.lifecycleUid)}`,
|
|
1994
2063
|
`$KV.${aclBucket(space)}.${aclKey(target.key, t.lifecycleUid)}`,
|
|
2064
|
+
// Purge the target lifecycle's durable membership rows, one exact key per named concrete
|
|
2065
|
+
// channel. The key embeds the uid, so a successor's row is unreachable by name.
|
|
2066
|
+
...(t.memberChannels.length > 0 ? [`$JS.API.STREAM.INFO.KV_${membersBucket(space)}`] : []),
|
|
2067
|
+
...t.memberChannels.map((ch) => `$JS.API.DIRECT.GET.KV_${membersBucket(space)}.$KV.${membersBucket(space)}.${memberKey(ch, target.key, t.lifecycleUid)}`),
|
|
2068
|
+
...t.memberChannels.map((ch) => `$KV.${membersBucket(space)}.${memberKey(ch, target.key, t.lifecycleUid)}`),
|
|
1995
2069
|
],
|
|
1996
2070
|
},
|
|
1997
2071
|
// Replies only: the CONSUMER.DELETE PubAcks + KV purge ack land on the per-connection inbox. NO chat/DM/ctl
|
|
@@ -2050,6 +2124,10 @@ function deliveryPermissions(space, pr) {
|
|
|
2050
2124
|
`$JS.API.CONSUMER.MSG.NEXT.${INBOX}.${INBOX_READER_DURABLE}`,
|
|
2051
2125
|
`$JS.API.CONSUMER.DELETE.${INBOX}.${INBOX_READER_DURABLE}`,
|
|
2052
2126
|
`$JS.ACK.${INBOX}.${INBOX_READER_DURABLE}.>`,
|
|
2127
|
+
// The reader removes an entry addressed to a retired lifecycle (its ACL row is tombstoned), so the
|
|
2128
|
+
// store does not keep what no reader can ever deliver. Nothing new in substance: the reader can
|
|
2129
|
+
// already ack any INBOX entry away.
|
|
2130
|
+
`$JS.API.STREAM.MSG.DELETE.${INBOX}`,
|
|
2053
2131
|
"$JS.FC.>", // ordered-consumer flow control
|
|
2054
2132
|
// Reads: presence (@mention resolve) + channel registry (delivery class) + members + ACL (re-auth).
|
|
2055
2133
|
...kvRead(PKV), ...kvRead(CHKV), ...kvRead(MKV), ...kvRead(AKV),
|
|
@@ -2095,11 +2173,21 @@ function deliveryPermissions(space, pr) {
|
|
|
2095
2173
|
// The privileged delivery-admin rail (D5 slice 5/6): same replies-only shape. Requests reach the
|
|
2096
2174
|
// daemon on the sub below; only the supervisor cred can PUBLISH them (nats-server is the boundary).
|
|
2097
2175
|
`${p}.ctl.delivery-admin.*.*.reply.>`,
|
|
2176
|
+
// LIVENESS REPLIES for the DELIVERY plane (#1577) — same replies-only shape as the two control
|
|
2177
|
+
// rails above. The daemon is the only process that can grade its own responder honestly: it
|
|
2178
|
+
// knows its own incarnation, so a predecessor's `ready:true` corpse in the lease classifies as
|
|
2179
|
+
// stale rather than as its own health. Bounded to the `.reply.` leaf, so it cannot publish to
|
|
2180
|
+
// the request subjects and forge a probe from a peer.
|
|
2181
|
+
livenessReplyGrant(space, "delivery"),
|
|
2098
2182
|
];
|
|
2099
2183
|
const sub = [
|
|
2100
2184
|
`_INBOX_${pr.connId}.>`,
|
|
2101
2185
|
`${p}.ctl.delivery.*.*`, // serve the delivery control service (queue-grouped; owner+actor caller slots)
|
|
2102
2186
|
`${p}.ctl.delivery-admin.*.*`, // serve the privileged admin rail (reloadCreds; eviction executor next)
|
|
2187
|
+
// Serve the delivery plane's liveness probe (#1577), queue-grouped. Presence-only: the handler
|
|
2188
|
+
// reduces the lease row to one enum before replying, so this subscription lets the daemon ANSWER
|
|
2189
|
+
// a peer without any peer gaining a read of the lease.
|
|
2190
|
+
livenessServeFilter(space, "delivery"),
|
|
2103
2191
|
];
|
|
2104
2192
|
return { pub: { allow: pub }, sub: { allow: sub } };
|
|
2105
2193
|
}
|
|
@@ -2201,17 +2289,6 @@ export async function mintConnectionEvictorCreds(auth, identity, opts = {}) {
|
|
|
2201
2289
|
const creds = fmtCreds(userJwt, fromSeed(new TextEncoder().encode(identity.seed)));
|
|
2202
2290
|
return new TextDecoder().decode(creds);
|
|
2203
2291
|
}
|
|
2204
|
-
/** Render the `nats-server` config that trusts ONE broker operator and serves N spaces' accounts via
|
|
2205
|
-
* the in-config MEMORY resolver.
|
|
2206
|
-
*
|
|
2207
|
-
* Broker trust (operator + system account) comes from `broker` and has exactly one owner; the
|
|
2208
|
-
* per-space data accounts are listed in `spaces`. Every space account is asserted to be signed by
|
|
2209
|
-
* THIS broker's operator before it is preloaded: rendering a foreign-signed account would either
|
|
2210
|
-
* refuse broker boot or, worse, advertise a tenant the broker cannot actually authenticate.
|
|
2211
|
-
*
|
|
2212
|
-
* NOTE (W4): the MEMORY resolver is one static whole-broker map, so every mutation rewrites all of
|
|
2213
|
-
* it. Concurrent add/remove of spaces needs a broker-authoritative inventory with generation/CAS
|
|
2214
|
-
* and atomic promotion above this function; this renderer is deliberately pure. */
|
|
2215
2292
|
/**
|
|
2216
2293
|
* Render the config for an OPEN (no-auth) broker.
|
|
2217
2294
|
*
|
|
@@ -2268,6 +2345,17 @@ function renderTlsBlock(transport) {
|
|
|
2268
2345
|
}
|
|
2269
2346
|
`;
|
|
2270
2347
|
}
|
|
2348
|
+
/** Render the `nats-server` config that trusts ONE broker operator and serves N spaces' accounts via
|
|
2349
|
+
* the in-config MEMORY resolver.
|
|
2350
|
+
*
|
|
2351
|
+
* Broker trust (operator + system account) comes from `broker` and has exactly one owner; the
|
|
2352
|
+
* per-space data accounts are listed in `spaces`. Every space account is asserted to be signed by
|
|
2353
|
+
* THIS broker's operator before it is preloaded: rendering a foreign-signed account would either
|
|
2354
|
+
* refuse broker boot or, worse, advertise a tenant the broker cannot actually authenticate.
|
|
2355
|
+
*
|
|
2356
|
+
* NOTE (W4): the MEMORY resolver is one static whole-broker map, so every mutation rewrites all of
|
|
2357
|
+
* it. Concurrent add/remove of spaces needs a broker-authoritative inventory with generation/CAS
|
|
2358
|
+
* and atomic promotion above this function; this renderer is deliberately pure. */
|
|
2271
2359
|
export function serverConfig(broker, spaces, opts) {
|
|
2272
2360
|
if (!spaces.length)
|
|
2273
2361
|
throw new Error("serverConfig: at least one space account is required");
|