@cotal-ai/core 0.69.0 → 0.70.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.
- package/dist/auth-provider.d.ts +9 -2
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/auth-provider.js +10 -0
- package/dist/auth-provider.js.map +1 -1
- package/dist/channels.js +1 -1
- package/dist/channels.js.map +1 -1
- package/dist/connector-setup.d.ts +9 -5
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/contract-manifest.d.ts +20 -0
- package/dist/contract-manifest.d.ts.map +1 -0
- package/dist/contract-manifest.js +28 -0
- package/dist/contract-manifest.js.map +1 -0
- package/dist/endpoint-action.d.ts +3 -0
- package/dist/endpoint-action.d.ts.map +1 -1
- package/dist/endpoint-action.js +8 -6
- package/dist/endpoint-action.js.map +1 -1
- package/dist/endpoint-binding.d.ts.map +1 -1
- package/dist/endpoint-binding.js +5 -0
- package/dist/endpoint-binding.js.map +1 -1
- package/dist/endpoint-cluster.d.ts.map +1 -1
- package/dist/endpoint-cluster.js +9 -4
- package/dist/endpoint-cluster.js.map +1 -1
- package/dist/endpoint-contract-store.d.ts +2 -18
- package/dist/endpoint-contract-store.d.ts.map +1 -1
- package/dist/endpoint-contract-store.js +3 -19
- package/dist/endpoint-contract-store.js.map +1 -1
- package/dist/endpoint-envelope.d.ts +6 -2
- package/dist/endpoint-envelope.d.ts.map +1 -1
- package/dist/endpoint-envelope.js +19 -2
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-error.d.ts +13 -0
- package/dist/endpoint-error.d.ts.map +1 -1
- package/dist/endpoint-error.js +15 -0
- package/dist/endpoint-error.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +5 -2
- package/dist/endpoint-invoke.d.ts.map +1 -1
- package/dist/endpoint-invoke.js +16 -6
- package/dist/endpoint-invoke.js.map +1 -1
- package/dist/endpoint-records.d.ts +2 -1
- package/dist/endpoint-records.d.ts.map +1 -1
- package/dist/endpoint-records.js +1 -1
- package/dist/endpoint-records.js.map +1 -1
- package/dist/endpoint-serve.d.ts.map +1 -1
- package/dist/endpoint-serve.js +4 -3
- package/dist/endpoint-serve.js.map +1 -1
- package/dist/endpoint.d.ts +9 -4
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +47 -37
- package/dist/endpoint.js.map +1 -1
- package/dist/evict.d.ts.map +1 -1
- package/dist/evict.js +2 -1
- package/dist/evict.js.map +1 -1
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +5 -0
- package/dist/identity.js.map +1 -1
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js +1 -6
- package/dist/kv-scan.js.map +1 -1
- package/dist/provision.d.ts +23 -24
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +112 -116
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +22 -2
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js +23 -15
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/resolve.d.ts +15 -2
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +15 -2
- package/dist/resolve.js.map +1 -1
- package/dist/run-driver-grants.d.ts.map +1 -1
- package/dist/run-driver-grants.js +4 -1
- package/dist/run-driver-grants.js.map +1 -1
- package/dist/run-journal.d.ts.map +1 -1
- package/dist/run-journal.js +17 -2
- package/dist/run-journal.js.map +1 -1
- package/dist/run-record.d.ts +6 -8
- package/dist/run-record.d.ts.map +1 -1
- package/dist/run-record.js.map +1 -1
- package/dist/schema-profile.d.ts +11 -0
- package/dist/schema-profile.d.ts.map +1 -1
- package/dist/schema-profile.js +13 -2
- package/dist/schema-profile.js.map +1 -1
- package/dist/secret-fs.d.ts +4 -3
- package/dist/secret-fs.d.ts.map +1 -1
- package/dist/secret-fs.js +10 -11
- package/dist/secret-fs.js.map +1 -1
- package/dist/streams.d.ts +2 -2
- package/dist/streams.js +2 -2
- package/package.json +5 -1
package/dist/provision.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* `@nats-io/jwt`, so there is no dependency on the external `nsc` CLI and the signing
|
|
7
7
|
* key stays in one place (whoever holds {@link SpaceAuth.account.signingSeed}).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* The static mint is out of band: `cotal up` creates the space's trust material
|
|
10
10
|
* once and writes a `nats-server` config (operator + system account + MEMORY resolver);
|
|
11
|
-
* `cotal mint` and the manager load that material and mint per-agent creds files.
|
|
12
|
-
*
|
|
11
|
+
* `cotal mint` and the manager load that material and mint per-agent creds files. The auth
|
|
12
|
+
* callout mints at connect time through the same {@link permissionsFor}.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Every credential kind is classified in {@link CREDENTIAL_LIFETIMES}: its lifetime class, default
|
|
15
|
+
* expiry and renewal owner.
|
|
16
16
|
*/
|
|
17
17
|
import { TTL_RECONCILE_CANARY_KEY, ttlBuckets } from "./streams.js";
|
|
18
18
|
import { join } from "node:path";
|
|
@@ -31,7 +31,7 @@ import { transferReaderGrants, transferWriterGrants } from "./transfer.js";
|
|
|
31
31
|
import { recordsBucket, recordSpecKey, recordStatusKey, recordAtomicKey, RECORD_KINDS, GOVERN_HEAD } from "./endpoint-records.js";
|
|
32
32
|
import { lifecycleHeadKey, uidReservationKey, issuanceGateKey, staticSlotKey, STATIC_SLOT_PREFIX, epgateKey, epcredFamilyPrefix, eprepairKey } from "./lifecycle-state.js";
|
|
33
33
|
import { rawDigest } from "./canonical.js";
|
|
34
|
-
import { credsClaims } from "./identity.js";
|
|
34
|
+
import { credsClaims, isUserNkey } from "./identity.js";
|
|
35
35
|
import { backupProfilePermissions, restoreProfilePermissions, } from "./backup.js";
|
|
36
36
|
const FIVE_MINUTES = 5 * 60;
|
|
37
37
|
/** The broker's pre-auth control-line cap, in bytes (`max_control_line` in the generated config —
|
|
@@ -57,7 +57,7 @@ export const CONNECT_ENVELOPE_OVERHEAD_BYTES = 512;
|
|
|
57
57
|
* (issue #375's review finding: six distinct 4096-char channels, each individually accepted,
|
|
58
58
|
* minted a JWT the broker would silently drop). */
|
|
59
59
|
export const MAX_MINTED_JWT_BYTES = MAX_CONTROL_LINE_BYTES - CONNECT_ENVELOPE_OVERHEAD_BYTES;
|
|
60
|
-
/** Bounded lifetime for `standing-renewable` credentials whose renewal owner is ONLINE
|
|
60
|
+
/** Bounded lifetime for `standing-renewable` credentials whose renewal owner is ONLINE:
|
|
61
61
|
* the holder (or its launcher) re-mints at 75% of the lifetime via the endpoint's creds-source seam,
|
|
62
62
|
* so a copied cred is broker-dead within a day while renewal never involves an operator. 24h keeps
|
|
63
63
|
* the remaining-25% loud-failure window at ~6h — wide enough to notice and repair before expiry. */
|
|
@@ -66,19 +66,19 @@ export const STANDING_RENEWABLE_TTL_SEC = 24 * 60 * 60;
|
|
|
66
66
|
* evictor). They are NOT online-renewable (the $SYS seed dies at end of `up`), so this exp is the
|
|
67
67
|
* credential-death horizon: a copied observer/evictor cred becomes broker-dead after it, and the
|
|
68
68
|
* operator is expected to have run a coordinated system-account rotation + broker restart within it
|
|
69
|
-
* (the doctor surface warns ahead
|
|
69
|
+
* (the doctor surface warns ahead). 30 days balances "copied cred eventually dies" against
|
|
70
70
|
* a comfortable monthly rotation cadence; tune here as one named knob. */
|
|
71
71
|
export const ROTATION_RENEWED_TTL_SEC = 30 * 24 * 60 * 60;
|
|
72
|
-
/**
|
|
72
|
+
/** The credential lifetime matrix. This is intentionally centralized so every new mint profile must classify its
|
|
73
73
|
* credential-death behavior instead of silently inheriting non-expiring static creds. */
|
|
74
74
|
export const CREDENTIAL_LIFETIMES = {
|
|
75
75
|
agent: { class: "mixed", note: "manager children, foreground spawn/join, and cotal mint static outputs all use this profile; split or repair flow required before default exp" },
|
|
76
76
|
"manager-caller": { class: "mixed", note: "short-lived user-auth view bound to the bearer expiry and one manager instance" },
|
|
77
77
|
observer: { class: "static-operator-managed", note: "out-of-band dashboard/audit credential from cotal mint" },
|
|
78
78
|
admin: { class: "static-operator-managed", note: "out-of-band elevated dashboard/audit credential from cotal mint" },
|
|
79
|
-
supervisor: { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "manager's always-on endpoint; the manager holds the DATA seed and self-remints via the endpoint creds source
|
|
80
|
-
delivery: { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "server-side Plane-3 daemon; seed-less - the manager re-signs .cotal/delivery.creds for the SAME nkey, requests delivery-admin reloadCreds for explicit adoption, and the endpoint source re-read is only a backstop
|
|
81
|
-
"membership-rw": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "membership feed writer; seed-less - the manager re-signs the membership-rw.creds store key for the SAME nkey, the feed adopts it on a 75% preflight-proven renewal timer (its active self-heal), and delivery-admin reloadCreds is the explicit adoption on top
|
|
79
|
+
supervisor: { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "manager's always-on endpoint; the manager holds the DATA seed and self-remints via the endpoint creds source" },
|
|
80
|
+
delivery: { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "server-side Plane-3 daemon; seed-less - the manager re-signs .cotal/delivery.creds for the SAME nkey, requests delivery-admin reloadCreds for explicit adoption, and the endpoint source re-read is only a backstop" },
|
|
81
|
+
"membership-rw": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "membership feed writer; seed-less - the manager re-signs the membership-rw.creds store key for the SAME nkey, the feed adopts it on a 75% preflight-proven renewal timer (its active self-heal), and delivery-admin reloadCreds is the explicit adoption on top" },
|
|
82
82
|
provisioner: { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "setup/spawn provisioning window only" },
|
|
83
83
|
deprovisioner: { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "target-pinned teardown window only" },
|
|
84
84
|
"retirement-requester": { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "one despawn's retirement request window; request+reply only" },
|
|
@@ -96,10 +96,10 @@ export const CREDENTIAL_LIFETIMES = {
|
|
|
96
96
|
"control-caller-admin": { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "stop/attach admin control call" },
|
|
97
97
|
deployer: { class: "one-shot", note: "manifest deploy spans planning/launch/ledger; needs near-expiry guard or remint before default exp" },
|
|
98
98
|
"endpoint-serve": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "per-instance endpoint serve credential (SPEC 13.9); the managing authority re-mints on renewal and on takeover (new epoch), and the 13.1 barrier revokes the superseded one" },
|
|
99
|
-
"goal-writer": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "self-mediated goal-writer for spawn-as-action
|
|
100
|
-
"session-caller": { class: "one-shot", defaultTtlSeconds: 24 * 60 * 60, note: "per-session console/CLI caller cred
|
|
101
|
-
"session-serving": { class: "one-shot", defaultTtlSeconds: 24 * 60 * 60, note: "per-session SERVING cred
|
|
102
|
-
"session-ledger": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "manager's session LEDGER
|
|
99
|
+
"goal-writer": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "self-mediated goal-writer for spawn-as-action; the manager re-mints for the SAME nkey on renewal, disjoint from the serve credential" },
|
|
100
|
+
"session-caller": { class: "one-shot", defaultTtlSeconds: 24 * 60 * 60, note: "per-session console/CLI caller cred: rails-only for ONE §13.6 session; TTL-BOUND to the session (the face mints with expiresAt = the session exp; the 24h default is the SESSION_GRANT_MAX_TTL ceiling, never a standing lifetime); NEVER renewed - a new session mints a new cred" },
|
|
101
|
+
"session-serving": { class: "one-shot", defaultTtlSeconds: 24 * 60 * 60, note: "per-session SERVING cred: rails-only for ONE §13.6 session, the mirror of session-caller with the directions swapped; minted at redemption and TTL-BOUND to the session (the 24h default is the SESSION_GRANT_MAX_TTL ceiling, never a standing lifetime); NEVER renewed - a new session mints a new cred, and the session's terminal revokes this one by name" },
|
|
102
|
+
"session-ledger": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "manager's session LEDGER: the dedicated sessions-bucket `session.<id>` rows and NOTHING else - no session rail of any shape. Standing because SPEC 13.6 makes it the durable revocation authority that must survive the serving endpoint; the manager re-mints for the SAME nkey on the half-TTL loop (the goal-writer precedent)" },
|
|
103
103
|
"run-driver": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "one workflow run's driver, per takeover attempt (SPEC 14.6): the hosting manager mints it when it takes the run over and re-mints for the SAME nkey on renewal; a new takeover mints a new one" },
|
|
104
104
|
"run-mediator": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "manager", note: "trusted workflow host operations, bound to one run and attempt; kept on the hosting process's connection" },
|
|
105
105
|
"run-operator": { class: "one-shot", defaultTtlSeconds: 60, note: "one served run-status / run-ps / run-answer call (SPEC 14.3): the hosting manager mints it per call on its own connection, never the serve rails; 60s bounds a copied cred to a minute" },
|
|
@@ -107,10 +107,10 @@ export const CREDENTIAL_LIFETIMES = {
|
|
|
107
107
|
"run-admitter": { class: "one-shot", defaultTtlSeconds: 60, note: "one hosted run's admission record create, or its revocation marker (SPEC 14.8); 60s bounds a copied cred to a minute" },
|
|
108
108
|
"transfer-writer": { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "one carried resume per CLI call: one object's chunk and meta subjects in one instance's transfer bucket" },
|
|
109
109
|
"transfer-reader": { class: "one-shot", defaultTtlSeconds: FIVE_MINUTES, note: "one transcript-receive or sweep over the minting instance's own transfer bucket" },
|
|
110
|
-
"endpoint-evictor": { class: "one-shot", defaultTtlSeconds: 60, note: "one re-registration's verify-evict window
|
|
110
|
+
"endpoint-evictor": { class: "one-shot", defaultTtlSeconds: 60, note: "one re-registration's verify-evict window: a scoped delivery-admin caller that kicks+verifies the SUPERSEDED serve family before the epoch advances; 60s bounds a copied cred to a minute" },
|
|
111
111
|
"remote-manager": { class: "standing-renewable", defaultTtlSeconds: STANDING_RENEWABLE_TTL_SEC, renewalOwner: "auth-service", note: "the scoped remote manager lifecycle: own lease/presence plus same-owner agent provisioning; issued only by the typed supervise protocol, never by cotal mint or a raw view/profile string" },
|
|
112
112
|
"membership-observer": { class: "rotation-renewed", defaultTtlSeconds: ROTATION_RENEWED_TTL_SEC, renewalOwner: "system-account rotation", note: "$SYS-account CONNZ observer; NOT online-renewable ($SYS seed dies at `up`) - bounded exp, renewed only by rotateSystemAccount + broker restart; doctor warns near expiry" },
|
|
113
|
-
"connection-evictor": { class: "rotation-renewed", defaultTtlSeconds: ROTATION_RENEWED_TTL_SEC, renewalOwner: "system-account rotation", note: "$SYS-account KICK-only live-eviction cred
|
|
113
|
+
"connection-evictor": { class: "rotation-renewed", defaultTtlSeconds: ROTATION_RENEWED_TTL_SEC, renewalOwner: "system-account rotation", note: "$SYS-account KICK-only live-eviction cred; same rotation-renewed posture as the observer" },
|
|
114
114
|
};
|
|
115
115
|
// RUNTIME integrity (the afa715b class): the matrix and every policy freeze at module load
|
|
116
116
|
// (a post-import `defaultTtlSeconds = undefined` would otherwise mint a NON-EXPIRING credential
|
|
@@ -198,8 +198,8 @@ export function stripSpaceAuth(auth) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
/** Rotate the DATA-account signing key and re-issue the data-account JWT so the old data signer is no
|
|
201
|
-
* longer trusted by the broker once it loads the returned auth. This does NOT rotate the system account
|
|
202
|
-
* persisted `membership-observer` creds
|
|
201
|
+
* longer trusted by the broker once it loads the returned auth. This does NOT rotate the system account
|
|
202
|
+
* (that is {@link rotateSystemAccount}): persisted `membership-observer` creds stay valid. */
|
|
203
203
|
export async function rotateDataAccountSigningKey(auth) {
|
|
204
204
|
if (!auth.operator.seed || !auth.account.seed)
|
|
205
205
|
throw new Error("rotateDataAccountSigningKey: full operator/account seed material is required (a stripped signer cannot rotate trust)");
|
|
@@ -291,7 +291,7 @@ export async function createSpaceAccountAuth(broker, space) {
|
|
|
291
291
|
}
|
|
292
292
|
/** Generate a fresh operator → account(+signing key) → system-account chain for a space.
|
|
293
293
|
* The single-space composition of {@link createBrokerAuth} + {@link createSpaceAccountAuth}: one
|
|
294
|
-
* broker whose only tenant is this space
|
|
294
|
+
* broker whose only tenant is this space. */
|
|
295
295
|
export async function createSpaceAuth(space) {
|
|
296
296
|
const broker = await createBrokerAuth(space);
|
|
297
297
|
const spaceAccount = await createSpaceAccountAuth(broker, space);
|
|
@@ -372,7 +372,7 @@ export async function provisionAgentDurables(provisioner, pr, opts = {}) {
|
|
|
372
372
|
// reissueAcl — rides THIS mint: allowSubscribe was just baked into the user JWT above. Separate
|
|
373
373
|
// from commitAcl so an ordinary registry writer cannot raise the ceiling by passing a flag
|
|
374
374
|
// (SPEC §9.6). Not crypto-bound to the JWT bytes — process discipline that this call stays next
|
|
375
|
-
// to the mint
|
|
375
|
+
// to the mint.
|
|
376
376
|
if (opts.durableMembership !== false) {
|
|
377
377
|
await provisioner.reissueAcl(principalKey(pr.owner, pr.actor).key, uid, allowSubscribe);
|
|
378
378
|
}
|
|
@@ -492,7 +492,7 @@ async function releaseIssuance(space, profile, pr, opts, perms, exp, credentialI
|
|
|
492
492
|
* lifetime builders. It signs with the space account's signing key alone, so a host that holds
|
|
493
493
|
* only that key passes no placeholder broker trust. */
|
|
494
494
|
export async function mintPublicUserJwt(auth, publicId, profile, opts) {
|
|
495
|
-
if (
|
|
495
|
+
if (!isUserNkey(publicId))
|
|
496
496
|
throw new Error("mintPublicUserJwt: publicId must be a user nkey");
|
|
497
497
|
if (!["remote-manager", "endpoint-serve", "goal-writer", "session-ledger", "session-serving", "retirement-requester", "run-driver", "run-mediator", "run-operator", "transfer-reader"].includes(profile))
|
|
498
498
|
throw new Error(`mintPublicUserJwt: profile "${profile}" is not part of the closed remote manager protocol`);
|
|
@@ -540,7 +540,7 @@ const ISSUABLE_PROFILES = new Set(["agent", "control-caller-privileged", "contro
|
|
|
540
540
|
/** Build the NATS user permission object for a profile: a default-deny allow-list scoped to
|
|
541
541
|
* exactly what each profile does. Every profile is now enumerated least-privilege — the former
|
|
542
542
|
* allow-all `manager` is gone (its roles split across supervisor/provisioner/operator/purger and the
|
|
543
|
-
*
|
|
543
|
+
* CLI-surface profiles). Subject/stream/durable names come from the shared builders so the ACLs
|
|
544
544
|
* can't drift from the wire layout.
|
|
545
545
|
*
|
|
546
546
|
* PRINCIPAL-PARAMETERIZED + MODE-AGNOSTIC (owner+actor grammar): `pr` carries the owner+actor wire
|
|
@@ -572,11 +572,11 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
572
572
|
if (profile === "membership-rw")
|
|
573
573
|
return membershipRwPermissions(space, pr); // scoped graph-feed reader/writer
|
|
574
574
|
if (profile === "supervisor")
|
|
575
|
-
return supervisorPermissions(space, pr); // always-on daemon
|
|
575
|
+
return supervisorPermissions(space, pr); // always-on daemon
|
|
576
576
|
if (profile === "provisioner")
|
|
577
|
-
return provisionerPermissions(space, pr); // ephemeral onboarding authority
|
|
577
|
+
return provisionerPermissions(space, pr); // ephemeral onboarding authority
|
|
578
578
|
if (profile === "deprovisioner") {
|
|
579
|
-
// Ephemeral, TARGET-PINNED teardown
|
|
579
|
+
// Ephemeral, TARGET-PINNED teardown — the counterpart to `provisioner`. The target is a
|
|
580
580
|
// full principal dot-form for user-mode agents, or a bare static/dev actor id (keyed under
|
|
581
581
|
// DEV_OWNER) — see {@link deprovisionTargetPrincipal}.
|
|
582
582
|
if (!opts.deprovisionTarget)
|
|
@@ -584,7 +584,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
584
584
|
return deprovisionerPermissions(space, pr, opts.deprovisionTarget);
|
|
585
585
|
}
|
|
586
586
|
if (profile === "retirement-requester") {
|
|
587
|
-
// Ephemeral request+reply on the AUTH ENDPOINT rail
|
|
587
|
+
// Ephemeral request+reply on the AUTH ENDPOINT rail:
|
|
588
588
|
// publish EXACTLY this caller triple's own request subject for the ONE target it names +
|
|
589
589
|
// subscribe its own reply-plane filter and inbox. No store reads, no barrier/scanner/plane
|
|
590
590
|
// authority - the requester only asks; the auth plane holds every executing right and
|
|
@@ -654,7 +654,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
654
654
|
return { pub: { allow: g.publish }, sub: { allow: g.subscribe } };
|
|
655
655
|
}
|
|
656
656
|
if (profile === "endpoint-evictor") {
|
|
657
|
-
//
|
|
657
|
+
// A SCOPED delivery-admin caller for ONE re-registration's verify-evict.
|
|
658
658
|
// Publish EXACTLY this credential's OWN delivery-admin control subject + $JS.API.INFO; subscribe
|
|
659
659
|
// its own reply subtree + inbox. NO lease, presence, store read, consumer, KV, or executing
|
|
660
660
|
// right — the daemon does the $SYS scan/KICK (subject-gated authority, like the supervisor evictor
|
|
@@ -679,7 +679,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
679
679
|
return goalWriterPermissions(space, pr, opts.goalWriter);
|
|
680
680
|
}
|
|
681
681
|
if (profile === "purger")
|
|
682
|
-
return purgerPermissions(space, pr); // ephemeral history-purge
|
|
682
|
+
return purgerPermissions(space, pr); // ephemeral history-purge
|
|
683
683
|
if (profile === "backup") {
|
|
684
684
|
if (!opts.backup)
|
|
685
685
|
throw new Error("permissionsFor: backup requires opts.backup");
|
|
@@ -691,27 +691,27 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
691
691
|
return restoreProfilePermissions(space, pr.connId, opts.restore);
|
|
692
692
|
}
|
|
693
693
|
if (profile === "operator")
|
|
694
|
-
return operatorPermissions(space, pr); // human-CLI client (send/dm/ask)
|
|
694
|
+
return operatorPermissions(space, pr); // human-CLI client (send/dm/ask)
|
|
695
695
|
if (profile === "probe")
|
|
696
|
-
return probePermissions(pr); // connect-only liveness/auth preflight
|
|
696
|
+
return probePermissions(pr); // connect-only liveness/auth preflight
|
|
697
697
|
if (profile === "channel-writer")
|
|
698
|
-
return channelWriterPermissions(space, pr); // channel-registry writes
|
|
698
|
+
return channelWriterPermissions(space, pr); // channel-registry writes
|
|
699
699
|
if (profile === "channel-purger")
|
|
700
|
-
return channelPurgerPermissions(space, pr); // channel-writer + CHAT purge
|
|
700
|
+
return channelPurgerPermissions(space, pr); // channel-writer + CHAT purge
|
|
701
701
|
if (profile === "teardown")
|
|
702
|
-
return teardownPermissions(space, pr, opts.transferStreams ?? []); // sole STREAM.DELETE holder
|
|
702
|
+
return teardownPermissions(space, pr, opts.transferStreams ?? []); // sole STREAM.DELETE holder
|
|
703
703
|
if (profile === "control-caller-privileged")
|
|
704
|
-
return controlCallerPermissions(space, pr, "privileged", opts); // ps/start reads
|
|
704
|
+
return controlCallerPermissions(space, pr, "privileged", opts); // ps/start reads
|
|
705
705
|
if (profile === "control-caller-admin")
|
|
706
|
-
return controlCallerPermissions(space, pr, "admin", opts); // any-mode stop/attach
|
|
706
|
+
return controlCallerPermissions(space, pr, "admin", opts); // any-mode stop/attach
|
|
707
707
|
if (profile === "deployer")
|
|
708
|
-
return deployerPermissions(space, pr, opts.controlTier ?? "admin", opts); // spawn -f deploy authority (
|
|
708
|
+
return deployerPermissions(space, pr, opts.controlTier ?? "admin", opts); // spawn -f deploy authority (user-mode view rides privileged)
|
|
709
709
|
if (profile === "session-caller")
|
|
710
|
-
return sessionCallerPermissions(space, pr, opts.sessionCaller); // one §13.6 session's caller rails
|
|
710
|
+
return sessionCallerPermissions(space, pr, opts.sessionCaller); // one §13.6 session's caller rails
|
|
711
711
|
if (profile === "session-serving")
|
|
712
|
-
return sessionServingPermissions(space, pr, opts.sessionServing); // one §13.6 session's SERVING rails
|
|
712
|
+
return sessionServingPermissions(space, pr, opts.sessionServing); // one §13.6 session's SERVING rails
|
|
713
713
|
if (profile === "session-ledger")
|
|
714
|
-
return sessionLedgerPermissions(space, pr); // the dedicated session ledger, no rails
|
|
714
|
+
return sessionLedgerPermissions(space, pr); // the dedicated session ledger, no rails
|
|
715
715
|
if (profile === "run-driver") {
|
|
716
716
|
if (!opts.runDriver)
|
|
717
717
|
throw new Error("permissionsFor: run-driver requires opts.runDriver ({endpoint, runId, takeoverId, instanceId, epoch} of the ONE run and attempt it drives)");
|
|
@@ -839,7 +839,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
839
839
|
unicastSubject(space, "*", "*", pr.owner, pr.actor), // inst.*.*.<o>.<a> — DM any instance, as me
|
|
840
840
|
anycastSubject(space, "*", pr.owner, pr.actor), // svc.*.<o>.<a> — anycast any role, as me
|
|
841
841
|
// Self stop/despawn rides the v0.4 ep baseline (`stop` self-mode) — the manager `ctl` rail is
|
|
842
|
-
// deleted
|
|
842
|
+
// deleted. The delivery-daemon rail below is a separate service (Plane-3), kept.
|
|
843
843
|
// ctl.delivery.<o>.<a> — request a durable backstop join/leave/list from the SERVER-SIDE delivery
|
|
844
844
|
// daemon (NOT the manager). The reply rides this same subtree (`ctl.delivery.<o>.<a>.reply.<n>`, in
|
|
845
845
|
// sub.allow below) so the daemon can answer without broad inbox-publish — see CONTROL_DELIVERY.
|
|
@@ -927,7 +927,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
927
927
|
pubAllow.push(`$JS.API.STREAM.INFO.${TASK}`, `$JS.API.CONSUMER.INFO.${TASK}.${svcD}`, `$JS.API.CONSUMER.MSG.NEXT.${TASK}.${svcD}`, `$JS.ACK.${TASK}.${svcD}.>`);
|
|
928
928
|
}
|
|
929
929
|
// Spawn / admin capability control reach rides the v0.4 ep rows below (the manager `ctl` rail is
|
|
930
|
-
// deleted
|
|
930
|
+
// deleted) — the spawn set (owner-mode manager lifecycle) and the admin instrument set
|
|
931
931
|
// (any-mode + manager.admin family) are added to the ep caller rows, not a ctl subject.
|
|
932
932
|
// v0.4 endpoint rails (SPEC §13.9 caller rows). EVERY agent gets the Appendix-B BASELINE set
|
|
933
933
|
// (wildcard describe + delivery join/leave/list + self-mode lifecycle + the reply rail), keyed
|
|
@@ -975,7 +975,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
975
975
|
epSub.push(s);
|
|
976
976
|
}
|
|
977
977
|
if (opts.capabilities?.includes("admin"))
|
|
978
|
-
// The admin capability's ep mirror
|
|
978
|
+
// The admin capability's ep mirror: the v0.3 `ctl.<admin>`
|
|
979
979
|
// subject above grants the FULL admin-tier op reach, so its holder gets the admin instrument
|
|
980
980
|
// set on the ep rails — any-mode despawn/attach + the `manager.admin` family + the reads. In
|
|
981
981
|
// user mode this is the ledger `admin` scope arriving via the callout, the broker-enforced
|
|
@@ -1032,7 +1032,7 @@ export function permissionsFor(profile, space, pr, opts) {
|
|
|
1032
1032
|
// addressed elsewhere.
|
|
1033
1033
|
const livenessReplies = LIVENESS_PLANES.map((plane) => `${livenessSubject(space, plane, pr.owner, pr.actor)}.>`);
|
|
1034
1034
|
// Manager control replies ride the v0.4 ep reply rail (in `epSub`, keyed on the caller triple) —
|
|
1035
|
-
// the `ctl.<tier>.<id>.reply.>` subtrees are gone with the ctl rail
|
|
1035
|
+
// the `ctl.<tier>.<id>.reply.>` subtrees are gone with the ctl rail.
|
|
1036
1036
|
return { pub: { allow: pubAllow, deny: pubDeny }, sub: { allow: [inbox, deliveryReplies, ...livenessReplies, ...subChat, ...epSub] } };
|
|
1037
1037
|
}
|
|
1038
1038
|
/** One seat's manager control view. It carries no agent messaging or delivery surface, and every
|
|
@@ -1064,10 +1064,10 @@ function managerCallerPermissions(space, pr, opts) {
|
|
|
1064
1064
|
sub: { allow: [`_INBOX_${pr.connId}.>`, ...rows.sub] },
|
|
1065
1065
|
};
|
|
1066
1066
|
}
|
|
1067
|
-
/** The long-lived SUPERVISOR permission set
|
|
1068
|
-
* (`manager.ts` `this.ep`), carved down from the former allow-all `manager`.
|
|
1069
|
-
* STANDING
|
|
1070
|
-
*
|
|
1067
|
+
/** The long-lived SUPERVISOR permission set — the always-on manager daemon
|
|
1068
|
+
* (`manager.ts` `this.ep`), carved down from the former allow-all `manager`. Its breadth is
|
|
1069
|
+
* STANDING, so tightening it removes the always-on DM/DLV body-read AND the stream-admin tamper
|
|
1070
|
+
* from the one connection that never goes away. It does exactly three things — serve
|
|
1071
1071
|
* the three lifecycle control tiers (bounded replies), hold the singleton manager lease, and publish +
|
|
1072
1072
|
* watch presence (the roster) — and nothing else. Provisioning (DM/DLV/TASK consumer-create + ACL
|
|
1073
1073
|
* writes) moves to the EPHEMERAL `provisioner` (opened per-spawn); destructive history-purge moves to the
|
|
@@ -1080,7 +1080,7 @@ function managerCallerPermissions(space, pr, opts) {
|
|
|
1080
1080
|
function supervisorPermissions(space, pr) {
|
|
1081
1081
|
const PKV = `KV_${presenceBucket(space)}`, MKV = `KV_${managerBucket(space)}`;
|
|
1082
1082
|
const SUP_DLVKV = `KV_${deliveryBucket(space)}`;
|
|
1083
|
-
//
|
|
1083
|
+
// The supervisor no longer serves the manager control tiers — the manager's control surface
|
|
1084
1084
|
// is its v0.4 `service` endpoint, served on a SEPARATE connection under its own `endpoint-serve`
|
|
1085
1085
|
// credential (its rails are that credential's grant, not the supervisor's). The supervisor now
|
|
1086
1086
|
// holds only the lease, presence, and the ONE delivery-admin call.
|
|
@@ -1089,18 +1089,17 @@ function supervisorPermissions(space, pr) {
|
|
|
1089
1089
|
allow: [
|
|
1090
1090
|
"$JS.API.INFO",
|
|
1091
1091
|
// Per-instance manager liveness lease (managerBucket, pre-created at `cotal up`): OPEN-ONLY bind +
|
|
1092
|
-
// CAS this instance's own `lease.<instanceId>` key (acquire/renew/release) + read the subtree.
|
|
1093
|
-
//
|
|
1094
|
-
//
|
|
1095
|
-
//
|
|
1096
|
-
// DELETE, or PURGE.
|
|
1092
|
+
// CAS this instance's own `lease.<instanceId>` key (acquire/renew/release) + read the subtree. The
|
|
1093
|
+
// lease is per instance, so the write grant spans `lease.*` (every instance of this space
|
|
1094
|
+
// shares this one supervisor principal — the isolation between instances is the logical-id/CAS
|
|
1095
|
+
// boundary, not a cred boundary). NO STREAM.CREATE (pre-created), DELETE, or PURGE.
|
|
1097
1096
|
`$JS.API.STREAM.INFO.${MKV}`,
|
|
1098
1097
|
`$JS.API.STREAM.MSG.GET.${MKV}`, // readManagerLease (last_by_subj lease.*) + CAS-conflict kv.get
|
|
1099
1098
|
`$KV.${managerBucket(space)}.${MANAGER_LEASE_KEY}.*`, // this instance's lease.<id> key (create/update/delete = $KV publishes)
|
|
1100
1099
|
// #1634: the per-space daemon-credential renewal lease, the CAS that picks ONE renewal owner
|
|
1101
1100
|
// when several managers share the daemon's store. Same bucket, its own key outside `lease.*`.
|
|
1102
1101
|
`$KV.${managerBucket(space)}.${MANAGER_RENEWAL_LEASE_KEY}`,
|
|
1103
|
-
// Presence: publish OWN key + watch the roster. Own key only (no peer-key forge
|
|
1102
|
+
// Presence: publish OWN key + watch the roster. Own key only (no peer-key forge); no
|
|
1104
1103
|
// presence-stream purge/delete (no force-offline tamper). No presence kv.get (roster is the in-memory
|
|
1105
1104
|
// watch cache + sweep), so no STREAM.MSG.GET on presence.
|
|
1106
1105
|
`$KV.${presenceBucket(space)}.${principalKey(pr.owner, pr.actor).key}`,
|
|
@@ -1108,8 +1107,8 @@ function supervisorPermissions(space, pr) {
|
|
|
1108
1107
|
`$JS.API.CONSUMER.CREATE.${PKV}.>`, // kv.watch ordered consumer (roster)
|
|
1109
1108
|
`$JS.API.CONSUMER.INFO.${PKV}.>`,
|
|
1110
1109
|
"$JS.FC.>", // ordered-consumer flow control
|
|
1111
|
-
// The ONE control service the supervisor CALLS
|
|
1112
|
-
// admin rail — the manager is the
|
|
1110
|
+
// The ONE control service the supervisor CALLS: the delivery daemon's privileged
|
|
1111
|
+
// admin rail — the manager is the daemon's renewal owner, and after re-signing the daemon creds
|
|
1113
1112
|
// files it requests `reloadCreds` here so adoption is an explicit, auditable event. Self-scoped
|
|
1114
1113
|
// request subject (its own owner+actor slots), bounded reply subtree in sub.allow below.
|
|
1115
1114
|
controlServiceSubject(space, CONTROL_DELIVERY_ADMIN, pr.owner, pr.actor),
|
|
@@ -1138,8 +1137,8 @@ function supervisorPermissions(space, pr) {
|
|
|
1138
1137
|
},
|
|
1139
1138
|
sub: {
|
|
1140
1139
|
// Own reply inbox + the delivery-admin reply subtree for its OWN requests. NO chat/inst/dlv
|
|
1141
|
-
// native sub (the supervisor reads no feed), NO manager control-tier serve (
|
|
1142
|
-
// the endpoint-serve credential), NO broad `$JS.>`/`$KV
|
|
1140
|
+
// native sub (the supervisor reads no feed), NO manager control-tier serve (that is
|
|
1141
|
+
// the endpoint-serve credential's), NO broad `$JS.>`/`$KV.>`.
|
|
1143
1142
|
//
|
|
1144
1143
|
// PLUS the manager-plane liveness SERVE filter (#1577): `live.manager.*.*`, queue-grouped, so
|
|
1145
1144
|
// any credentialed peer's presence probe reaches the lease holder. This is a serve
|
|
@@ -1247,7 +1246,7 @@ function remoteManagerPermissions(space, pr, pin) {
|
|
|
1247
1246
|
},
|
|
1248
1247
|
};
|
|
1249
1248
|
}
|
|
1250
|
-
/** The human-CLI OPERATOR permission set
|
|
1249
|
+
/** The human-CLI OPERATOR permission set — the ephemeral key the headless
|
|
1251
1250
|
* client commands mint (`cotal send dm|msg|ask`, `cotal dm`, `personas list --running`, via
|
|
1252
1251
|
* `openTransient`). It does exactly what those do: POST as itself (chat/DM/anycast — self-scoped, can
|
|
1253
1252
|
* never forge another actor), and READ the public roster (presence) + the channel registry to resolve a
|
|
@@ -1290,7 +1289,7 @@ function operatorPermissions(space, pr) {
|
|
|
1290
1289
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1291
1290
|
};
|
|
1292
1291
|
}
|
|
1293
|
-
/** Connect-only PROBE
|
|
1292
|
+
/** Connect-only PROBE — the liveness/auth preflight (`preflight.ts preflightTarget`, minted on
|
|
1294
1293
|
* ~every CLI command that resolves a mesh). `probeConnect` opens a connection to prove the broker is up
|
|
1295
1294
|
* and the creds are accepted, then closes it — it performs NO pub/sub. So the tightest possible grant:
|
|
1296
1295
|
* deny ALL publish, subscribe only to the own reply inbox. A leaked probe cred can open a socket and do
|
|
@@ -1298,7 +1297,7 @@ function operatorPermissions(space, pr) {
|
|
|
1298
1297
|
function probePermissions(pr) {
|
|
1299
1298
|
return { pub: { deny: [">"] }, sub: { allow: [`_INBOX_${pr.connId}.>`] } };
|
|
1300
1299
|
}
|
|
1301
|
-
/** CHANNEL-WRITER
|
|
1300
|
+
/** CHANNEL-WRITER — edits the channel registry ONLY: `cotal channels set/default` and the
|
|
1302
1301
|
* `spawn -f` new-channel seed (`seedChannelRegistry`). It VALUE-writes `$KV.<channelBucket>` (a channel's
|
|
1303
1302
|
* config key) and read-before-writes it. NO stream data, NO other bucket, NO chat/DM — a leaked
|
|
1304
1303
|
* channel-writer can only rewrite channel config, never post, read a body, or tear a stream down. */
|
|
@@ -1319,7 +1318,7 @@ function channelWriterPermissions(space, pr) {
|
|
|
1319
1318
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1320
1319
|
};
|
|
1321
1320
|
}
|
|
1322
|
-
/** CHANNEL-PURGER
|
|
1321
|
+
/** CHANNEL-PURGER — the `cotal web` dashboard's ONLY write path: delete a channel
|
|
1323
1322
|
* (`clearChannel` = filtered `STREAM.PURGE.CHAT` to drop the channel's messages + a `$KV.<channelBucket>`
|
|
1324
1323
|
* key delete). Pre-minted once by `web` so the account signing seed falls out of scope; the dashboard's
|
|
1325
1324
|
* READ side runs on the separate read-only `admin` cred. = channel-writer + the scoped CHAT purge. */
|
|
@@ -1342,8 +1341,8 @@ function channelPurgerPermissions(space, pr) {
|
|
|
1342
1341
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1343
1342
|
};
|
|
1344
1343
|
}
|
|
1345
|
-
/** TEARDOWN
|
|
1346
|
-
*
|
|
1344
|
+
/** TEARDOWN — `cotal down -f` space teardown. The SOLE cred that keeps `STREAM.DELETE` (the
|
|
1345
|
+
* tamper verb). `down -f` is multi-step: `connectProbe` (presence-watch + channel-registry read)
|
|
1347
1346
|
* → invoke the manager's `ps` + any-mode `despawn` over the ep rails to politely stop the managed
|
|
1348
1347
|
* agents → `deleteChannels`
|
|
1349
1348
|
* (channel-registry key delete + CHAT purge) → `deleteSpace` (STREAM.DELETE every owned stream/bucket).
|
|
@@ -1352,7 +1351,7 @@ function channelPurgerPermissions(space, pr) {
|
|
|
1352
1351
|
* can delete a stream; a leaked teardown can wipe a space you own + stop its agents (that IS its job),
|
|
1353
1352
|
* nothing else. Minted ephemerally per teardown from the local trust material (same-checkout `down -f`). */
|
|
1354
1353
|
function teardownPermissions(space, pr, transferStreams) {
|
|
1355
|
-
// The ep-rail mirror of the admin deploy tier
|
|
1354
|
+
// The ep-rail mirror of the admin deploy tier: teardown reads `ps` and stops owned agents
|
|
1356
1355
|
// it did not spawn (any-mode despawn) - the admin instrument set. Lifecycle-keyed, so a uid is
|
|
1357
1356
|
// required at mint (fail-loud).
|
|
1358
1357
|
const ep = instrumentEpRows(space, pr, "admin");
|
|
@@ -1360,7 +1359,7 @@ function teardownPermissions(space, pr, transferStreams) {
|
|
|
1360
1359
|
const PKV = `KV_${presenceBucket(space)}`, CHKV = `KV_${channelBucket(space)}`;
|
|
1361
1360
|
// deleteSpace() deletes EVERY stream + KV bucket setup creates; each needs INFO (jsm existence)
|
|
1362
1361
|
// + DELETE. This is the ONLY cred that
|
|
1363
|
-
// holds STREAM.DELETE
|
|
1362
|
+
// holds STREAM.DELETE. This list and deleteSpace()'s own array must agree:
|
|
1364
1363
|
// a stream in one and not the other is either an undeletable leak or a grant for nothing.
|
|
1365
1364
|
const del = [
|
|
1366
1365
|
CHAT, dmStream(space), taskStream(space), inboxStream(space), dlvStream(space),
|
|
@@ -1390,7 +1389,7 @@ function teardownPermissions(space, pr, transferStreams) {
|
|
|
1390
1389
|
`$JS.API.CONSUMER.INFO.${CHKV}.>`,
|
|
1391
1390
|
"$JS.FC.>", // ordered-consumer flow control
|
|
1392
1391
|
// Stop the managed agents over the v0.4 ep rails only (ps + any-mode despawn) — the admin
|
|
1393
|
-
// instrument set. The manager `ctl` rail is deleted
|
|
1392
|
+
// instrument set. The manager `ctl` rail is deleted.
|
|
1394
1393
|
...ep.pub,
|
|
1395
1394
|
...del,
|
|
1396
1395
|
// deleteChannels/clearChannel: purge the channel's chat messages + delete its registry key.
|
|
@@ -1399,16 +1398,16 @@ function teardownPermissions(space, pr, transferStreams) {
|
|
|
1399
1398
|
],
|
|
1400
1399
|
},
|
|
1401
1400
|
// Own inbox (connectProbe presence-watch delivery + JS API responses) + the ep reply rail (the
|
|
1402
|
-
// ps + any-mode despawn calls reply there). The `ctl.admin.<id>.reply.>` subtree is gone
|
|
1401
|
+
// ps + any-mode despawn calls reply there). The `ctl.admin.<id>.reply.>` subtree is gone.
|
|
1403
1402
|
sub: { allow: [`_INBOX_${pr.connId}.>`, ...ep.sub] },
|
|
1404
1403
|
};
|
|
1405
1404
|
}
|
|
1406
|
-
/** CONTROL-CALLER (
|
|
1405
|
+
/** CONTROL-CALLER (ep rails only) — the operator's lifecycle commands
|
|
1407
1406
|
* (`cotal ps/start/stop/attach`, `manager/commands.ts`). It invokes the manager's v0.4 service
|
|
1408
1407
|
* endpoint and reads the bounded reply on the ep reply rail. That is ALL — no `$JS`, no `$KV`,
|
|
1409
1408
|
* no chat/DM: it forges nothing, reads no body.
|
|
1410
1409
|
*
|
|
1411
|
-
* The tiers stay SPLIT because the BROKER grant is load-bearing
|
|
1410
|
+
* The tiers stay SPLIT because the BROKER grant is load-bearing: an any-mode
|
|
1412
1411
|
* despawn/attach row *is* cross-agent reach — the manager maps mode `any` to its admin
|
|
1413
1412
|
* authorization path, so which ROWS an instrument holds is the tier boundary. Therefore:
|
|
1414
1413
|
* • `control-caller-privileged` (ps/start) holds the manager reads + untargeted `spawn` +
|
|
@@ -1420,11 +1419,11 @@ function teardownPermissions(space, pr, transferStreams) {
|
|
|
1420
1419
|
* disconnect, from the local signing seed); on a user mesh the manager's serve-time ledger
|
|
1421
1420
|
* re-check sits on top. */
|
|
1422
1421
|
function controlCallerPermissions(space, pr, epTier, opts = {}) {
|
|
1423
|
-
//
|
|
1422
|
+
// The manager `ctl` rail is gone — an operator instrument holds ONLY its v0.4 ep rows (the
|
|
1424
1423
|
// tier-matched request set, the reply rail, describe, the one epc fetch). The `epTier` selects
|
|
1425
1424
|
// privileged (ps/start reads) vs admin (any-mode stop/attach) exactly as the ctl tier did.
|
|
1426
1425
|
//
|
|
1427
|
-
//
|
|
1426
|
+
// `--on <instanceId>`: these instruments are ONE-SHOT, minted per control call, and the
|
|
1428
1427
|
// resolve that pins the instance happens BEFORE the mint. So the caller can hand the exact
|
|
1429
1428
|
// instance id down and get the exact `ep.inst.<endpoint>.<iid>.<command>` row for THIS invocation
|
|
1430
1429
|
// and nothing else — the least-privilege issuance, with no standing wildcard anywhere. The
|
|
@@ -1434,17 +1433,17 @@ function controlCallerPermissions(space, pr, epTier, opts = {}) {
|
|
|
1434
1433
|
const ep = instrumentEpRows(space, pr, epTier, opts.endpointCapabilities ?? [], opts);
|
|
1435
1434
|
return {
|
|
1436
1435
|
// The PRIVILEGED tier (the `cotal ps` instrument) also carries the SCOPED §13.9 records read the
|
|
1437
|
-
// class scatter's freeze rides
|
|
1436
|
+
// class scatter's freeze rides: `freezeExpectedSet` enumerates `svc.<endpoint>.*.spec`
|
|
1438
1437
|
// and LEADER-reads each frozen slot's svc spec/status before it scatters `ps` on the `all` rail.
|
|
1439
1438
|
// The admin tier (stop/attach) never scatters, so it gets no records read.
|
|
1440
1439
|
pub: { allow: epTier === "privileged" ? [...ep.pub, ...scatterFreezeReadRows(space)] : ep.pub },
|
|
1441
1440
|
sub: { allow: [`_INBOX_${pr.connId}.>`, ...ep.sub] },
|
|
1442
1441
|
};
|
|
1443
1442
|
}
|
|
1444
|
-
/** The SCOPED §13.9 records-read rows the class-scatter freeze rides (
|
|
1443
|
+
/** The SCOPED §13.9 records-read rows the class-scatter freeze rides (`cotal ps`): the
|
|
1445
1444
|
* `svc.*` enumeration consumer + the leader-served per-slot spec/status read of the endpoint's
|
|
1446
|
-
* `svc` registry — a READ of exactly the service-registration keys, no write, no other bucket.
|
|
1447
|
-
*
|
|
1445
|
+
* `svc` registry — a READ of exactly the service-registration keys, no write, no other bucket.
|
|
1446
|
+
* The keyed Direct Get is subject-PINNED to `svc.>`; the enumeration consumer
|
|
1448
1447
|
* and the leader `STREAM.MSG.GET` are STREAM-scoped because the requested key rides the PAYLOAD
|
|
1449
1448
|
* (which a subject grant cannot narrow) — the SAME NAMED RESIDUAL every records reader already
|
|
1450
1449
|
* accepts (the provisioner, the lifecycle/serve executors): for this EPHEMERAL one-shot instrument's
|
|
@@ -1468,12 +1467,10 @@ function scatterFreezeReadRows(space) {
|
|
|
1468
1467
|
`$JS.API.DIRECT.GET.KV_${REC}.$KV.${REC}.svc.>`,
|
|
1469
1468
|
];
|
|
1470
1469
|
}
|
|
1471
|
-
/** The v0.4 ep-rail rows of an operator INSTRUMENT credential
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
* row
|
|
1475
|
-
* the instrument's ctl tier onto the ep rails during dual-serve; at 1d the ctl row disappears and
|
|
1476
|
-
* these ARE the instrument. The caller triple pins the instrument's own mint-time lifecycle uid
|
|
1470
|
+
/** The v0.4 ep-rail rows of an operator INSTRUMENT credential: the tier-matched
|
|
1471
|
+
* {@link operatorInstrumentCapabilities} request rows + the caller's reply rail, the wildcard
|
|
1472
|
+
* `describe` form, and the ONE subject-scoped §13.7 contract-store fetch row (the same shape the
|
|
1473
|
+
* agent baseline holds). No ctl row exists, so these ARE the instrument. The caller triple pins the instrument's own mint-time lifecycle uid
|
|
1477
1474
|
* ({@link MintPrincipal.lifecycleUid}) — REQUIRED here: without it the reply rail cannot be pinned
|
|
1478
1475
|
* and the mint fails loud rather than emit a triple-less (unfenced) caller surface. `extra` lets a
|
|
1479
1476
|
* profile append its tier-refined additions (the user-mode deployer's owner-equality `launch`). */
|
|
@@ -1549,7 +1546,7 @@ function endpointServePermissions(space, pr, opts) {
|
|
|
1549
1546
|
sub: { allow: [...rows.sub, `_INBOX_${pr.connId}.>`] },
|
|
1550
1547
|
};
|
|
1551
1548
|
}
|
|
1552
|
-
/** DEPLOYER
|
|
1549
|
+
/** DEPLOYER — the `cotal spawn -f` manifest-deploy authority. `spawn -f` drives ONE
|
|
1553
1550
|
* `connectProbe` endpoint that both READS live state (roster/presence watch, channel registry,
|
|
1554
1551
|
* membership feed, manager-singleton lease) AND invokes the running manager's `launch` + `ps`
|
|
1555
1552
|
* readiness over the v0.4 ep rails. Those interleave on one connection, so a strict 3-connection
|
|
@@ -1572,7 +1569,7 @@ function deployerPermissions(space, pr, epTier = "admin", opts = {}) {
|
|
|
1572
1569
|
// set; the user-mode deployer VIEW (privileged) carries the privileged set PLUS an untargeted
|
|
1573
1570
|
// `launch` row — its launch stays owner-equality-authorized (the manager's ledger-derived admin
|
|
1574
1571
|
// flag is false for a spawn-scoped deployer), exactly the v0.3 user-mode privileged-tier launch.
|
|
1575
|
-
//
|
|
1572
|
+
// `--on`: the per-invocation pin APPENDS to this profile's standing set, never replaces it -
|
|
1576
1573
|
// the privileged deployer view keeps its owner-equality `launch` row and additionally gets the one
|
|
1577
1574
|
// exact `ep.inst.<endpoint>.<iid>.<command>` row for the instance this deploy resolved.
|
|
1578
1575
|
const pinned = opts.endpointCapabilities ?? [];
|
|
@@ -1609,7 +1606,7 @@ function deployerPermissions(space, pr, epTier = "admin", opts = {}) {
|
|
|
1609
1606
|
...kvPointRead(MGRKV), // manager-singleton lease keyed read (waitManagerReady) — point-get, NO write, NO watch
|
|
1610
1607
|
...kvPointRead(DLVKV), // delivery-lease keyed read (preserve-state quiescence proof) — point-get, NO write, NO watch
|
|
1611
1608
|
"$JS.FC.>", // ordered-consumer flow control
|
|
1612
|
-
//
|
|
1609
|
+
// Launch + ps readiness ride the v0.4 ep rows only (the manager `ctl` rail is gone).
|
|
1613
1610
|
// Static deploys carry the admin instrument set; the user-mode deployer VIEW carries the
|
|
1614
1611
|
// privileged set + an owner-equality `launch` row (the manager's ledger-derived admin flag
|
|
1615
1612
|
// is false for a spawn-scoped deployer, so its launch stays owner-equality-authorized).
|
|
@@ -1620,7 +1617,7 @@ function deployerPermissions(space, pr, epTier = "admin", opts = {}) {
|
|
|
1620
1617
|
sub: { allow: [`_INBOX_${pr.connId}.>`, ...ep.sub] },
|
|
1621
1618
|
};
|
|
1622
1619
|
}
|
|
1623
|
-
/** The ephemeral PURGER permission set
|
|
1620
|
+
/** The ephemeral PURGER permission set — minted per-purge inside the daemon's
|
|
1624
1621
|
* `opPurge` and `cotal history clear`. Isolates the DESTRUCTIVE history-purge grant
|
|
1625
1622
|
* (`STREAM.PURGE.CHAT` + `STREAM.PURGE.DM`) off the always-on supervisor: `--dms` purges the DM stream,
|
|
1626
1623
|
* exactly the grant the supervisor must not hold. It PURGES but never READS — no DM/chat consumer, no
|
|
@@ -1635,12 +1632,12 @@ function purgerPermissions(space, pr) {
|
|
|
1635
1632
|
`$JS.API.STREAM.PURGE.${DM}`, // clearSpaceHistory includeDms — the isolated DM-purge grant
|
|
1636
1633
|
],
|
|
1637
1634
|
// NOTE: this profile does NOT cover `clearChannel` (web/`down -f` channel-delete) — that also does a
|
|
1638
|
-
// `$KV.<channelBucket>.<ch>` registry delete this cred lacks
|
|
1635
|
+
// `$KV.<channelBucket>.<ch>` registry delete this cred lacks, which `channel-purger` and `teardown` hold.
|
|
1639
1636
|
},
|
|
1640
1637
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1641
1638
|
};
|
|
1642
1639
|
}
|
|
1643
|
-
/** The ephemeral PROVISIONER permission set
|
|
1640
|
+
/** The ephemeral PROVISIONER permission set — the onboarding authority,
|
|
1644
1641
|
* carved off the long-lived manager. Minted short-lived for per-spawn provisioning (pre-create each
|
|
1645
1642
|
* agent's bind-only DM/DLV/TASK durables + record its read ACL via `commitAcl`) — the daemon opens it per
|
|
1646
1643
|
* spawn (`manager.ts withProvisioner`). It is ALSO the cred that creates the space's streams + KV buckets
|
|
@@ -1654,8 +1651,7 @@ function purgerPermissions(space, pr) {
|
|
|
1654
1651
|
* create a DM/DLV consumer can stream the bodies). That is exactly why it is split OFF the always-on
|
|
1655
1652
|
* supervisor and made EPHEMERAL: the daemon opens a provisioner connection per spawn and drains it
|
|
1656
1653
|
* immediately, so the surface exists only for the provisioning window, not as a standing target. The
|
|
1657
|
-
* cred is MEMORY-ONLY (never written to `.cotal`) and
|
|
1658
|
-
* rotation, live eviction, and full revocation are later D5 slices.
|
|
1654
|
+
* cred is MEMORY-ONLY (never written to `.cotal`) and carries a short profile-default `exp`.
|
|
1659
1655
|
*
|
|
1660
1656
|
* `$JS` is an ENUMERATED allow-list, never `$JS.>`: STREAM.CREATE + INFO for the space streams/buckets,
|
|
1661
1657
|
* DM/DLV/TASK consumer CREATE/DURABLE.CREATE/INFO — and deliberately NO `MSG.NEXT`/`MSG.GET`/`ACK` on
|
|
@@ -1685,9 +1681,9 @@ function provisionerPermissions(space, pr) {
|
|
|
1685
1681
|
// STREAM.CREATE + INFO for each (idempotent setup at `cotal up`; CREATE is create-if-matching, INFO covers
|
|
1686
1682
|
// the client's existence checks). NO DELETE/PURGE — provisioning never tears a stream down.
|
|
1687
1683
|
// The §13.7 CONTRACT store (EPC) joins the list for the static manager's start-time
|
|
1688
|
-
// `ensureContractStore
|
|
1684
|
+
// `ensureContractStore`: create-or-verify only — the provisioner holds no
|
|
1689
1685
|
// artifact-publish grant on it (publication rides the scoped endpoint-serve executor).
|
|
1690
|
-
// The seven §13.12 ENDPOINT streams join the list
|
|
1686
|
+
// The seven §13.12 ENDPOINT streams join the list: spawn-as-action makes the manager
|
|
1691
1687
|
// the first EPF (goal facts) + EPE (progress) writer, and nothing provisioned the endpoint streams
|
|
1692
1688
|
// before (no manager code wrote to them), so `createEndpointStreams` now runs at the manager's
|
|
1693
1689
|
// start-time ensure over this provisioner. Create-or-verify only (idempotent, fail-loud on drift);
|
|
@@ -1764,7 +1760,7 @@ function provisionerPermissions(space, pr) {
|
|
|
1764
1760
|
`$JS.API.DIRECT.GET.KV_${aclBucket(space)}.>`, // keyed get: `.>` (the key rides the subject)
|
|
1765
1761
|
`$JS.API.STREAM.MSG.GET.KV_${channelBucket(space)}`,
|
|
1766
1762
|
`$JS.API.DIRECT.GET.KV_${channelBucket(space)}.>`, // keyed get: `.>` (the key rides the subject)
|
|
1767
|
-
// The
|
|
1763
|
+
// The static-manager start path: `ensureAuthorityStores` UPDATEs the records store's
|
|
1768
1764
|
// deny-flags exactly once at fresh creation (create → update → verify), and the boot
|
|
1769
1765
|
// reconciliation sweep enumerates the manager's slot rows (`keys()` → an ordered consumer)
|
|
1770
1766
|
// then reads each slot BODY (phase/uid/actor) to plan resume — the reads ride the
|
|
@@ -1840,7 +1836,7 @@ function runAdmitterPermissions(space, pr, pin) {
|
|
|
1840
1836
|
};
|
|
1841
1837
|
}
|
|
1842
1838
|
/** The ephemeral, LIFECYCLE-PINNED §13.1 state-write permission set for the STATIC manager's
|
|
1843
|
-
* lifecycle executor
|
|
1839
|
+
* lifecycle executor. One credential per lifecycle OPERATION (activation, terminal,
|
|
1844
1840
|
* renewal ledger append): every grant names exactly ONE incarnation's keys — the alias head,
|
|
1845
1841
|
* the uid reservation, the manager slot row, the issuance gate, and the `cred.<uid>.>` ledger
|
|
1846
1842
|
* family — so a leaked executor cred can move one incarnation's state machine and nothing else.
|
|
@@ -1868,7 +1864,7 @@ function lifecycleExecutorPermissions(space, pr, pin) {
|
|
|
1868
1864
|
// grant names exactly one of this incarnation's keys.
|
|
1869
1865
|
...recordKeys.map((k) => `$KV.${REC}.${k}`),
|
|
1870
1866
|
// Auth-store CAS writes — this incarnation's gate + its cred-ledger family (renewals
|
|
1871
|
-
// append rows here; the terminal's
|
|
1867
|
+
// append rows here; the terminal's revoke CASes them).
|
|
1872
1868
|
`$KV.${AUTH}.${issuanceGateKey(pin.lifecycleUid)}`,
|
|
1873
1869
|
`$KV.${AUTH}.cred.${pin.lifecycleUid}.>`,
|
|
1874
1870
|
// Keyed Direct Get reads of the same records keys (key-pinned) for direct-aware read
|
|
@@ -1885,16 +1881,16 @@ function lifecycleExecutorPermissions(space, pr, pin) {
|
|
|
1885
1881
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
1886
1882
|
};
|
|
1887
1883
|
}
|
|
1888
|
-
/** The SELF-MEDIATED goal-writer profile (
|
|
1884
|
+
/** The SELF-MEDIATED goal-writer profile (spawn-as-action): exactly
|
|
1889
1885
|
* {@link goalWriterGrants} for ITS endpoint — the goal bind + terminal facts, the goal-record KV
|
|
1890
1886
|
* writes, and the leader-served fencing reads — plus the connection-scoped reply inbox. Disjoint
|
|
1891
|
-
* from the endpoint's serve credential
|
|
1887
|
+
* from the endpoint's serve credential: a serve connection carries none of these rows, so it
|
|
1892
1888
|
* is broker-denied every goal write. */
|
|
1893
1889
|
function goalWriterPermissions(space, pr, pin) {
|
|
1894
1890
|
const g = goalWriterGrants(space, pin.endpoint, pr.connId);
|
|
1895
1891
|
return { pub: { allow: g.publish }, sub: { allow: g.subscribe } };
|
|
1896
1892
|
}
|
|
1897
|
-
/** The console/CLI per-session CALLER rows
|
|
1893
|
+
/** The console/CLI per-session CALLER rows: RAILS-ONLY for ONE §13.6 session — pub the
|
|
1898
1894
|
* session's epoch-pinned `in` rail, sub its `out` rail plus the caller's own reply inbox, and
|
|
1899
1895
|
* NOTHING else. Deliberately NO KV, NO JetStream API, NO store: the caller drives the terminal over
|
|
1900
1896
|
* the two core-only eps subjects and never reads the session ledger, so there is no subject-blind
|
|
@@ -1909,7 +1905,7 @@ function sessionCallerPermissions(space, pr, pin) {
|
|
|
1909
1905
|
sub: { allow: [epsSubject(space, pin.endpoint, pin.sessionId, pin.epoch, "out"), `_INBOX_${pr.connId}.>`] },
|
|
1910
1906
|
};
|
|
1911
1907
|
}
|
|
1912
|
-
/** The manager's per-session SERVING rows
|
|
1908
|
+
/** The manager's per-session SERVING rows: the EXACT mirror of
|
|
1913
1909
|
* {@link sessionCallerPermissions} with the directions swapped — sub the ONE session's epoch-pinned
|
|
1914
1910
|
* `in` rail (caller→serving), pub its `out` rail (serving→caller), plus the connection-scoped reply
|
|
1915
1911
|
* inbox, and NOTHING else. Same asymmetry §13.6 states: "the caller publishes `in` and subscribes
|
|
@@ -1932,7 +1928,7 @@ function sessionServingPermissions(space, pr, pin) {
|
|
|
1932
1928
|
sub: { allow: [epsSubject(space, pin.endpoint, pin.sessionId, pin.epoch, "in"), `_INBOX_${pr.connId}.>`] },
|
|
1933
1929
|
};
|
|
1934
1930
|
}
|
|
1935
|
-
/** The manager's SESSION-LEDGER rows
|
|
1931
|
+
/** The manager's SESSION-LEDGER rows: the DEDICATED sessions-bucket rows and nothing
|
|
1936
1932
|
* else — no session rail of any shape. Needs no pin: the grant carries no endpoint, epoch, or
|
|
1937
1933
|
* session component, because §13.6's durable revocation authority is per-space, not per-session
|
|
1938
1934
|
* (it must still be able to resolve and revoke a row after the endpoint that served it is gone).
|
|
@@ -1942,10 +1938,10 @@ function sessionLedgerPermissions(space, pr) {
|
|
|
1942
1938
|
const g = sessionLedgerGrants(space, pr.connId);
|
|
1943
1939
|
return { pub: { allow: g.publish }, sub: { allow: g.subscribe } };
|
|
1944
1940
|
}
|
|
1945
|
-
/** The ephemeral, ENDPOINT-INSTANCE-PINNED endpoint-serve executor permission set
|
|
1946
|
-
*
|
|
1941
|
+
/** The ephemeral, ENDPOINT-INSTANCE-PINNED endpoint-serve executor permission set:
|
|
1942
|
+
* the manager mints this per registration/serve-mint op and drives the endpoint
|
|
1947
1943
|
* registration barrier's `epgate` CAS + the mint fence's `epcred` stage/revoke THROUGH it — never
|
|
1948
|
-
* its standing seed/supervisor connection
|
|
1944
|
+
* its standing seed/supervisor connection. Every
|
|
1949
1945
|
* WRITE is key-pinned to exactly ONE (endpoint, instanceId): the gate `epgate.<ep>.<iid>`, its
|
|
1950
1946
|
* serving ledger family `epcred.<ep>.<iid>.>`, and the registration's two records keys (the
|
|
1951
1947
|
* instance's `svc` spec + the endpoint's governance head — `registerServiceInstance` drives the
|
|
@@ -1963,7 +1959,7 @@ function endpointServeExecutorPermissions(space, pr, pin) {
|
|
|
1963
1959
|
const repairKey = eprepairKey(pin.endpoint, pin.instanceId);
|
|
1964
1960
|
// The registration writes this ONE instance's spec key plus the endpoint's governance head
|
|
1965
1961
|
// (`registerServiceInstance` PHASE 1/3b: the slot-take + promote ride the SAME executor), and this
|
|
1966
|
-
// instance's own svc STATUS key (
|
|
1962
|
+
// instance's own svc STATUS key (the manager writes its CONVERGED `ready` status so it
|
|
1967
1963
|
// is a §13.5 scatter member — `freezeExpectedSet` requires a status caught up to the current
|
|
1968
1964
|
// registration; the write is epoch-fenced by `writeServiceStatus`).
|
|
1969
1965
|
const recordKeys = [
|
|
@@ -1983,7 +1979,7 @@ function endpointServeExecutorPermissions(space, pr, pin) {
|
|
|
1983
1979
|
`$KV.${AUTH}.${repairKey}`,
|
|
1984
1980
|
`$KV.${AUTH}.${credPrefix}.>`,
|
|
1985
1981
|
...recordKeys.map((k) => `$KV.${REC}.${k}`),
|
|
1986
|
-
// §13.7 contract-artifact publication
|
|
1982
|
+
// §13.7 contract-artifact publication: the registration publishes the
|
|
1987
1983
|
// endpoint's cluster document, closure manifests, and schema roots to the EPC store so
|
|
1988
1984
|
// callers can fetch-verify-compile the registered digests. A digest subject is a SINGLE
|
|
1989
1985
|
// hex token (`epc.<64hex>`), so the grant is the single-token `epc.*` form (matching
|
|
@@ -2023,7 +2019,7 @@ function endpointServeExecutorPermissions(space, pr, pin) {
|
|
|
2023
2019
|
sub: { allow: [`_INBOX_${pr.connId}.>`] },
|
|
2024
2020
|
};
|
|
2025
2021
|
}
|
|
2026
|
-
/** The ephemeral, TARGET-PINNED DEPROVISIONER permission set
|
|
2022
|
+
/** The ephemeral, TARGET-PINNED DEPROVISIONER permission set — the teardown counterpart
|
|
2027
2023
|
* to {@link provisionerPermissions}, minted per departed agent inside the manager's `deprovision` tail
|
|
2028
2024
|
* (`withProvisioner`-style: a fresh scoped cred per teardown is cheap). It deletes exactly the
|
|
2029
2025
|
* dev/static principal footprint the provisioner created for ONE agent: that agent's two bind-only
|
|
@@ -2174,7 +2170,7 @@ function deliveryPermissions(space, pr) {
|
|
|
2174
2170
|
// the `.reply.>` leaf so the daemon can't publish to the request subjects themselves — tighter than a
|
|
2175
2171
|
// blanket `ctl.delivery.>` (fact-check precision, review panel). The caller slots widened to `.*.*`.
|
|
2176
2172
|
`${p}.ctl.delivery.*.*.reply.>`,
|
|
2177
|
-
// The privileged delivery-admin rail
|
|
2173
|
+
// The privileged delivery-admin rail: same replies-only shape. Requests reach the
|
|
2178
2174
|
// daemon on the sub below; only the supervisor cred can PUBLISH them (nats-server is the boundary).
|
|
2179
2175
|
`${p}.ctl.delivery-admin.*.*.reply.>`,
|
|
2180
2176
|
// LIVENESS REPLIES for the DELIVERY plane (#1577) — same replies-only shape as the two control
|
|
@@ -2255,7 +2251,7 @@ export async function mintMembershipObserverCreds(auth, identity, opts = {}) {
|
|
|
2255
2251
|
throw new Error("mintMembershipObserverCreds: no in-memory system-account signing seed - the observer can only be minted from a system account that is being (re)provisioned, because the $SYS seed is never persisted. Rotate the system account to mint a fresh one (`cotal down` then `cotal up --rotate-sys`); a plain re-`up` reuses the existing account and its existing creds.");
|
|
2256
2252
|
const signer = fromSeed(new TextEncoder().encode(auth.sys.signingSeed));
|
|
2257
2253
|
const perms = membershipObserverPermissions(auth.account.pub);
|
|
2258
|
-
// Bounded exp
|
|
2254
|
+
// Bounded exp: the observer is `rotation-renewed` — it carries the matrix's default
|
|
2259
2255
|
// lifetime so a copied cred becomes broker-dead, but there is NO online renewal (the $SYS seed is
|
|
2260
2256
|
// gone after `up`); renewal is a coordinated system-account rotation + restart.
|
|
2261
2257
|
const validDates = userValidDates("membership-observer", opts);
|
|
@@ -2263,7 +2259,7 @@ export async function mintMembershipObserverCreds(auth, identity, opts = {}) {
|
|
|
2263
2259
|
const creds = fmtCreds(userJwt, fromSeed(new TextEncoder().encode(identity.seed)));
|
|
2264
2260
|
return new TextDecoder().decode(creds);
|
|
2265
2261
|
}
|
|
2266
|
-
/** The KICK-ONLY connection-evictor permission set
|
|
2262
|
+
/** The KICK-ONLY connection-evictor permission set — a SYSTEM-account user that can do
|
|
2267
2263
|
* exactly ONE thing: `$SYS.REQ.SERVER.*.KICK` (disconnect a live client by cid). It CANNOT read
|
|
2268
2264
|
* CONNZ (discovery stays on the separate observer cred — never one broad sys user that both
|
|
2269
2265
|
* enumerates and kills), touch any other `$SYS` verb, or reach another account's data. A leaked
|
|
@@ -2280,14 +2276,14 @@ function connectionEvictorPermissions() {
|
|
|
2280
2276
|
sub: { allow: ["_INBOX.>"] },
|
|
2281
2277
|
};
|
|
2282
2278
|
}
|
|
2283
|
-
/** Mint the scoped `connection-evictor` creds — the kick-only SYSTEM-account user
|
|
2284
|
-
* eviction
|
|
2279
|
+
/** Mint the scoped `connection-evictor` creds — the kick-only SYSTEM-account user for live
|
|
2280
|
+
* eviction. Same mint-only-at-provision property as the observer (the $SYS seed is in-memory
|
|
2285
2281
|
* only), same fail-loud when it's absent. Paired with the observer at `up`. */
|
|
2286
2282
|
export async function mintConnectionEvictorCreds(auth, identity, opts = {}) {
|
|
2287
2283
|
if (!auth.sys.signingSeed)
|
|
2288
2284
|
throw new Error("mintConnectionEvictorCreds: no in-memory system-account signing seed - the evictor can only be minted from a system account that is being (re)provisioned, because the $SYS seed is never persisted. Rotate the system account to mint a fresh one (`cotal down` then `cotal up --rotate-sys`); a plain re-`up` reuses the existing account and its existing creds.");
|
|
2289
2285
|
const signer = fromSeed(new TextEncoder().encode(auth.sys.signingSeed));
|
|
2290
|
-
// Bounded exp
|
|
2286
|
+
// Bounded exp: `rotation-renewed`, same posture as the observer above.
|
|
2291
2287
|
const validDates = userValidDates("connection-evictor", opts);
|
|
2292
2288
|
const userJwt = await encodeUser("connection-evictor", fromPublic(identity.id), fromPublic(auth.sys.pub), connectionEvictorPermissions(), { signer, ...validDates });
|
|
2293
2289
|
const creds = fmtCreds(userJwt, fromSeed(new TextEncoder().encode(identity.seed)));
|
|
@@ -2357,7 +2353,7 @@ function renderTlsBlock(transport) {
|
|
|
2357
2353
|
* THIS broker's operator before it is preloaded: rendering a foreign-signed account would either
|
|
2358
2354
|
* refuse broker boot or, worse, advertise a tenant the broker cannot actually authenticate.
|
|
2359
2355
|
*
|
|
2360
|
-
* NOTE
|
|
2356
|
+
* NOTE: the MEMORY resolver is one static whole-broker map, so every mutation rewrites all of
|
|
2361
2357
|
* it. Concurrent add/remove of spaces needs a broker-authoritative inventory with generation/CAS
|
|
2362
2358
|
* and atomic promotion above this function; this renderer is deliberately pure. */
|
|
2363
2359
|
export function serverConfig(broker, spaces, opts) {
|
|
@@ -2374,7 +2370,7 @@ export function serverConfig(broker, spaces, opts) {
|
|
|
2374
2370
|
}
|
|
2375
2371
|
const port = opts.port ?? 4222;
|
|
2376
2372
|
const host = opts.host ?? "127.0.0.1";
|
|
2377
|
-
// The websocket listener
|
|
2373
|
+
// The websocket listener: LOCALHOST by default, no_tls for the dev loopback. Emitted only
|
|
2378
2374
|
// when wsPort is set — a broker with no console session client opens no ws surface.
|
|
2379
2375
|
const websocket = opts.wsPort === undefined ? "" : `websocket {
|
|
2380
2376
|
host: ${opts.wsHost ?? "127.0.0.1"}
|