@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.
Files changed (169) hide show
  1. package/dist/agent-file.d.ts +2 -1
  2. package/dist/agent-file.d.ts.map +1 -1
  3. package/dist/agent-file.js +5 -3
  4. package/dist/agent-file.js.map +1 -1
  5. package/dist/auth-provider.d.ts +155 -1
  6. package/dist/auth-provider.d.ts.map +1 -1
  7. package/dist/auth-provider.js +24 -0
  8. package/dist/auth-provider.js.map +1 -1
  9. package/dist/authority-store-config.d.ts +31 -0
  10. package/dist/authority-store-config.d.ts.map +1 -0
  11. package/dist/authority-store-config.js +45 -0
  12. package/dist/authority-store-config.js.map +1 -0
  13. package/dist/backup-config.d.ts +32 -1
  14. package/dist/backup-config.d.ts.map +1 -1
  15. package/dist/backup-config.js +11 -0
  16. package/dist/backup-config.js.map +1 -1
  17. package/dist/backup.d.ts +4 -0
  18. package/dist/backup.d.ts.map +1 -1
  19. package/dist/backup.js +14 -0
  20. package/dist/backup.js.map +1 -1
  21. package/dist/broker-identity.d.ts +24 -0
  22. package/dist/broker-identity.d.ts.map +1 -0
  23. package/dist/broker-identity.js +36 -0
  24. package/dist/broker-identity.js.map +1 -0
  25. package/dist/browser-control.d.ts +1 -0
  26. package/dist/browser-control.d.ts.map +1 -1
  27. package/dist/browser-control.js +1 -0
  28. package/dist/browser-control.js.map +1 -1
  29. package/dist/browser-validators-build.d.ts +1 -0
  30. package/dist/browser-validators-build.d.ts.map +1 -1
  31. package/dist/channels.d.ts +14 -0
  32. package/dist/channels.d.ts.map +1 -1
  33. package/dist/channels.js +19 -5
  34. package/dist/channels.js.map +1 -1
  35. package/dist/command.d.ts +57 -0
  36. package/dist/command.d.ts.map +1 -1
  37. package/dist/command.js +49 -2
  38. package/dist/command.js.map +1 -1
  39. package/dist/connector-setup.d.ts +6 -0
  40. package/dist/connector-setup.d.ts.map +1 -1
  41. package/dist/connector.d.ts +3 -0
  42. package/dist/connector.d.ts.map +1 -1
  43. package/dist/connector.js.map +1 -1
  44. package/dist/endpoint-binding.d.ts +66 -1
  45. package/dist/endpoint-binding.d.ts.map +1 -1
  46. package/dist/endpoint-binding.js +167 -118
  47. package/dist/endpoint-binding.js.map +1 -1
  48. package/dist/endpoint-call.js +1 -1
  49. package/dist/endpoint-call.js.map +1 -1
  50. package/dist/endpoint-caller.d.ts +2 -0
  51. package/dist/endpoint-caller.d.ts.map +1 -1
  52. package/dist/endpoint-caller.js.map +1 -1
  53. package/dist/endpoint-grants.d.ts +15 -5
  54. package/dist/endpoint-grants.d.ts.map +1 -1
  55. package/dist/endpoint-grants.js +17 -5
  56. package/dist/endpoint-grants.js.map +1 -1
  57. package/dist/endpoint-invoke.d.ts +9 -0
  58. package/dist/endpoint-invoke.d.ts.map +1 -1
  59. package/dist/endpoint-invoke.js +7 -0
  60. package/dist/endpoint-invoke.js.map +1 -1
  61. package/dist/endpoint-schema-diff.d.ts +20 -0
  62. package/dist/endpoint-schema-diff.d.ts.map +1 -0
  63. package/dist/endpoint-schema-diff.js +15 -0
  64. package/dist/endpoint-schema-diff.js.map +1 -0
  65. package/dist/endpoint-serve.d.ts +4 -0
  66. package/dist/endpoint-serve.d.ts.map +1 -1
  67. package/dist/endpoint-serve.js +1 -1
  68. package/dist/endpoint-serve.js.map +1 -1
  69. package/dist/endpoint.d.ts +98 -5
  70. package/dist/endpoint.d.ts.map +1 -1
  71. package/dist/endpoint.js +350 -36
  72. package/dist/endpoint.js.map +1 -1
  73. package/dist/identity.d.ts +18 -0
  74. package/dist/identity.d.ts.map +1 -1
  75. package/dist/identity.js +28 -1
  76. package/dist/identity.js.map +1 -1
  77. package/dist/index.d.ts +15 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +15 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/issued-authority.d.ts +25 -1
  82. package/dist/issued-authority.d.ts.map +1 -1
  83. package/dist/issued-authority.js +24 -7
  84. package/dist/issued-authority.js.map +1 -1
  85. package/dist/membership-feed.d.ts +16 -12
  86. package/dist/membership-feed.d.ts.map +1 -1
  87. package/dist/membership-feed.js +143 -12
  88. package/dist/membership-feed.js.map +1 -1
  89. package/dist/one-line.d.ts +5 -4
  90. package/dist/one-line.d.ts.map +1 -1
  91. package/dist/one-line.js +6 -5
  92. package/dist/one-line.js.map +1 -1
  93. package/dist/provision.d.ts +8 -1
  94. package/dist/provision.d.ts.map +1 -1
  95. package/dist/provision.js +39 -17
  96. package/dist/provision.js.map +1 -1
  97. package/dist/registry.d.ts +8 -4
  98. package/dist/registry.d.ts.map +1 -1
  99. package/dist/registry.js +58 -20
  100. package/dist/registry.js.map +1 -1
  101. package/dist/remote-manager-authority.d.ts +34 -2
  102. package/dist/remote-manager-authority.d.ts.map +1 -1
  103. package/dist/remote-manager-authority.js.map +1 -1
  104. package/dist/remote-service-authority.d.ts +10 -6
  105. package/dist/remote-service-authority.d.ts.map +1 -1
  106. package/dist/remote-service-authority.js +4 -1
  107. package/dist/remote-service-authority.js.map +1 -1
  108. package/dist/run-admission.d.ts +24 -1
  109. package/dist/run-admission.d.ts.map +1 -1
  110. package/dist/run-admission.js +60 -3
  111. package/dist/run-admission.js.map +1 -1
  112. package/dist/run-cancel.d.ts +43 -0
  113. package/dist/run-cancel.d.ts.map +1 -0
  114. package/dist/run-cancel.js +72 -0
  115. package/dist/run-cancel.js.map +1 -0
  116. package/dist/run-host.d.ts +12 -0
  117. package/dist/run-host.d.ts.map +1 -1
  118. package/dist/run-submission-client.d.ts +35 -0
  119. package/dist/run-submission-client.d.ts.map +1 -0
  120. package/dist/run-submission-client.js +61 -0
  121. package/dist/run-submission-client.js.map +1 -0
  122. package/dist/run-submission.d.ts +78 -0
  123. package/dist/run-submission.d.ts.map +1 -0
  124. package/dist/run-submission.js +158 -0
  125. package/dist/run-submission.js.map +1 -0
  126. package/dist/sealed-reply.d.ts +21 -0
  127. package/dist/sealed-reply.d.ts.map +1 -0
  128. package/dist/sealed-reply.js +75 -0
  129. package/dist/sealed-reply.js.map +1 -0
  130. package/dist/secret-fs.d.ts +29 -6
  131. package/dist/secret-fs.d.ts.map +1 -1
  132. package/dist/secret-fs.js +107 -18
  133. package/dist/secret-fs.js.map +1 -1
  134. package/dist/space-deletion-targets.d.ts +4 -0
  135. package/dist/space-deletion-targets.d.ts.map +1 -0
  136. package/dist/space-deletion-targets.js +15 -0
  137. package/dist/space-deletion-targets.js.map +1 -0
  138. package/dist/space-initialization-diff.d.ts +47 -0
  139. package/dist/space-initialization-diff.d.ts.map +1 -0
  140. package/dist/space-initialization-diff.js +100 -0
  141. package/dist/space-initialization-diff.js.map +1 -0
  142. package/dist/space-initialization-observation.d.ts +52 -0
  143. package/dist/space-initialization-observation.d.ts.map +1 -0
  144. package/dist/space-initialization-observation.js +133 -0
  145. package/dist/space-initialization-observation.js.map +1 -0
  146. package/dist/space-resource-definitions.d.ts +86 -0
  147. package/dist/space-resource-definitions.d.ts.map +1 -0
  148. package/dist/space-resource-definitions.js +133 -0
  149. package/dist/space-resource-definitions.js.map +1 -0
  150. package/dist/space-schema-diff.d.ts +45 -0
  151. package/dist/space-schema-diff.d.ts.map +1 -0
  152. package/dist/space-schema-diff.js +204 -0
  153. package/dist/space-schema-diff.js.map +1 -0
  154. package/dist/space-schema-observation.d.ts +45 -0
  155. package/dist/space-schema-observation.d.ts.map +1 -0
  156. package/dist/space-schema-observation.js +78 -0
  157. package/dist/space-schema-observation.js.map +1 -0
  158. package/dist/spaces.d.ts.map +1 -1
  159. package/dist/spaces.js +5 -25
  160. package/dist/spaces.js.map +1 -1
  161. package/dist/stream-schema-diff.d.ts +31 -0
  162. package/dist/stream-schema-diff.d.ts.map +1 -0
  163. package/dist/stream-schema-diff.js +45 -0
  164. package/dist/stream-schema-diff.js.map +1 -0
  165. package/dist/streams.d.ts +30 -1
  166. package/dist/streams.d.ts.map +1 -1
  167. package/dist/streams.js +41 -18
  168. package/dist/streams.js.map +1 -1
  169. 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"}
