@cotal-ai/core 0.90.1 → 0.92.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/agent-file.d.ts +2 -1
- package/dist/agent-file.d.ts.map +1 -1
- package/dist/agent-file.js +5 -3
- package/dist/agent-file.js.map +1 -1
- package/dist/auth-provider.d.ts +155 -1
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/auth-provider.js +24 -0
- package/dist/auth-provider.js.map +1 -1
- package/dist/authority-store-config.d.ts +31 -0
- package/dist/authority-store-config.d.ts.map +1 -0
- package/dist/authority-store-config.js +45 -0
- package/dist/authority-store-config.js.map +1 -0
- package/dist/backup-config.d.ts +32 -1
- package/dist/backup-config.d.ts.map +1 -1
- package/dist/backup-config.js +11 -0
- package/dist/backup-config.js.map +1 -1
- package/dist/backup.d.ts +4 -0
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +14 -0
- package/dist/backup.js.map +1 -1
- package/dist/broker-identity.d.ts +24 -0
- package/dist/broker-identity.d.ts.map +1 -0
- package/dist/broker-identity.js +36 -0
- package/dist/broker-identity.js.map +1 -0
- package/dist/browser-control.d.ts +1 -0
- package/dist/browser-control.d.ts.map +1 -1
- package/dist/browser-control.js +1 -0
- package/dist/browser-control.js.map +1 -1
- package/dist/browser-validators-build.d.ts +1 -0
- package/dist/browser-validators-build.d.ts.map +1 -1
- package/dist/channels.d.ts +14 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +19 -5
- package/dist/channels.js.map +1 -1
- package/dist/command.d.ts +57 -0
- package/dist/command.d.ts.map +1 -1
- package/dist/command.js +49 -2
- package/dist/command.js.map +1 -1
- package/dist/connector-setup.d.ts +6 -0
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/connector.d.ts +3 -0
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js.map +1 -1
- package/dist/endpoint-binding.d.ts +66 -1
- package/dist/endpoint-binding.d.ts.map +1 -1
- package/dist/endpoint-binding.js +167 -118
- package/dist/endpoint-binding.js.map +1 -1
- package/dist/endpoint-call.js +1 -1
- package/dist/endpoint-call.js.map +1 -1
- package/dist/endpoint-caller.d.ts +2 -0
- package/dist/endpoint-caller.d.ts.map +1 -1
- package/dist/endpoint-caller.js.map +1 -1
- package/dist/endpoint-grants.d.ts +15 -5
- package/dist/endpoint-grants.d.ts.map +1 -1
- package/dist/endpoint-grants.js +17 -5
- package/dist/endpoint-grants.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +9 -0
- package/dist/endpoint-invoke.d.ts.map +1 -1
- package/dist/endpoint-invoke.js +7 -0
- package/dist/endpoint-invoke.js.map +1 -1
- package/dist/endpoint-schema-diff.d.ts +20 -0
- package/dist/endpoint-schema-diff.d.ts.map +1 -0
- package/dist/endpoint-schema-diff.js +15 -0
- package/dist/endpoint-schema-diff.js.map +1 -0
- package/dist/endpoint-serve.d.ts +4 -0
- package/dist/endpoint-serve.d.ts.map +1 -1
- package/dist/endpoint-serve.js +1 -1
- package/dist/endpoint-serve.js.map +1 -1
- package/dist/endpoint.d.ts +98 -5
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +350 -36
- package/dist/endpoint.js.map +1 -1
- package/dist/identity.d.ts +18 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +28 -1
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/issued-authority.d.ts +25 -1
- package/dist/issued-authority.d.ts.map +1 -1
- package/dist/issued-authority.js +24 -7
- package/dist/issued-authority.js.map +1 -1
- package/dist/membership-feed.d.ts +16 -12
- package/dist/membership-feed.d.ts.map +1 -1
- package/dist/membership-feed.js +143 -12
- package/dist/membership-feed.js.map +1 -1
- package/dist/one-line.d.ts +5 -4
- package/dist/one-line.d.ts.map +1 -1
- package/dist/one-line.js +6 -5
- package/dist/one-line.js.map +1 -1
- package/dist/provision.d.ts +8 -1
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +39 -17
- package/dist/provision.js.map +1 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +58 -20
- package/dist/registry.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +34 -2
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/remote-service-authority.d.ts +10 -6
- package/dist/remote-service-authority.d.ts.map +1 -1
- package/dist/remote-service-authority.js +4 -1
- package/dist/remote-service-authority.js.map +1 -1
- package/dist/run-admission.d.ts +24 -1
- package/dist/run-admission.d.ts.map +1 -1
- package/dist/run-admission.js +60 -3
- package/dist/run-admission.js.map +1 -1
- package/dist/run-cancel.d.ts +43 -0
- package/dist/run-cancel.d.ts.map +1 -0
- package/dist/run-cancel.js +72 -0
- package/dist/run-cancel.js.map +1 -0
- package/dist/run-host.d.ts +12 -0
- package/dist/run-host.d.ts.map +1 -1
- package/dist/run-submission-client.d.ts +35 -0
- package/dist/run-submission-client.d.ts.map +1 -0
- package/dist/run-submission-client.js +61 -0
- package/dist/run-submission-client.js.map +1 -0
- package/dist/run-submission.d.ts +78 -0
- package/dist/run-submission.d.ts.map +1 -0
- package/dist/run-submission.js +158 -0
- package/dist/run-submission.js.map +1 -0
- package/dist/sealed-reply.d.ts +21 -0
- package/dist/sealed-reply.d.ts.map +1 -0
- package/dist/sealed-reply.js +75 -0
- package/dist/sealed-reply.js.map +1 -0
- package/dist/secret-fs.d.ts +29 -6
- package/dist/secret-fs.d.ts.map +1 -1
- package/dist/secret-fs.js +107 -18
- package/dist/secret-fs.js.map +1 -1
- package/dist/space-deletion-targets.d.ts +4 -0
- package/dist/space-deletion-targets.d.ts.map +1 -0
- package/dist/space-deletion-targets.js +15 -0
- package/dist/space-deletion-targets.js.map +1 -0
- package/dist/space-initialization-diff.d.ts +47 -0
- package/dist/space-initialization-diff.d.ts.map +1 -0
- package/dist/space-initialization-diff.js +100 -0
- package/dist/space-initialization-diff.js.map +1 -0
- package/dist/space-initialization-observation.d.ts +52 -0
- package/dist/space-initialization-observation.d.ts.map +1 -0
- package/dist/space-initialization-observation.js +133 -0
- package/dist/space-initialization-observation.js.map +1 -0
- package/dist/space-resource-definitions.d.ts +86 -0
- package/dist/space-resource-definitions.d.ts.map +1 -0
- package/dist/space-resource-definitions.js +133 -0
- package/dist/space-resource-definitions.js.map +1 -0
- package/dist/space-schema-diff.d.ts +45 -0
- package/dist/space-schema-diff.d.ts.map +1 -0
- package/dist/space-schema-diff.js +204 -0
- package/dist/space-schema-diff.js.map +1 -0
- package/dist/space-schema-observation.d.ts +45 -0
- package/dist/space-schema-observation.d.ts.map +1 -0
- package/dist/space-schema-observation.js +78 -0
- package/dist/space-schema-observation.js.map +1 -0
- package/dist/spaces.d.ts.map +1 -1
- package/dist/spaces.js +5 -25
- package/dist/spaces.js.map +1 -1
- package/dist/stream-schema-diff.d.ts +31 -0
- package/dist/stream-schema-diff.d.ts.map +1 -0
- package/dist/stream-schema-diff.js +45 -0
- package/dist/stream-schema-diff.js.map +1 -0
- package/dist/streams.d.ts +30 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +41 -18
- package/dist/streams.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time sealing of a reply to its caller. The caller mints a {@link SealRecipient}, sends only
|
|
3
|
+
* its public `X…` curve key, and keeps the private half in memory. The replier calls
|
|
4
|
+
* {@link sealJson}, which seals under a fresh one-time sender key and clears that key before it
|
|
5
|
+
* returns, and sends the sender's public key beside the sealed bytes. Only the recipient opens
|
|
6
|
+
* them.
|
|
7
|
+
*
|
|
8
|
+
* The cryptography is `@nats-io/nkeys` curve keys, unchanged: NaCl box (X25519 and
|
|
9
|
+
* XSalsa20-Poly1305) framed as `xkv1`, a random 24-byte nonce, then the box. That is the xkey format
|
|
10
|
+
* NATS uses to seal auth-callout responses. A sealed reply is confidential to its recipient. It
|
|
11
|
+
* does not identify the replier, because the sender key is one-time, so the rail that carried the
|
|
12
|
+
* reply stays the trust anchor and the opener checks every field it expects inside.
|
|
13
|
+
*/
|
|
14
|
+
import { createCurve, fromPublic } from "@nats-io/nkeys";
|
|
15
|
+
import { base64url, fromBase64url } from "./base64url.js";
|
|
16
|
+
/** Whether `key` is an encoded curve public key: `X`, 55 more base32 characters and a valid
|
|
17
|
+
* checksum. */
|
|
18
|
+
export function isCurvePublicKey(key) {
|
|
19
|
+
if (!/^X[A-Z2-7]{55}$/.test(key))
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
fromPublic(key);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Seal `value`, as JSON, to the curve key `recipient` under a one-time sender key. `sealed` is
|
|
30
|
+
* base64url. Throws for a recipient that is not a curve public key. */
|
|
31
|
+
export function sealJson(value, recipient) {
|
|
32
|
+
if (!isCurvePublicKey(recipient))
|
|
33
|
+
throw new Error("the seal recipient is not a curve public key");
|
|
34
|
+
const sender = createCurve();
|
|
35
|
+
try {
|
|
36
|
+
const sealed = sender.seal(new TextEncoder().encode(JSON.stringify(value)), recipient);
|
|
37
|
+
return { sealer: sender.getPublicKey(), sealed: base64url(sealed) };
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
sender.clear();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** A one-time curve key for receiving one sealed reply. It opens at most once and is cleared
|
|
44
|
+
* then, or by {@link SealRecipient.clear}. */
|
|
45
|
+
export class SealRecipient {
|
|
46
|
+
publicKey;
|
|
47
|
+
key;
|
|
48
|
+
constructor() {
|
|
49
|
+
this.key = createCurve();
|
|
50
|
+
this.publicKey = this.key.getPublicKey();
|
|
51
|
+
}
|
|
52
|
+
/** The JSON value `sealer` sealed to this key, or undefined when the bytes do not open or are
|
|
53
|
+
* not JSON, or this key was already used or cleared. */
|
|
54
|
+
openJson(sealed, sealer) {
|
|
55
|
+
const key = this.key;
|
|
56
|
+
this.key = undefined;
|
|
57
|
+
if (!key)
|
|
58
|
+
return undefined;
|
|
59
|
+
try {
|
|
60
|
+
const opened = key.open(fromBase64url(sealed), sealer);
|
|
61
|
+
return opened ? JSON.parse(new TextDecoder().decode(opened)) : undefined;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
key.clear();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
clear() {
|
|
71
|
+
this.key?.clear();
|
|
72
|
+
this.key = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=sealed-reply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed-reply.js","sourceRoot":"","sources":["../src/sealed-reply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAgB,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE1D;gBACgB;AAChB,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,CAAC;QACH,UAAU,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;wEACwE;AACxE,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,SAAiB;IACxD,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClG,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QACvF,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;IACtE,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED;+CAC+C;AAC/C,MAAM,OAAO,aAAa;IACf,SAAS,CAAS;IACnB,GAAG,CAAsB;IAEjC;QACE,IAAI,CAAC,GAAG,GAAG,WAAW,EAAE,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC3C,CAAC;IAED;6DACyD;IACzD,QAAQ,CAAC,MAAc,EAAE,MAAc;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC;QACrB,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC;IACvB,CAAC;CACF"}
|
package/dist/secret-fs.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lock a file or directory down to the current user (+ SYSTEM + Administrators) only.
|
|
3
3
|
*
|
|
4
|
-
* POSIX:
|
|
4
|
+
* POSIX: a file takes {@link openPrivate}'s rule, so a symlink, a non-regular file or another user's
|
|
5
|
+
* file is refused with `EPERM` and left unchanged, and an owned file is set to 0o600 through the
|
|
6
|
+
* descriptor. A directory is set to 0o700 with `chmod` by path, which follows a symlinked directory:
|
|
7
|
+
* a symlinked `~/.cotal` or project `.cotal` is a working setup, so it is not refused.
|
|
5
8
|
* win32: harden the NTFS ACL with `icacls`: `/inheritance:r` drops inherited ACEs; `/remove:g` drops
|
|
6
9
|
* any pre-existing EXPLICIT broad grant (Everyone / Authenticated Users / Users) that `/grant:r`
|
|
7
10
|
* alone would leave intact (e.g. on a pre-planted or pre-existing target); `/grant:r` then sets ONLY
|
|
@@ -13,12 +16,32 @@
|
|
|
13
16
|
*/
|
|
14
17
|
export declare function hardenPrivate(path: string, kind: "file" | "dir"): void;
|
|
15
18
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
19
|
+
* Open a private log for appending under {@link openPrivate}'s rule: created 0o600, never through a
|
|
20
|
+
* final symlink, refused unless it is this user's regular file, and an owned log set to 0o600. The
|
|
21
|
+
* caller owns the returned descriptor.
|
|
22
|
+
*/
|
|
23
|
+
export declare function openPrivateAppend(path: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* Write a private secret file through {@link openPrivate}: mode 0o600 on the descriptor before the
|
|
26
|
+
* bytes, whether or not the name already existed, and on POSIX a refusal for a final symlink, a
|
|
27
|
+
* non-regular file or a file another user owns. Then {@link hardenPrivate} for the win32 ACL.
|
|
28
|
+
* FAIL-CLOSED — if hardening throws, the hardening error propagates (the caller never proceeds as if
|
|
29
|
+
* the secret were safe) and the just-written file is best-effort deleted so it isn't left readable.
|
|
20
30
|
*/
|
|
21
31
|
export declare function writeSecretFile(path: string, data: string | Buffer): void;
|
|
32
|
+
/**
|
|
33
|
+
* Write an owner-private record in place under {@link openPrivate}'s rule, without the win32 ACL
|
|
34
|
+
* step {@link writeSecretFile} adds: for a record that is not a secret, such as a pid identity pin
|
|
35
|
+
* or a process marker. On POSIX a symlink, a non-regular file or a file another user owns is refused
|
|
36
|
+
* with `EPERM` and left unchanged.
|
|
37
|
+
*/
|
|
38
|
+
export declare function writePrivateRecord(path: string, data: string | Buffer): void;
|
|
39
|
+
/**
|
|
40
|
+
* Create an owner-private record that must not exist yet, under {@link openPrivate}'s rule with
|
|
41
|
+
* `O_EXCL` and without the win32 ACL step: for a temporary that a rename then publishes. On POSIX any
|
|
42
|
+
* existing name, a symlink included, fails with `EEXIST` and is left as it was.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createPrivateRecord(path: string, data: string | Buffer): void;
|
|
22
45
|
/**
|
|
23
46
|
* Like {@link writeSecretFile} but ATOMIC: write a private temp sibling (same 0o600 + win32-ACL
|
|
24
47
|
* hardening), then rename it over the target. `renameSync` is an atomic replace on POSIX and via
|
|
@@ -51,7 +74,7 @@ export declare function __setPublishLinkForTest(fn: ((from: string, to: string)
|
|
|
51
74
|
* two creators that collided on the name would plain-overwrite each other's bytes, and then the
|
|
52
75
|
* one whose `link` succeeded would return the candidate IT minted while the published file held
|
|
53
76
|
* the OTHER one's identity: the exact split this function exists to prevent, reintroduced one
|
|
54
|
-
* step earlier. `
|
|
77
|
+
* step earlier. `O_EXCL` on the temp makes a collision fail loudly instead of silently swapping bytes.
|
|
55
78
|
*/
|
|
56
79
|
export declare function writeSecretFileCreateOnly(path: string, data: string | Buffer): void;
|
|
57
80
|
/** Create a private directory chain (recursive) and harden it — call BEFORE writing secrets into it
|
package/dist/secret-fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-fs.d.ts","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-fs.d.ts","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"AAqDA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CA4BtE;AAkDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7E;AA+BD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa/E;AAiDD;wEACwE;AACxE,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAElG;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiCnF;AAED;;2EAE2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAG9C"}
|
package/dist/secret-fs.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* path). The fix is to harden the NTFS ACL explicitly with the built-in `icacls` (no new
|
|
9
9
|
* dependency). See {@link hardenPrivate}.
|
|
10
10
|
*/
|
|
11
|
-
import { chmodSync, closeSync, fchmodSync, linkSync, lstatSync, mkdirSync, openSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
11
|
+
import { chmodSync, closeSync, constants, fchmodSync, fstatSync, ftruncateSync, linkSync, lstatSync, mkdirSync, openSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
13
|
-
import { join } from "node:path";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
14
|
const isWin = process.platform === "win32";
|
|
15
15
|
/** Absolute path to a System32 tool — `icacls`/`whoami` are resolved from `%SystemRoot%` (not PATH)
|
|
16
16
|
* so a hijacked PATH can't substitute a malicious binary during the secret-hardening window. */
|
|
@@ -36,7 +36,10 @@ const BROAD_SIDS = ["S-1-1-0", "S-1-5-11", "S-1-5-32-545"];
|
|
|
36
36
|
/**
|
|
37
37
|
* Lock a file or directory down to the current user (+ SYSTEM + Administrators) only.
|
|
38
38
|
*
|
|
39
|
-
* POSIX:
|
|
39
|
+
* POSIX: a file takes {@link openPrivate}'s rule, so a symlink, a non-regular file or another user's
|
|
40
|
+
* file is refused with `EPERM` and left unchanged, and an owned file is set to 0o600 through the
|
|
41
|
+
* descriptor. A directory is set to 0o700 with `chmod` by path, which follows a symlinked directory:
|
|
42
|
+
* a symlinked `~/.cotal` or project `.cotal` is a working setup, so it is not refused.
|
|
40
43
|
* win32: harden the NTFS ACL with `icacls`: `/inheritance:r` drops inherited ACEs; `/remove:g` drops
|
|
41
44
|
* any pre-existing EXPLICIT broad grant (Everyone / Authenticated Users / Users) that `/grant:r`
|
|
42
45
|
* alone would leave intact (e.g. on a pre-planted or pre-existing target); `/grant:r` then sets ONLY
|
|
@@ -48,7 +51,10 @@ const BROAD_SIDS = ["S-1-1-0", "S-1-5-11", "S-1-5-32-545"];
|
|
|
48
51
|
*/
|
|
49
52
|
export function hardenPrivate(path, kind) {
|
|
50
53
|
if (!isWin) {
|
|
51
|
-
|
|
54
|
+
if (kind === "file")
|
|
55
|
+
closeSync(openPrivate(path, constants.O_RDONLY));
|
|
56
|
+
else
|
|
57
|
+
chmodSync(path, 0o700);
|
|
52
58
|
return;
|
|
53
59
|
}
|
|
54
60
|
const perm = kind === "dir" ? "(OI)(CI)(F)" : "(F)";
|
|
@@ -69,27 +75,109 @@ export function hardenPrivate(path, kind) {
|
|
|
69
75
|
`Cotal will not leave a secret with a permissive ACL - ensure the path is on an NTFS volume and %SystemRoot%\\System32\\icacls.exe is available.`);
|
|
70
76
|
}
|
|
71
77
|
}
|
|
78
|
+
/** A refused private open. The name was left as it was. */
|
|
79
|
+
function notPrivate(path, why) {
|
|
80
|
+
const refused = new Error(`refusing to write "${path}": it ${why}; it was left unchanged`);
|
|
81
|
+
refused.code = "EPERM";
|
|
82
|
+
return refused;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Open `path` for writing as this user's private file, mode 0o600 when the open creates it.
|
|
86
|
+
*
|
|
87
|
+
* POSIX: the open never follows a final symlink (`O_NOFOLLOW`) and never waits on a FIFO
|
|
88
|
+
* (`O_NONBLOCK`). The opened inode must be a regular file this user owns; anything else is refused
|
|
89
|
+
* with code `EPERM` and left unchanged. An owned file is then set to 0o600 through the descriptor,
|
|
90
|
+
* which names no path, so a name swapped after the open cannot redirect the change.
|
|
91
|
+
*
|
|
92
|
+
* win32: POSIX owners, modes and `O_NOFOLLOW` are not that platform's protection model; its boundary
|
|
93
|
+
* is the NTFS ACL (see {@link hardenPrivate}). So the open there takes mode 0o600 with no owner check.
|
|
94
|
+
*/
|
|
95
|
+
function openPrivate(path, flags) {
|
|
96
|
+
if (isWin) {
|
|
97
|
+
const fd = openSync(path, flags, 0o600);
|
|
98
|
+
try {
|
|
99
|
+
fchmodSync(fd, 0o600);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
closeSync(fd);
|
|
103
|
+
throw e;
|
|
104
|
+
}
|
|
105
|
+
return fd;
|
|
106
|
+
}
|
|
107
|
+
let fd;
|
|
108
|
+
try {
|
|
109
|
+
fd = openSync(path, flags | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
if (e.code === "ELOOP")
|
|
113
|
+
throw notPrivate(path, "is a symlink");
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const info = fstatSync(fd);
|
|
118
|
+
if (!info.isFile())
|
|
119
|
+
throw notPrivate(path, "is not a regular file");
|
|
120
|
+
if (info.uid !== process.getuid())
|
|
121
|
+
throw notPrivate(path, `is owned by uid ${info.uid}, not this user`);
|
|
122
|
+
fchmodSync(fd, 0o600);
|
|
123
|
+
return fd;
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
closeSync(fd);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Open a private log for appending under {@link openPrivate}'s rule: created 0o600, never through a
|
|
132
|
+
* final symlink, refused unless it is this user's regular file, and an owned log set to 0o600. The
|
|
133
|
+
* caller owns the returned descriptor.
|
|
134
|
+
*/
|
|
135
|
+
export function openPrivateAppend(path) {
|
|
136
|
+
return openPrivate(path, constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND);
|
|
137
|
+
}
|
|
72
138
|
/**
|
|
73
|
-
* Write a private secret file: mode 0o600 on the descriptor before the
|
|
74
|
-
* name already existed,
|
|
75
|
-
*
|
|
76
|
-
* the
|
|
139
|
+
* Write a private secret file through {@link openPrivate}: mode 0o600 on the descriptor before the
|
|
140
|
+
* bytes, whether or not the name already existed, and on POSIX a refusal for a final symlink, a
|
|
141
|
+
* non-regular file or a file another user owns. Then {@link hardenPrivate} for the win32 ACL.
|
|
142
|
+
* FAIL-CLOSED — if hardening throws, the hardening error propagates (the caller never proceeds as if
|
|
143
|
+
* the secret were safe) and the just-written file is best-effort deleted so it isn't left readable.
|
|
77
144
|
*/
|
|
78
145
|
export function writeSecretFile(path, data) {
|
|
79
|
-
writePrivate(path, data,
|
|
146
|
+
writePrivate(path, data, false);
|
|
80
147
|
}
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Write an owner-private record in place under {@link openPrivate}'s rule, without the win32 ACL
|
|
150
|
+
* step {@link writeSecretFile} adds: for a record that is not a secret, such as a pid identity pin
|
|
151
|
+
* or a process marker. On POSIX a symlink, a non-regular file or a file another user owns is refused
|
|
152
|
+
* with `EPERM` and left unchanged.
|
|
153
|
+
*/
|
|
154
|
+
export function writePrivateRecord(path, data) {
|
|
155
|
+
writeBytes(path, data, false);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create an owner-private record that must not exist yet, under {@link openPrivate}'s rule with
|
|
159
|
+
* `O_EXCL` and without the win32 ACL step: for a temporary that a rename then publishes. On POSIX any
|
|
160
|
+
* existing name, a symlink included, fails with `EEXIST` and is left as it was.
|
|
161
|
+
*/
|
|
162
|
+
export function createPrivateRecord(path, data) {
|
|
163
|
+
writeBytes(path, data, true);
|
|
164
|
+
}
|
|
165
|
+
/** {@link openPrivate}, then truncate and write the bytes; the descriptor is closed on every path. */
|
|
166
|
+
function writeBytes(path, data, exclusive) {
|
|
167
|
+
// No O_TRUNC: a name openPrivate refuses keeps its bytes, so truncation waits until it passed.
|
|
168
|
+
const fd = openPrivate(path, constants.O_WRONLY | constants.O_CREAT | (exclusive ? constants.O_EXCL : 0));
|
|
85
169
|
try {
|
|
86
|
-
|
|
87
|
-
fchmodSync(fd, 0o600);
|
|
170
|
+
ftruncateSync(fd);
|
|
88
171
|
writeFileSync(fd, data);
|
|
89
172
|
}
|
|
90
173
|
finally {
|
|
91
174
|
closeSync(fd);
|
|
92
175
|
}
|
|
176
|
+
}
|
|
177
|
+
/** The fail-closed write {@link writeSecretFile} documents, with exclusive create as a parameter so
|
|
178
|
+
* {@link writeSecretFileCreateOnlyRaw} can ask for `O_EXCL`. */
|
|
179
|
+
function writePrivate(path, data, exclusive) {
|
|
180
|
+
writeBytes(path, data, exclusive);
|
|
93
181
|
if (!isWin)
|
|
94
182
|
return;
|
|
95
183
|
try {
|
|
@@ -167,7 +255,7 @@ function writeSecretFileCreateOnlyRaw(path, data) {
|
|
|
167
255
|
throw taken;
|
|
168
256
|
}
|
|
169
257
|
}
|
|
170
|
-
writePrivate(path, data,
|
|
258
|
+
writePrivate(path, data, true);
|
|
171
259
|
}
|
|
172
260
|
/**
|
|
173
261
|
* Publish a completed temp inode at its final name. Separated from {@link writeSecretFileCreateOnly}
|
|
@@ -201,10 +289,11 @@ export function __setPublishLinkForTest(fn) {
|
|
|
201
289
|
* two creators that collided on the name would plain-overwrite each other's bytes, and then the
|
|
202
290
|
* one whose `link` succeeded would return the candidate IT minted while the published file held
|
|
203
291
|
* the OTHER one's identity: the exact split this function exists to prevent, reintroduced one
|
|
204
|
-
* step earlier. `
|
|
292
|
+
* step earlier. `O_EXCL` on the temp makes a collision fail loudly instead of silently swapping bytes.
|
|
205
293
|
*/
|
|
206
294
|
export function writeSecretFileCreateOnly(path, data) {
|
|
207
|
-
|
|
295
|
+
// Keep the sibling name independent of the destination, which may already fill NAME_MAX.
|
|
296
|
+
const tmp = join(dirname(path), `.cotal-create.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
208
297
|
// Exclusive, so EEXIST here means the temp name belongs to ANOTHER creator. Cleanup below must
|
|
209
298
|
// never run in that case: unlinking it would delete a live creator's bytes out from under it.
|
|
210
299
|
writeSecretFileCreateOnlyRaw(tmp, data);
|
package/dist/secret-fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-fs.js","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"secret-fs.js","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Native streams belonging to one space, including the backing streams of its KV and object buckets.
|
|
2
|
+
* A transfer stream is included only when its exact name was observed on this broker. */
|
|
3
|
+
export declare function spaceDeletionStreams(space: string, observedNames?: readonly string[]): string[];
|
|
4
|
+
//# sourceMappingURL=space-deletion-targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-deletion-targets.d.ts","sourceRoot":"","sources":["../src/space-deletion-targets.ts"],"names":[],"mappings":"AAOA;yFACyF;AACzF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,EAAE,CASnG"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { aclBucket, artifactBucket, channelBucket, chatStream, deliveryBucket, dlvStream, dmStream, inboxStream, isTransferStream, managerBucket, membershipBucket, membersBucket, objectStoreStream, presenceBucket, taskStream, } from "./subjects.js";
|
|
2
|
+
import { endpointPlaneStreamNames } from "./endpoint-binding.js";
|
|
3
|
+
/** Native streams belonging to one space, including the backing streams of its KV and object buckets.
|
|
4
|
+
* A transfer stream is included only when its exact name was observed on this broker. */
|
|
5
|
+
export function spaceDeletionStreams(space, observedNames = []) {
|
|
6
|
+
const base = [
|
|
7
|
+
chatStream(space), dmStream(space), taskStream(space), inboxStream(space), dlvStream(space),
|
|
8
|
+
`KV_${presenceBucket(space)}`, `KV_${channelBucket(space)}`, `KV_${membersBucket(space)}`,
|
|
9
|
+
`KV_${aclBucket(space)}`, `KV_${membershipBucket(space)}`,
|
|
10
|
+
`KV_${deliveryBucket(space)}`, `KV_${managerBucket(space)}`,
|
|
11
|
+
...endpointPlaneStreamNames(space), objectStoreStream(artifactBucket(space)),
|
|
12
|
+
];
|
|
13
|
+
return [...base, ...observedNames.filter((name) => isTransferStream(space, name))];
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=space-deletion-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-deletion-targets.js","sourceRoot":"","sources":["../src/space-deletion-targets.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EACzF,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAC7E,iBAAiB,EAAE,cAAc,EAAE,UAAU,GAC9C,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEjE;yFACyF;AACzF,MAAM,UAAU,oBAAoB,CAAC,KAAa,EAAE,gBAAmC,EAAE;IACvF,MAAM,IAAI,GAAG;QACX,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;QAC3F,MAAM,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE;QACzF,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,gBAAgB,CAAC,KAAK,CAAC,EAAE;QACzD,MAAM,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE;QAC3D,GAAG,wBAAwB,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;KAC7E,CAAC;IACF,OAAO,CAAC,GAAG,IAAI,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type SpaceInitializationObservation } from "./space-initialization-observation.js";
|
|
2
|
+
import type { ChannelRegistryFile } from "./channels.js";
|
|
3
|
+
import type { ChannelConfig, ChannelDefaults, DeliveryClass } from "./types.js";
|
|
4
|
+
type RegistryValue = ChannelConfig | ChannelDefaults;
|
|
5
|
+
export interface SpaceInitializationDiffRow {
|
|
6
|
+
key: string;
|
|
7
|
+
state: SpaceInitializationObservation["state"] | "not-observed" | "field-policy-required" | "observed-fields-match" | "patch-differs";
|
|
8
|
+
observedRevision: number | null;
|
|
9
|
+
seedRequested: boolean;
|
|
10
|
+
profileDefault: "not-applicable" | "not-established" | "preserve-explicit" | "propose-profile";
|
|
11
|
+
changedFields: readonly string[];
|
|
12
|
+
unmodeledFieldCount: number;
|
|
13
|
+
/** Private proposed data, not an accepted plan. The observed revision can already be stale. */
|
|
14
|
+
candidate: {
|
|
15
|
+
expectedRevision: number;
|
|
16
|
+
value: RegistryValue;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
/** Compare native seed-before-profile semantics against observed existing rows. Only present,
|
|
20
|
+
* closed, valid values receive a private candidate pinned to their observed revision. Missing,
|
|
21
|
+
* deleted, unreadable or unvisited rows never become empty values or create requests. This
|
|
22
|
+
* pure result carries no connection, current authority, accepted write plan or apply port. */
|
|
23
|
+
export declare function diffSpaceInitialization(space: string, deliveryClass: DeliveryClass, observed: {
|
|
24
|
+
space: string;
|
|
25
|
+
registry: {
|
|
26
|
+
bucket: string;
|
|
27
|
+
stream: string;
|
|
28
|
+
};
|
|
29
|
+
observations: readonly SpaceInitializationObservation[];
|
|
30
|
+
}, seed?: ChannelRegistryFile): {
|
|
31
|
+
kind: "space-initialization-diff";
|
|
32
|
+
space: string;
|
|
33
|
+
registry: {
|
|
34
|
+
bucket: string;
|
|
35
|
+
stream: string;
|
|
36
|
+
};
|
|
37
|
+
rows: SpaceInitializationDiffRow[];
|
|
38
|
+
scope: "existing-row native seed/profile comparison; private revision candidates are not accepted write plans";
|
|
39
|
+
registryFieldPolicy: "closed-native-fields";
|
|
40
|
+
revisionGuardsApplied: false;
|
|
41
|
+
connectionAuthorityChecked: false;
|
|
42
|
+
absenceAuthoritativelyProven: false;
|
|
43
|
+
applyPermitted: false;
|
|
44
|
+
writes: readonly [];
|
|
45
|
+
};
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=space-initialization-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-initialization-diff.d.ts","sourceRoot":"","sources":["../src/space-initialization-diff.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqC,KAAK,8BAA8B,EAAE,MAAM,uCAAuC,CAAC;AAC/H,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhF,KAAK,aAAa,GAAG,aAAa,GAAG,eAAe,CAAC;AAErD,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,8BAA8B,CAAC,OAAO,CAAC,GAAG,cAAc,GAAG,uBAAuB,GAAG,uBAAuB,GAAG,eAAe,CAAC;IACtI,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,gBAAgB,GAAG,iBAAiB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;IAC/F,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+FAA+F;IAC/F,SAAS,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI,CAAC;CACtE;AAiBD;;;+FAG+F;AAC/F,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,YAAY,EAAE,SAAS,8BAA8B,EAAE,CAAA;CAAE,EAClI,IAAI,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;EAwD3B"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { canonicalJson } from "./canonical.js";
|
|
2
|
+
import { validateChannelConfig, validateChannelDefaults } from "./channels.js";
|
|
3
|
+
import { CHANNEL_DEFAULTS_KEY } from "./subjects.js";
|
|
4
|
+
import { spaceInitializationDefinitions } from "./space-resource-definitions.js";
|
|
5
|
+
import { validateSpaceInitializationInputs } from "./space-initialization-observation.js";
|
|
6
|
+
const DEFAULT_FIELDS = { replay: true, replayWindow: true, deliveryClass: true };
|
|
7
|
+
const CHANNEL_FIELDS = { ...DEFAULT_FIELDS, description: true, instructions: true };
|
|
8
|
+
/** Reject unmodeled fields instead of dropping them from a proposed value. Native validators
|
|
9
|
+
* retain their existing compatibility behavior; this closed policy belongs to inspection. */
|
|
10
|
+
function registryValue(value, key) {
|
|
11
|
+
let unmodeledFieldCount = 0;
|
|
12
|
+
try {
|
|
13
|
+
const encoded = canonicalJson(value);
|
|
14
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || new TextEncoder().encode(encoded).byteLength > 65536)
|
|
15
|
+
return { valid: false, unmodeledFieldCount };
|
|
16
|
+
const fields = key === CHANNEL_DEFAULTS_KEY ? DEFAULT_FIELDS : CHANNEL_FIELDS;
|
|
17
|
+
unmodeledFieldCount = Object.keys(value).filter((field) => !Object.hasOwn(fields, field)).length;
|
|
18
|
+
if (unmodeledFieldCount)
|
|
19
|
+
return { valid: false, unmodeledFieldCount };
|
|
20
|
+
if (key === CHANNEL_DEFAULTS_KEY)
|
|
21
|
+
validateChannelDefaults(value);
|
|
22
|
+
else
|
|
23
|
+
validateChannelConfig(value);
|
|
24
|
+
return { valid: true, unmodeledFieldCount };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { valid: false, unmodeledFieldCount };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Compare native seed-before-profile semantics against observed existing rows. Only present,
|
|
31
|
+
* closed, valid values receive a private candidate pinned to their observed revision. Missing,
|
|
32
|
+
* deleted, unreadable or unvisited rows never become empty values or create requests. This
|
|
33
|
+
* pure result carries no connection, current authority, accepted write plan or apply port. */
|
|
34
|
+
export function diffSpaceInitialization(space, deliveryClass, observed, seed) {
|
|
35
|
+
validateSpaceInitializationInputs(deliveryClass, seed);
|
|
36
|
+
const definitions = spaceInitializationDefinitions(space, deliveryClass, seed);
|
|
37
|
+
if (observed.space !== space || observed.registry.bucket !== definitions.registry.bucket || observed.registry.stream !== definitions.registry.stream)
|
|
38
|
+
throw new Error("initialization comparison requires observations of the selected registry; no different target was adopted");
|
|
39
|
+
const patches = new Map(definitions.seed.entries.map((entry) => [entry.key, entry.patch]));
|
|
40
|
+
const keys = [...new Set([...patches.keys(), definitions.profileDefault.key])];
|
|
41
|
+
const requested = new Set(keys), observations = new Map();
|
|
42
|
+
for (const row of observed.observations) {
|
|
43
|
+
if (!requested.has(row.key) || observations.has(row.key) || row.operation !== "kv-get")
|
|
44
|
+
throw new Error("initialization comparison requires unique native observations of the requested keys; no foreign or duplicate row was adopted");
|
|
45
|
+
observations.set(row.key, row);
|
|
46
|
+
}
|
|
47
|
+
const rows = keys.map((key) => {
|
|
48
|
+
const observation = observations.get(key), patch = patches.get(key);
|
|
49
|
+
const row = {
|
|
50
|
+
key, state: observation?.state ?? "not-observed", observedRevision: null,
|
|
51
|
+
seedRequested: patches.has(key), profileDefault: key === CHANNEL_DEFAULTS_KEY ? "not-established" : "not-applicable",
|
|
52
|
+
changedFields: [], unmodeledFieldCount: 0, candidate: null,
|
|
53
|
+
};
|
|
54
|
+
if (!observation)
|
|
55
|
+
return row;
|
|
56
|
+
if ("revision" in observation) {
|
|
57
|
+
if (!Number.isSafeInteger(observation.revision) || observation.revision <= 0)
|
|
58
|
+
return { ...row, state: "observation-failed" };
|
|
59
|
+
row.observedRevision = observation.revision;
|
|
60
|
+
}
|
|
61
|
+
if (observation.state !== "present")
|
|
62
|
+
return row;
|
|
63
|
+
const currentPolicy = registryValue(observation.value, key), patchPolicy = registryValue(patch ?? {}, key);
|
|
64
|
+
row.unmodeledFieldCount = currentPolicy.unmodeledFieldCount + patchPolicy.unmodeledFieldCount;
|
|
65
|
+
if (!currentPolicy.valid || !patchPolicy.valid)
|
|
66
|
+
return { ...row, state: "field-policy-required" };
|
|
67
|
+
const current = observation.value;
|
|
68
|
+
const value = { ...observation.value, ...patch };
|
|
69
|
+
if (key === CHANNEL_DEFAULTS_KEY) {
|
|
70
|
+
if (value.deliveryClass === undefined) {
|
|
71
|
+
Object.assign(value, definitions.profileDefault.patch);
|
|
72
|
+
row.profileDefault = "propose-profile";
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
row.profileDefault = "preserve-explicit";
|
|
76
|
+
}
|
|
77
|
+
if (!registryValue(value, key).valid)
|
|
78
|
+
return { ...row, state: "field-policy-required" };
|
|
79
|
+
const next = value;
|
|
80
|
+
row.changedFields = Object.keys(key === CHANNEL_DEFAULTS_KEY ? DEFAULT_FIELDS : CHANNEL_FIELDS).filter((field) => {
|
|
81
|
+
const before = Object.hasOwn(current, field), after = Object.hasOwn(next, field);
|
|
82
|
+
return before !== after || (before && after && canonicalJson(current[field]) !== canonicalJson(next[field]));
|
|
83
|
+
});
|
|
84
|
+
if (row.changedFields.length === 0)
|
|
85
|
+
return { ...row, state: "observed-fields-match" };
|
|
86
|
+
return { ...row, state: "patch-differs", candidate: { expectedRevision: observation.revision, value } };
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
kind: "space-initialization-diff",
|
|
90
|
+
space, registry: definitions.registry, rows,
|
|
91
|
+
scope: "existing-row native seed/profile comparison; private revision candidates are not accepted write plans",
|
|
92
|
+
registryFieldPolicy: "closed-native-fields",
|
|
93
|
+
revisionGuardsApplied: false,
|
|
94
|
+
connectionAuthorityChecked: false,
|
|
95
|
+
absenceAuthoritativelyProven: false,
|
|
96
|
+
applyPermitted: false,
|
|
97
|
+
writes: [],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=space-initialization-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-initialization-diff.js","sourceRoot":"","sources":["../src/space-initialization-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type KvEntry } from "@nats-io/kv";
|
|
2
|
+
import type { NatsConnection } from "@nats-io/transport-node";
|
|
3
|
+
import { type ChannelRegistryFile } from "./channels.js";
|
|
4
|
+
import type { ChannelConfig, ChannelDefaults, DeliveryClass } from "./types.js";
|
|
5
|
+
export interface SpaceInitializationReadPort {
|
|
6
|
+
readonly bucket: string;
|
|
7
|
+
readonly stream: string;
|
|
8
|
+
readonly get: (key: string) => Promise<KvEntry | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Bind the selected existing registry on this connection through the public native API.
|
|
11
|
+
* No bucket creation or stream override. Names are not server/account or write authority. */
|
|
12
|
+
export declare function spaceInitializationReadPort(nc: NatsConnection, space: string): Promise<SpaceInitializationReadPort>;
|
|
13
|
+
/** Values are private planning data and can contain model-facing text. Never serialize these
|
|
14
|
+
* rows wholesale into public diagnostics, operation receipts or an authority artifact. */
|
|
15
|
+
export type SpaceInitializationObservation = {
|
|
16
|
+
key: string;
|
|
17
|
+
operation: "kv-get";
|
|
18
|
+
} & ({
|
|
19
|
+
state: "present";
|
|
20
|
+
revision: number;
|
|
21
|
+
value: ChannelConfig | ChannelDefaults;
|
|
22
|
+
} | {
|
|
23
|
+
state: "observed-tombstone";
|
|
24
|
+
revision: number;
|
|
25
|
+
marker: "DEL" | "PURGE";
|
|
26
|
+
} | {
|
|
27
|
+
state: "reported-not-found" | "registry-unavailable" | "permission-denied" | "target-mismatch" | "observation-failed" | "not-observed-budget";
|
|
28
|
+
});
|
|
29
|
+
export declare function validateSpaceInitializationInputs(deliveryClass: DeliveryClass, seed?: ChannelRegistryFile): void;
|
|
30
|
+
/** Observe only the requested seed keys and defaults. Native validation precedes reads. The
|
|
31
|
+
* five-second/8 MiB budget leaves unvisited rows explicit; expiry does not cancel an in-flight
|
|
32
|
+
* request. The caller retains connection custody. No row absence authorizes creation. */
|
|
33
|
+
export declare function observeSpaceInitialization(reads: SpaceInitializationReadPort, space: string, deliveryClass: DeliveryClass, seed?: ChannelRegistryFile): Promise<{
|
|
34
|
+
kind: "space-initialization-observations";
|
|
35
|
+
space: string;
|
|
36
|
+
registry: {
|
|
37
|
+
bucket: string;
|
|
38
|
+
stream: string;
|
|
39
|
+
};
|
|
40
|
+
observations: SpaceInitializationObservation[];
|
|
41
|
+
scope: "provided registry handle and requested keys; observations can be stale and are not a revision-guarded write plan";
|
|
42
|
+
byteBudget: number;
|
|
43
|
+
deadlineMs: number;
|
|
44
|
+
deadlineReached: boolean;
|
|
45
|
+
fullFieldPolicyApplied: false;
|
|
46
|
+
readerRequestCancellationEstablished: false;
|
|
47
|
+
connectionAuthorityChecked: false;
|
|
48
|
+
absenceAuthoritativelyProven: false;
|
|
49
|
+
applyPermitted: false;
|
|
50
|
+
writes: readonly [];
|
|
51
|
+
}>;
|
|
52
|
+
//# sourceMappingURL=space-initialization-observation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-initialization-observation.d.ts","sourceRoot":"","sources":["../src/space-initialization-observation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,EAAkD,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACxD;AAED;8FAC8F;AAC9F,wBAAsB,2BAA2B,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAIzH;AAED;2FAC2F;AAC3F,MAAM,MAAM,8BAA8B,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,QAAQ,CAAA;CAAE,GAAG,CAChF;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,GAAG,eAAe,CAAA;CAAE,GAC9E;IAAE,KAAK,EAAE,oBAAoB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,KAAK,GAAG,OAAO,CAAA;CAAE,GAC1E;IAAE,KAAK,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,qBAAqB,CAAA;CAAE,CACpJ,CAAC;AAMF,wBAAgB,iCAAiC,CAAC,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAkBhH;AAED;;0FAE0F;AAC1F,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,2BAA2B,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,mBAAmB;;;;;;;;;;;;;;;;;;GAgD3J"}
|