@cotal-ai/workspace 0.16.0 → 0.18.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/auth-paths.d.ts +38 -0
- package/dist/auth-paths.d.ts.map +1 -1
- package/dist/auth-paths.js +102 -13
- package/dist/auth-paths.js.map +1 -1
- package/dist/broker-policy.d.ts +33 -0
- package/dist/broker-policy.d.ts.map +1 -0
- package/dist/broker-policy.js +98 -0
- package/dist/broker-policy.js.map +1 -0
- package/dist/connect.d.ts +47 -2
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +123 -17
- package/dist/connect.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mesh-registry.d.ts +35 -5
- package/dist/mesh-registry.d.ts.map +1 -1
- package/dist/mesh-registry.js +13 -5
- package/dist/mesh-registry.js.map +1 -1
- package/dist/mesh-target.d.ts +5 -0
- package/dist/mesh-target.d.ts.map +1 -1
- package/dist/mesh-target.js +3 -1
- package/dist/mesh-target.js.map +1 -1
- package/dist/pid.d.ts +49 -0
- package/dist/pid.d.ts.map +1 -0
- package/dist/pid.js +53 -0
- package/dist/pid.js.map +1 -0
- package/dist/preflight.d.ts +6 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +91 -1
- package/dist/preflight.js.map +1 -1
- package/dist/render.d.ts +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +15 -4
- package/dist/render.js.map +1 -1
- package/dist/system-rotation.d.ts +89 -0
- package/dist/system-rotation.d.ts.map +1 -0
- package/dist/system-rotation.js +142 -0
- package/dist/system-rotation.js.map +1 -0
- package/package.json +2 -2
package/dist/pid.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE pid-attribution contract for machine-local pidfiles, in ONE place.
|
|
3
|
+
*
|
|
4
|
+
* Two copies of "parse a pid" + "is it alive" had drifted (a bounded parser in `auth-proc`, an
|
|
5
|
+
* unbounded `Number.isInteger` parser plus a two-state `isAlive` in `down`), and the gap between a
|
|
6
|
+
* guard's validity predicate and what `process.kill` actually accepts is exactly where a live
|
|
7
|
+
* process gets misread as dead and its pidfile deleted under a clean-stop report. One parser, one
|
|
8
|
+
* tri-state probe, so every surface that decides "is this record a live process, a dead one, or
|
|
9
|
+
* unattributable" decides it the same way.
|
|
10
|
+
*
|
|
11
|
+
* WHO CAN ACTUALLY CONSUME IT, because "consumed everywhere" was never reachable and claiming it
|
|
12
|
+
* hid the gap. This lives in `workspace` (machine-local operator tooling), the widest tier that may
|
|
13
|
+
* hold a local-process concept: the CLI, the manager, the auth service and the web surface all
|
|
14
|
+
* depend on it. `extensions/*` peer-depend `core` ONLY, and a pid probe is not a wire concept, so
|
|
15
|
+
* moving it into core to reach them would leak a local concern into the standard. The two
|
|
16
|
+
* extension-side probes (`connector-opencode`, `connector-hermes`) therefore keep their own copies
|
|
17
|
+
* BY CONSTRUCTION, not by oversight; if they need this contract, the fix is a shared local-process
|
|
18
|
+
* module they may depend on, never a core export.
|
|
19
|
+
*/
|
|
20
|
+
/** A Node/POSIX-signalable pid: a positive INTEGER within the signed 32-bit range `process.kill`
|
|
21
|
+
* accepts (it throws `ERR_INVALID_ARG_TYPE`/`ERR_OUT_OF_RANGE` outside it). Anything else -
|
|
22
|
+
* fractional, non-numeric, non-positive, oversized - is undefined: UNATTRIBUTABLE, never a pid to
|
|
23
|
+
* probe or delete a record against. */
|
|
24
|
+
export function parsePid(raw) {
|
|
25
|
+
const n = Number(raw.trim());
|
|
26
|
+
return Number.isInteger(n) && n > 0 && n <= 0x7fffffff ? n : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** The errno-to-state MAPPING, split out from the syscall so it can be tested exhaustively without
|
|
29
|
+
* an environment in the loop. The whole contract turns on one rule: only an actual `ESRCH` proves a
|
|
30
|
+
* process gone. `EPERM` (it exists, it is just another user's, so we cannot signal it) is ALIVE.
|
|
31
|
+
* Anything else - argument/range errors, unfamiliar errnos, a missing code - is UNKNOWN, never dead.
|
|
32
|
+
*
|
|
33
|
+
* This is exported because the old suite could only reach the EPERM rule by probing pid 1 and
|
|
34
|
+
* hoping the process was unprivileged, so as root or in some containers that cell SKIPPED and the
|
|
35
|
+
* suite still printed a passing banner: a wrong implementation reading green. A pure mapping has
|
|
36
|
+
* no fixture to skip. Found by review, not by me. */
|
|
37
|
+
export function livenessFromErrno(code) {
|
|
38
|
+
if (code === "ESRCH")
|
|
39
|
+
return "dead";
|
|
40
|
+
if (code === "EPERM")
|
|
41
|
+
return "alive"; // exists, just not ours to signal
|
|
42
|
+
return "unknown"; // ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE / anything else - cannot attribute
|
|
43
|
+
}
|
|
44
|
+
export function probeLiveness(pid) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return "alive";
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return livenessFromErrno(e.code);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=pid.js.map
|
package/dist/pid.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pid.js","sourceRoot":"","sources":["../src/pid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;wCAGwC;AACxC,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC;AAED;;;;;;;;sDAQsD;AACtD,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACxD,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC;IACpC,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC,CAAC,kCAAkC;IACxE,OAAO,SAAS,CAAC,CAAC,6EAA6E;AACjG,CAAC;AAgBD,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,iBAAiB,CAAE,CAA2B,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC"}
|
package/dist/preflight.d.ts
CHANGED
|
@@ -13,7 +13,12 @@ import type { MeshTarget } from "./mesh-target.js";
|
|
|
13
13
|
/** The five distinct ways a preflight fails. Each also carries whether the target OWNS its registry
|
|
14
14
|
* entry (→ prune): `fromRegistry` means the server+mode came from a registry record (incl. a
|
|
15
15
|
* `local-recorded` project matched by root), so a definitive failure is a stale-entry signal. */
|
|
16
|
-
export type PreflightFailure = "unreachable" | "registry-creds-rejected" | "registry-open-now-auth" | "creds-rejected" | "open-wants-auth" | "stale-auth"
|
|
16
|
+
export type PreflightFailure = "unreachable" | "registry-creds-rejected" | "registry-open-now-auth" | "creds-rejected" | "open-wants-auth" | "stale-auth"
|
|
17
|
+
/** A TLS-required NATS listener still greets (INFO.tls_required) but the TLS probe could not
|
|
18
|
+
* complete the handshake — typically a private CA without `NODE_EXTRA_CA_CERTS`. NEVER a prune
|
|
19
|
+
* signal: INFO is unauthenticated shape evidence, not peer identity; conservatively keep the
|
|
20
|
+
* record and repair client trust rather than re-register. */
|
|
21
|
+
| "tls-trust";
|
|
17
22
|
/** Pure decision tree — separated from I/O so the whole branch tree is unit-testable (it's the
|
|
18
23
|
* riskiest logic: a wrong branch prunes a LIVE registry entry). Only a registry-OWNED source
|
|
19
24
|
* (`registry`/`current`/`flag-space`/`local-recorded`) is ever pruned. A non-registry source —
|
package/dist/preflight.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;GAUG;AAEH;;kGAEkG;AAClG,MAAM,MAAM,gBAAgB,GACxB,aAAa,GACb,yBAAyB,GACzB,wBAAwB,GACxB,gBAAgB,GAChB,iBAAiB,GACjB,YAAY;AACd;;;8DAG8D;GAC5D,WAAW,CAAC;AAEhB;;;;;oGAKoG;AACpG,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,EAC5B,MAAM,EAAE,eAAe,GAAG,YAAY,GAAG,aAAa,EACtD,OAAO,EAAE,OAAO,GACf;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAkB5C;AAED;;;uFAGuF;AACvF,wBAAsB,eAAe,CACnC,MAAM,EAAE,UAAU,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CA4D/E;AAgFD;;+FAE+F;AAC/F,MAAM,WAAW,SAAS;IACxB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,SAAS,CAAC,CAW3D"}
|
package/dist/preflight.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
1
2
|
import { mintCreds, newIdentity, probeConnect, isReachable } from "@cotal-ai/core";
|
|
2
3
|
import { loadMeshes, pruneMesh } from "./mesh-registry.js";
|
|
3
4
|
/** Pure decision tree — separated from I/O so the whole branch tree is unit-testable (it's the
|
|
@@ -40,7 +41,18 @@ export async function preflightTarget(target, probeCreds) {
|
|
|
40
41
|
if (target.mode === "user")
|
|
41
42
|
throw new Error("preflightTarget: a user-mode target cannot be credless-probed - the caller owns the user connect (see preflightOrExit's user branch)");
|
|
42
43
|
const creds = probeCreds ?? (target.auth ? await mintCreds(target.auth, newIdentity(), "probe") : undefined);
|
|
43
|
-
|
|
44
|
+
// THE RECORDED TLS REQUIREMENT IS PART OF THE PROBE, not decoration on the record.
|
|
45
|
+
//
|
|
46
|
+
// Without it this probe connects to ANY broker on the recorded address, including a plaintext one
|
|
47
|
+
// substituted for the TLS broker the record describes — and reports `ok`. Two independent testers
|
|
48
|
+
// drove exactly that: record a TLS mesh, kill its broker, start a plaintext `nats-server` on the
|
|
49
|
+
// same port, and `cotal status` returned rc=0 and "connection ok".
|
|
50
|
+
//
|
|
51
|
+
// That is worse than a missing feature. A tool that is silent about a substitution is a gap; one
|
|
52
|
+
// that AFFIRMATIVELY REPORTS HEALTHY is a hazard, because the operator's rational response to a
|
|
53
|
+
// green check is to stop looking. The recorded requirement is the only thing that can tell those
|
|
54
|
+
// two brokers apart, since the plaintext one answers perfectly well.
|
|
55
|
+
const auth = { ...(creds ? { creds } : {}), ...(target.tlsRequired ? { tls: true } : {}) };
|
|
44
56
|
let probe = await probeConnect(target.server, auth);
|
|
45
57
|
if (probe.ok)
|
|
46
58
|
return { ok: true };
|
|
@@ -55,9 +67,87 @@ export async function preflightTarget(target, probeCreds) {
|
|
|
55
67
|
probe = await probeConnect(target.server, { ...auth, timeoutMs: PREFLIGHT_CONFIRM_TIMEOUT_MS });
|
|
56
68
|
if (probe.ok)
|
|
57
69
|
return { ok: true };
|
|
70
|
+
// TLS-REQUIRED + "unreachable" is often NOT a dead broker. `probeConnect` maps every non-auth
|
|
71
|
+
// failure (including certificate verification) to `unreachable`. Against a private-CA mesh
|
|
72
|
+
// without `NODE_EXTRA_CA_CERTS`, the TLS handshake fails while the broker is live and still
|
|
73
|
+
// greets with plaintext INFO advertising `tls_required`. Classifying that as unreachable + prune
|
|
74
|
+
// DELETES a healthy tlsRequired registry entry — durable state destroyed on a recoverable trust
|
|
75
|
+
// error. This branch introduced tlsRequired meshes, so the mis-prune is in scope.
|
|
76
|
+
//
|
|
77
|
+
// Discriminator must be stronger than bare liveness. A *different* broker on the recorded port
|
|
78
|
+
// (the classic plaintext-substitute case this file's own comment documents) also answers INFO —
|
|
79
|
+
// bare `isReachable` alone would mis-label that as tls-trust and keep a stale record. Only an
|
|
80
|
+
// INFO that still advertises `tls_required: true` is shape evidence consistent with the record
|
|
81
|
+
// (a TLS-required NATS listener, not a plaintext substitute). INFO is unauthenticated and is NOT
|
|
82
|
+
// peer identity. Anything else falls through to the normal unreachable/prune path.
|
|
83
|
+
//
|
|
84
|
+
// CONFIRM BEFORE CONDEMNING applies here too: a single 1s INFO read on a slow/jittery link would
|
|
85
|
+
// miss a live TLS greeting and fall through to prune — recreating the S10 data-loss under load.
|
|
86
|
+
// First miss only makes it a candidate; a second, longer read must also miss before we prune.
|
|
87
|
+
if (target.tlsRequired && probe.reason === "unreachable") {
|
|
88
|
+
const info = (await readNatsInfoGreeting(target.server)) ??
|
|
89
|
+
(await readNatsInfoGreeting(target.server, PRUNE_CONFIRM_TIMEOUT_MS));
|
|
90
|
+
if (info?.tls_required === true)
|
|
91
|
+
return { ok: false, kind: "tls-trust", prune: false };
|
|
92
|
+
}
|
|
58
93
|
const { prune, kind } = classifyPreflightFailure(target.source, probe.reason, Boolean(target.auth));
|
|
59
94
|
return { ok: false, kind, prune };
|
|
60
95
|
}
|
|
96
|
+
/** Read the pre-auth NATS INFO greeting (plaintext, before any STARTTLS). Returns null if nothing
|
|
97
|
+
* usable answered. Used to distinguish a TLS-required NATS listener from a plaintext substitute
|
|
98
|
+
* without a TLS handshake. It does NOT establish mesh identity — INFO is unauthenticated. */
|
|
99
|
+
async function readNatsInfoGreeting(server, timeoutMs = 1_000) {
|
|
100
|
+
let host;
|
|
101
|
+
let port;
|
|
102
|
+
try {
|
|
103
|
+
const u = new URL(server.includes("://") ? server : `nats://${server}`);
|
|
104
|
+
host = u.hostname;
|
|
105
|
+
port = u.port ? Number(u.port) : 4222;
|
|
106
|
+
if (!host || !Number.isFinite(port))
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
const sock = createConnection({ host, port });
|
|
114
|
+
let buf = "";
|
|
115
|
+
let done = false;
|
|
116
|
+
const finish = (v) => {
|
|
117
|
+
if (done)
|
|
118
|
+
return;
|
|
119
|
+
done = true;
|
|
120
|
+
try {
|
|
121
|
+
sock.destroy();
|
|
122
|
+
}
|
|
123
|
+
catch { /* */ }
|
|
124
|
+
resolve(v);
|
|
125
|
+
};
|
|
126
|
+
sock.setTimeout(timeoutMs, () => finish(null));
|
|
127
|
+
sock.on("error", () => finish(null));
|
|
128
|
+
sock.on("close", () => finish(null));
|
|
129
|
+
sock.on("data", (chunk) => {
|
|
130
|
+
buf += chunk.toString("utf8");
|
|
131
|
+
const nl = buf.indexOf("\r\n");
|
|
132
|
+
if (nl < 0) {
|
|
133
|
+
if (buf.length > 4096)
|
|
134
|
+
finish(null);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const line = buf.slice(0, nl);
|
|
138
|
+
const brace = line.indexOf("{");
|
|
139
|
+
if (!/^INFO\b/.test(line) || brace < 0)
|
|
140
|
+
return finish(null);
|
|
141
|
+
try {
|
|
142
|
+
const j = JSON.parse(line.slice(brace));
|
|
143
|
+
finish(j);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
finish(null);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
61
151
|
/**
|
|
62
152
|
* Drop registry entries whose broker is gone — a `cotal up` that crashed or was `kill -9`'d without
|
|
63
153
|
* `cotal down` leaves a record behind. This is liveness-only and we hold NO creds for these meshes,
|
package/dist/preflight.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA+B3D;;;;;oGAKoG;AACpG,MAAM,UAAU,wBAAwB,CACtC,MAA4B,EAC5B,MAAsD,EACtD,OAAgB;IAEhB,mGAAmG;IACnG,iGAAiG;IACjG,MAAM,YAAY,GAChB,MAAM,KAAK,UAAU;QACrB,MAAM,KAAK,SAAS;QACpB,MAAM,KAAK,YAAY;QACvB,MAAM,KAAK,gBAAgB,CAAC;IAC9B,IAAI,MAAM,KAAK,aAAa;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAClF,iGAAiG;IACjG,+FAA+F;IAC/F,2FAA2F;IAC3F,6FAA6F;IAC7F,IAAI,MAAM,KAAK,YAAY;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACzE,IAAI,YAAY,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,yBAAyB,EAAE,CAAC;IACrF,IAAI,YAAY;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,wBAAwB,EAAE,CAAC;IACzE,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IAC7D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;AACnD,CAAC;AAED;;;uFAGuF;AACvF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAkB,EAClB,UAAmB;IAEnB,8FAA8F;IAC9F,4FAA4F;IAC5F,iGAAiG;IACjG,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QACxB,MAAM,IAAI,KAAK,CAAC,sIAAsI,CAAC,CAAC;IAC1J,MAAM,KAAK,GACT,UAAU,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACjG,mFAAmF;IACnF,EAAE;IACF,kGAAkG;IAClG,kGAAkG;IAClG,iGAAiG;IACjG,mEAAmE;IACnE,EAAE;IACF,iGAAiG;IACjG,gGAAgG;IAChG,iGAAiG;IACjG,qEAAqE;IACrE,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACpG,IAAI,KAAK,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpD,IAAI,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAClC,+FAA+F;IAC/F,8FAA8F;IAC9F,kGAAkG;IAClG,gGAAgG;IAChG,yFAAyF;IACzF,2FAA2F;IAC3F,6FAA6F;IAC7F,iGAAiG;IACjG,KAAK,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAAC,CAAC;IAChG,IAAI,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAElC,8FAA8F;IAC9F,2FAA2F;IAC3F,4FAA4F;IAC5F,iGAAiG;IACjG,gGAAgG;IAChG,kFAAkF;IAClF,EAAE;IACF,+FAA+F;IAC/F,gGAAgG;IAChG,8FAA8F;IAC9F,+FAA+F;IAC/F,iGAAiG;IACjG,mFAAmF;IACnF,EAAE;IACF,iGAAiG;IACjG,gGAAgG;IAChG,8FAA8F;IAC9F,IAAI,MAAM,CAAC,WAAW,IAAI,KAAK,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACzD,MAAM,IAAI,GACR,CAAC,MAAM,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3C,CAAC,MAAM,oBAAoB,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;QACxE,IAAI,IAAI,EAAE,YAAY,KAAK,IAAI;YAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1D,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,wBAAwB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACpG,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED;;8FAE8F;AAC9F,KAAK,UAAU,oBAAoB,CACjC,MAAc,EACd,SAAS,GAAG,KAAK;IAEjB,IAAI,IAAY,CAAC;IACjB,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,MAAM,EAAE,CAAC,CAAC;QACxE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;QAClB,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,MAAM,GAAG,CAAC,CAAoC,EAAE,EAAE;YACtD,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,IAAI,CAAC;gBAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;YACvC,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAChC,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9B,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;gBACX,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI;oBAAE,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpC,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC;gBAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAA+B,CAAC;gBACtE,MAAM,CAAC,CAAC,CAAC,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAC,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAEvC;0FAC0F;AAC1F,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAY3C,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,KAAK,GAAc,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACrD,MAAM,OAAO,CAAC,GAAG,CACf,UAAU,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3B,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,OAAO;QACxC,IAAI,MAAM,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,wBAAwB,EAAE,CAAC;YAAE,OAAO;QACjF,2FAA2F;QAC3F,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CACH,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/render.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type WorkspaceError = {
|
|
|
23
23
|
kind: "reachable";
|
|
24
24
|
reason: "auth-required" | "stale-auth" | "unreachable";
|
|
25
25
|
server: string;
|
|
26
|
+
hasAuth: boolean;
|
|
26
27
|
};
|
|
27
28
|
/** Render a workspace failure as the canonical one-line `cotal …` sentence. */
|
|
28
29
|
export declare function renderWorkspaceError(e: WorkspaceError): string;
|
package/dist/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,YAAY,GAAG,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpH,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,cAAc,GAAG,MAAM,CAS9D"}
|
package/dist/render.js
CHANGED
|
@@ -6,7 +6,7 @@ export function renderWorkspaceError(e) {
|
|
|
6
6
|
case "preflight":
|
|
7
7
|
return renderPreflightFailure(e.failure, e.target, e.pruned);
|
|
8
8
|
case "reachable":
|
|
9
|
-
return renderReachable(e.reason, e.server);
|
|
9
|
+
return renderReachable(e.reason, e.server, e.hasAuth);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
/** "Which mesh" resolution failures — maps a {@link MeshTargetError}'s `{code, details}` to copy. */
|
|
@@ -62,15 +62,26 @@ function renderPreflightFailure(kind, t, pruned) {
|
|
|
62
62
|
return `✗ broker at ${t.server} requires auth, but this mesh is open (no trust material) - use \`--space <name>\` for an auth mesh, or run \`cotal up\` here without \`--open\``;
|
|
63
63
|
case "stale-auth":
|
|
64
64
|
return `✗ credentials for "${t.space}" have EXPIRED - this mesh enforces bounded credential lifetimes; run \`cotal doctor auth\` in ${t.root} for the diagnosis + exact repair (the mesh itself is up)`;
|
|
65
|
+
case "tls-trust":
|
|
66
|
+
// INFO advertised tls_required (shape evidence only — unauthenticated). Never claims removal
|
|
67
|
+
// or peer identity; conservatively keep the record and point at the trust-store repair.
|
|
68
|
+
return `✗ mesh "${t.space}" at ${t.server} requires TLS but this client could not complete the handshake (untrusted or missing CA?) - set \`NODE_EXTRA_CA_CERTS\` to the issuing CA for a private CA, or fix the trust store; a TLS-required NATS listener still greets there (INFO is unauthenticated — not mesh identity) so the registry entry was conservatively kept`;
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
/** Plain reachability for a RAW (off-registry) probe — the `--creds` / `--server`+unregistered-`--space`
|
|
68
72
|
* escape hatch, which never touches the registry (no prune, no stale-entry wording). */
|
|
69
|
-
function renderReachable(reason, server) {
|
|
73
|
+
function renderReachable(reason, server, hasAuth) {
|
|
70
74
|
if (reason === "stale-auth")
|
|
71
75
|
return `✗ credential EXPIRED at ${server} - bounded lifetime reached (credential death); run \`cotal doctor auth\` where the mesh runs, or re-mint the credential`;
|
|
72
|
-
|
|
76
|
+
if (reason === "unreachable")
|
|
77
|
+
return `✗ can't reach a broker at ${server} - is it running? (\`cotal up\`)`;
|
|
78
|
+
// `auth-required` is two different user situations with two different repairs, and this path used
|
|
79
|
+
// to collapse them. The registry path never did: `classifyPreflightFailure` branches on the same
|
|
80
|
+
// bit into `creds-rejected` vs `open-wants-auth`. Told "credentials rejected" after sending NO
|
|
81
|
+
// credentials, a caller goes looking for what is wrong with creds they never had, when the actual
|
|
82
|
+
// next step is to obtain some.
|
|
83
|
+
return hasAuth
|
|
73
84
|
? `✗ credentials rejected at ${server} - check your creds, or the broker wants different auth`
|
|
74
|
-
: `✗
|
|
85
|
+
: `✗ broker at ${server} requires auth, but no credentials were supplied - pass \`--creds <file>\`, or \`cotal join\` with a link/token`;
|
|
75
86
|
}
|
|
76
87
|
//# sourceMappingURL=render.js.map
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAmBA,+EAA+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,CAAiB;IACpD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACpC,KAAK,WAAW;YACd,OAAO,sBAAsB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;QAC/D,KAAK,WAAW;YACd,OAAO,eAAe,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type SpaceAuth } from "@cotal-ai/core";
|
|
2
|
+
/**
|
|
3
|
+
* The class-3 ($SYS) renewal owner's half, the counterpart to `renewal.ts`, which owns the class-2
|
|
4
|
+
* standing renewal and deliberately EXCLUDES these two files.
|
|
5
|
+
*
|
|
6
|
+
* `membership-observer.creds` and `connection-evictor.creds` are `rotation-renewed`: they are signed
|
|
7
|
+
* by the system-account seed, which is never persisted (`putSpaceAuth` strips it), so no running
|
|
8
|
+
* process can re-sign them for their existing identity the way `remintDaemonCreds` does. Their only
|
|
9
|
+
* renewal is a system-account ROTATION: a fresh $SYS account under the SAME broker operator, fresh
|
|
10
|
+
* creds minted from its in-memory seed, and a broker that reloads the new operator/system JWTs.
|
|
11
|
+
*
|
|
12
|
+
* That rotation is NOT destructive. `rotateSystemAccount` re-signs the operator JWT with a new
|
|
13
|
+
* `system_account` using the operator's own (unchanged) seed, so the space's DATA account, its
|
|
14
|
+
* signing key, every agent credential minted from it, and the JetStream store all survive untouched.
|
|
15
|
+
* What dies is exactly what should: the retired system account and any copy of the old $SYS creds.
|
|
16
|
+
*
|
|
17
|
+
* The one thing this module cannot do is make the broker load the result. Rotation is only complete
|
|
18
|
+
* once the broker restarts on a `server.conf` rendered from the rotated record, which is why the
|
|
19
|
+
* operator surface is `cotal up --rotate-sys` (the one command that already renders that config and
|
|
20
|
+
* boots the broker + daemons from it) rather than a standalone verb that would leave the mesh in a
|
|
21
|
+
* half-rotated state.
|
|
22
|
+
*
|
|
23
|
+
* WORKSTATION ONLY, deliberately: unlike `renewal.ts`, nothing here takes a {@link SecretStore}. The
|
|
24
|
+
* $SYS pair lives on the raw FS with no store seam, and the multi-tenant guard below reads the FS
|
|
25
|
+
* account records, which an injected store could neither supply nor be enumerated for. See
|
|
26
|
+
* {@link rotateSystemCreds}.
|
|
27
|
+
*/
|
|
28
|
+
/** The two $SYS credential files, by the same key↔filename convention `renewal.ts` uses. They stay on
|
|
29
|
+
* the raw FS, with no secret-store seam: renewing them means rewriting the broker config too, so the
|
|
30
|
+
* pair and the trust record move together or not at all, which is not something a store can hold half
|
|
31
|
+
* of. Named here so the rotation writer, the staleness check and `cotal clean`'s removal list cannot
|
|
32
|
+
* drift apart. */
|
|
33
|
+
export declare const SYSTEM_CREDS_FILES: readonly ["membership-observer.creds", "connection-evictor.creds"];
|
|
34
|
+
export interface SystemRotationResult {
|
|
35
|
+
/** The broker record's new system-account generation (the successor discriminator `putSpaceAuth` guards). */
|
|
36
|
+
gen: number;
|
|
37
|
+
/** The rotated bundle. The caller MUST render `server.conf` from this, not from its pre-rotation copy. */
|
|
38
|
+
auth: SpaceAuth;
|
|
39
|
+
/** Expiry (epoch sec) of the freshly minted $SYS creds, so the caller can print the next rotation date. */
|
|
40
|
+
expiresAt?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Rotate the space's system account and re-mint both $SYS creds against it.
|
|
44
|
+
*
|
|
45
|
+
* `expectedSpace` is validated by `getSpaceAuth` against the store's signer, the same cross-space
|
|
46
|
+
* guard `remintDaemonCreds` runs: rotating with a foreign space's operator would re-issue an operator
|
|
47
|
+
* JWT this broker does not trust and strand every account under it.
|
|
48
|
+
*
|
|
49
|
+
* Ordering is the safety property, and it is a DIRECTIONAL one, not atomicity. All MINTING (the
|
|
50
|
+
* rotation and both creds) happens in memory before anything is persisted, so a mint failure changes
|
|
51
|
+
* nothing on disk. Then the trust record commits, where `putSpaceAuth`'s generation guard refuses a
|
|
52
|
+
* stale or non-successor write. Then the creds land. What this buys is a single failure direction: a
|
|
53
|
+
* cred file is never overwritten for a system account the record does not already carry, because the
|
|
54
|
+
* inverse order could clobber the last-good creds with creds for an authority the broker will never
|
|
55
|
+
* load, which is the availability loss `remintDaemonCreds` guards on its own class.
|
|
56
|
+
*
|
|
57
|
+
* It does NOT make the persistence atomic. `putSpaceAuth` is two puts and the creds are two more, so
|
|
58
|
+
* a crash leaves the record AHEAD of the creds. That state is detected rather than prevented (see
|
|
59
|
+
* {@link staleSystemCreds}) and is repaired by re-running the rotation.
|
|
60
|
+
*
|
|
61
|
+
* THROWS rather than degrading: a stripped signer (no operator seed) cannot rotate, and a caller that
|
|
62
|
+
* ignored the throw would boot a broker whose config still carries the retired system account.
|
|
63
|
+
*/
|
|
64
|
+
export declare function rotateSystemCreds(root: string, expectedSpace: string): Promise<SystemRotationResult>;
|
|
65
|
+
/** A $SYS credential file on disk that the persisted trust record does NOT authorize. */
|
|
66
|
+
export interface StaleSystemCred {
|
|
67
|
+
file: string;
|
|
68
|
+
/** The retired system account that signed it; absent when the file could not be parsed. */
|
|
69
|
+
iss?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The $SYS cred files whose issuer is not `sysPub`: the ONE staleness question, asked in one place
|
|
73
|
+
* so every surface answers it identically.
|
|
74
|
+
*
|
|
75
|
+
* This exists because expiry cannot see the failure. A rotation that committed the trust record and
|
|
76
|
+
* then died leaves credential files that parse, are nowhere near their expiry, and are refused by the
|
|
77
|
+
* broker, because the account that signed them no longer exists as far as the successor config is
|
|
78
|
+
* concerned. Comparing the two files to each other is not enough either: a crash BEFORE either write
|
|
79
|
+
* leaves them stale but mutually consistent. Only the persisted record settles it, which is why this
|
|
80
|
+
* takes `sysPub` and why the callers are the two surfaces that hold it: the boot path, which warns
|
|
81
|
+
* before it renders a config the files cannot serve, and `cotal doctor auth`, which must never call
|
|
82
|
+
* this state healthy. The delivery daemon is deliberately NOT a caller: it never loads the signer.
|
|
83
|
+
*
|
|
84
|
+
* Absent files are not stale (an unprovisioned space is a different, separately reported state), and
|
|
85
|
+
* an unreadable file is reported with no `iss` rather than throwing, because a diagnosis surface must not
|
|
86
|
+
* crash on the corruption it exists to describe.
|
|
87
|
+
*/
|
|
88
|
+
export declare function staleSystemCreds(root: string, sysPub: string): StaleSystemCred[];
|
|
89
|
+
//# sourceMappingURL=system-rotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-rotation.d.ts","sourceRoot":"","sources":["../src/system-rotation.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;mBAImB;AACnB,eAAO,MAAM,kBAAkB,oEAAqE,CAAC;AAErG,MAAM,WAAW,oBAAoB;IACnC,6GAA6G;IAC7G,GAAG,EAAE,MAAM,CAAC;IACZ,0GAA0G;IAC1G,IAAI,EAAE,SAAS,CAAC;IAChB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAkD1G;AAED,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAehF"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { credsClaims, mintConnectionEvictorCreds, mintMembershipObserverCreds, newIdentity, rotateSystemAccount, writeSecretFileAtomic, } from "@cotal-ai/core";
|
|
4
|
+
import { assertSingleSpaceBroker, authDir, getSpaceAuth, putSpaceAuth } from "./auth-paths.js";
|
|
5
|
+
import { workspaceSecretStore } from "./secret-store-fs.js";
|
|
6
|
+
/**
|
|
7
|
+
* The class-3 ($SYS) renewal owner's half, the counterpart to `renewal.ts`, which owns the class-2
|
|
8
|
+
* standing renewal and deliberately EXCLUDES these two files.
|
|
9
|
+
*
|
|
10
|
+
* `membership-observer.creds` and `connection-evictor.creds` are `rotation-renewed`: they are signed
|
|
11
|
+
* by the system-account seed, which is never persisted (`putSpaceAuth` strips it), so no running
|
|
12
|
+
* process can re-sign them for their existing identity the way `remintDaemonCreds` does. Their only
|
|
13
|
+
* renewal is a system-account ROTATION: a fresh $SYS account under the SAME broker operator, fresh
|
|
14
|
+
* creds minted from its in-memory seed, and a broker that reloads the new operator/system JWTs.
|
|
15
|
+
*
|
|
16
|
+
* That rotation is NOT destructive. `rotateSystemAccount` re-signs the operator JWT with a new
|
|
17
|
+
* `system_account` using the operator's own (unchanged) seed, so the space's DATA account, its
|
|
18
|
+
* signing key, every agent credential minted from it, and the JetStream store all survive untouched.
|
|
19
|
+
* What dies is exactly what should: the retired system account and any copy of the old $SYS creds.
|
|
20
|
+
*
|
|
21
|
+
* The one thing this module cannot do is make the broker load the result. Rotation is only complete
|
|
22
|
+
* once the broker restarts on a `server.conf` rendered from the rotated record, which is why the
|
|
23
|
+
* operator surface is `cotal up --rotate-sys` (the one command that already renders that config and
|
|
24
|
+
* boots the broker + daemons from it) rather than a standalone verb that would leave the mesh in a
|
|
25
|
+
* half-rotated state.
|
|
26
|
+
*
|
|
27
|
+
* WORKSTATION ONLY, deliberately: unlike `renewal.ts`, nothing here takes a {@link SecretStore}. The
|
|
28
|
+
* $SYS pair lives on the raw FS with no store seam, and the multi-tenant guard below reads the FS
|
|
29
|
+
* account records, which an injected store could neither supply nor be enumerated for. See
|
|
30
|
+
* {@link rotateSystemCreds}.
|
|
31
|
+
*/
|
|
32
|
+
/** The two $SYS credential files, by the same key↔filename convention `renewal.ts` uses. They stay on
|
|
33
|
+
* the raw FS, with no secret-store seam: renewing them means rewriting the broker config too, so the
|
|
34
|
+
* pair and the trust record move together or not at all, which is not something a store can hold half
|
|
35
|
+
* of. Named here so the rotation writer, the staleness check and `cotal clean`'s removal list cannot
|
|
36
|
+
* drift apart. */
|
|
37
|
+
export const SYSTEM_CREDS_FILES = ["membership-observer.creds", "connection-evictor.creds"];
|
|
38
|
+
/**
|
|
39
|
+
* Rotate the space's system account and re-mint both $SYS creds against it.
|
|
40
|
+
*
|
|
41
|
+
* `expectedSpace` is validated by `getSpaceAuth` against the store's signer, the same cross-space
|
|
42
|
+
* guard `remintDaemonCreds` runs: rotating with a foreign space's operator would re-issue an operator
|
|
43
|
+
* JWT this broker does not trust and strand every account under it.
|
|
44
|
+
*
|
|
45
|
+
* Ordering is the safety property, and it is a DIRECTIONAL one, not atomicity. All MINTING (the
|
|
46
|
+
* rotation and both creds) happens in memory before anything is persisted, so a mint failure changes
|
|
47
|
+
* nothing on disk. Then the trust record commits, where `putSpaceAuth`'s generation guard refuses a
|
|
48
|
+
* stale or non-successor write. Then the creds land. What this buys is a single failure direction: a
|
|
49
|
+
* cred file is never overwritten for a system account the record does not already carry, because the
|
|
50
|
+
* inverse order could clobber the last-good creds with creds for an authority the broker will never
|
|
51
|
+
* load, which is the availability loss `remintDaemonCreds` guards on its own class.
|
|
52
|
+
*
|
|
53
|
+
* It does NOT make the persistence atomic. `putSpaceAuth` is two puts and the creds are two more, so
|
|
54
|
+
* a crash leaves the record AHEAD of the creds. That state is detected rather than prevented (see
|
|
55
|
+
* {@link staleSystemCreds}) and is repaired by re-running the rotation.
|
|
56
|
+
*
|
|
57
|
+
* THROWS rather than degrading: a stripped signer (no operator seed) cannot rotate, and a caller that
|
|
58
|
+
* ignored the throw would boot a broker whose config still carries the retired system account.
|
|
59
|
+
*/
|
|
60
|
+
export async function rotateSystemCreds(root, expectedSpace) {
|
|
61
|
+
// A rotation is BROKER-wide, not space-wide, however it is spelled: the system account lives in
|
|
62
|
+
// the shared broker record and the re-issued operator JWT names the successor for every space
|
|
63
|
+
// under it, while the two $SYS cred files are per-ROOT (one pair, its observer permissions pinned
|
|
64
|
+
// to ONE data account). So on a multi-tenant root, "rotate space A" would retire space B's system
|
|
65
|
+
// account and leave no cred that can observe B. Same guard, same reason, as `cotal down`,
|
|
66
|
+
// `cotal backup`, `cotal clean` and `cotal up --restore`.
|
|
67
|
+
//
|
|
68
|
+
// The guard reads the FS account records, and that is exactly why this function takes NO
|
|
69
|
+
// SecretStore. `SecretStore` cannot enumerate, so an injected multi-tenant store paired with an
|
|
70
|
+
// empty local root would sail past this check and retire the system account for every tenant in
|
|
71
|
+
// it: a guard that looks broker-wide while enforcing nothing. Taking the store away makes the
|
|
72
|
+
// mismatch impossible to express rather than merely refused at runtime, and it costs nothing real:
|
|
73
|
+
// the $SYS pair is FS-only anyway (a hosted composition has nowhere in the store to put it), so
|
|
74
|
+
// this operation was never store-composable to begin with. If store enumeration ever exists, this
|
|
75
|
+
// becomes a real hosted seam; until then it is a workstation operation and says so.
|
|
76
|
+
assertSingleSpaceBroker(authDir(root), "a system-account rotation (`cotal up --rotate-sys`)");
|
|
77
|
+
const s = workspaceSecretStore(root);
|
|
78
|
+
const auth = await getSpaceAuth(s, expectedSpace);
|
|
79
|
+
if (!auth)
|
|
80
|
+
throw new Error(`rotateSystemCreds: no trust record for space "${expectedSpace}" - there is no system account to rotate`);
|
|
81
|
+
if (!auth.operator.seed)
|
|
82
|
+
throw new Error("rotateSystemCreds: the broker operator seed is required to rotate the system account (this root holds a stripped signer) - run the rotation where the broker trust record lives");
|
|
83
|
+
// In-memory first: the rotation and BOTH mints must succeed before anything is persisted.
|
|
84
|
+
const rotated = await rotateSystemAccount(auth);
|
|
85
|
+
const observer = await mintMembershipObserverCreds(rotated, newIdentity());
|
|
86
|
+
const evictor = await mintConnectionEvictorCreds(rotated, newIdentity());
|
|
87
|
+
// Commit the trust record FIRST: the generation guard lives in this call, and it is what makes the
|
|
88
|
+
// successor real. Only then the creds that record authorizes.
|
|
89
|
+
//
|
|
90
|
+
// This is NOT one atomic act, and the code must not pretend otherwise: `putSpaceAuth` is itself two
|
|
91
|
+
// puts, and the two cred writes are two more. A crash anywhere in this sequence leaves the record
|
|
92
|
+
// AHEAD of the creds: both files stale (crash before either write) or one stale (crash between
|
|
93
|
+
// them). The window is not closed here; it is DETECTED, by {@link staleSystemCreds}, which every
|
|
94
|
+
// boot and every `doctor auth` runs against the persisted record. It cannot be repaired in place
|
|
95
|
+
// either: the successor's signing seed is gone the moment it is persisted, so the only repair is
|
|
96
|
+
// another rotation, which is idempotent and costs one generation. A durable rotation journal would
|
|
97
|
+
// close the window instead of reporting it; that is deliberately not built for a display-only feed
|
|
98
|
+
// plus an eviction rail whose recovery is one re-run.
|
|
99
|
+
//
|
|
100
|
+
// Each file is still written atomically, so no reader can ever see a half-written credential.
|
|
101
|
+
await putSpaceAuth(s, rotated);
|
|
102
|
+
writeSecretFileAtomic(join(root, ".cotal", SYSTEM_CREDS_FILES[0]), observer);
|
|
103
|
+
writeSecretFileAtomic(join(root, ".cotal", SYSTEM_CREDS_FILES[1]), evictor);
|
|
104
|
+
return { gen: rotated.gen ?? 0, auth: rotated, expiresAt: credsClaims(observer).exp };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The $SYS cred files whose issuer is not `sysPub`: the ONE staleness question, asked in one place
|
|
108
|
+
* so every surface answers it identically.
|
|
109
|
+
*
|
|
110
|
+
* This exists because expiry cannot see the failure. A rotation that committed the trust record and
|
|
111
|
+
* then died leaves credential files that parse, are nowhere near their expiry, and are refused by the
|
|
112
|
+
* broker, because the account that signed them no longer exists as far as the successor config is
|
|
113
|
+
* concerned. Comparing the two files to each other is not enough either: a crash BEFORE either write
|
|
114
|
+
* leaves them stale but mutually consistent. Only the persisted record settles it, which is why this
|
|
115
|
+
* takes `sysPub` and why the callers are the two surfaces that hold it: the boot path, which warns
|
|
116
|
+
* before it renders a config the files cannot serve, and `cotal doctor auth`, which must never call
|
|
117
|
+
* this state healthy. The delivery daemon is deliberately NOT a caller: it never loads the signer.
|
|
118
|
+
*
|
|
119
|
+
* Absent files are not stale (an unprovisioned space is a different, separately reported state), and
|
|
120
|
+
* an unreadable file is reported with no `iss` rather than throwing, because a diagnosis surface must not
|
|
121
|
+
* crash on the corruption it exists to describe.
|
|
122
|
+
*/
|
|
123
|
+
export function staleSystemCreds(root, sysPub) {
|
|
124
|
+
const stale = [];
|
|
125
|
+
for (const file of SYSTEM_CREDS_FILES) {
|
|
126
|
+
const path = join(root, ".cotal", file);
|
|
127
|
+
if (!existsSync(path))
|
|
128
|
+
continue;
|
|
129
|
+
let iss;
|
|
130
|
+
try {
|
|
131
|
+
iss = credsClaims(readFileSync(path, "utf8")).iss;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
stale.push({ file }); // unreadable: cannot be shown to match, so it is not assumed to
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (iss !== sysPub)
|
|
138
|
+
stale.push({ file, iss });
|
|
139
|
+
}
|
|
140
|
+
return stale;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=system-rotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-rotation.js","sourceRoot":"","sources":["../src/system-rotation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/workspace",
|
|
3
3
|
"description": "Cotal machine-local workstation layer: the ~/.cotal mesh registry, target resolution, preflight, on-disk auth-path I/O, and the command-copy renderer — operator concerns over a local checkout, kept separate from the wire protocol in @cotal-ai/core.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@cotal-ai/core": "0.
|
|
21
|
+
"@cotal-ai/core": "0.18.0"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist"
|