@@ -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: `chmod` (0o600 file / 0o700 dir), which also tightens a path that already existed.
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
- * Write a private secret file: mode 0o600 on the descriptor before the bytes, whether or not the
17
- * name already existed, then {@link hardenPrivate} for the win32 ACL. FAIL-CLOSED — if hardening
18
- * throws, the hardening error propagates (the caller never proceeds as if the secret were safe) and
19
- * the just-written file is best-effort deleted so it isn't left readable.
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. `wx` on the temp makes a collision fail loudly instead of silently swapping bytes.
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
@@ -1 +1 @@
1
- {"version":3,"file":"secret-fs.d.ts","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CA2BtE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEzE;AA0BD;;;;;;;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,CAgCnF;AAED;;2EAE2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAG9C"}
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: `chmod` (0o600 file / 0o700 dir), which also tightens a path that already existed.
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
- chmodSync(path, kind === "dir" ? 0o700 : 0o600);
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 bytes, whether or not the
74
- * name already existed, then {@link hardenPrivate} for the win32 ACL. FAIL-CLOSED — if hardening
75
- * throws, the hardening error propagates (the caller never proceeds as if the secret were safe) and
76
- * the just-written file is best-effort deleted so it isn't left readable.
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, "w");
146
+ writePrivate(path, data, false);
80
147
  }
81
- /** The fail-closed write {@link writeSecretFile} documents, with the open flag as a parameter so
82
- * {@link writeSecretFileCreateOnlyRaw} can pass `wx`. */
83
- function writePrivate(path, data, flag) {
84
- const fd = openSync(path, flag, 0o600);
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
- // The open mode applies only when the open creates the file; an existing one keeps its own.
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, "wx");
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. `wx` on the temp makes a collision fail loudly instead of silently swapping bytes.
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
- const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
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);
@@ -1 +1 @@
1
- {"version":3,"file":"secret-fs.js","sourceRoot":"","sources":["../src/secret-fs.ts"],"names":[],"mappings":"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"}
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"}