@jini-ai/platform 0.3.0 → 0.5.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 (127) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +121 -2
  3. package/dist/asset-cache.d.ts +0 -22
  4. package/dist/asset-cache.js +3 -138
  5. package/dist/download.js +2 -2
  6. package/dist/fs/atomic-write.d.ts +103 -0
  7. package/dist/fs/atomic-write.js +201 -0
  8. package/dist/fs/durable-json.d.ts +89 -0
  9. package/dist/fs/durable-json.js +166 -0
  10. package/dist/fs/env-text.d.ts +11 -0
  11. package/dist/fs/env-text.js +22 -0
  12. package/dist/fs/file-lock.d.ts +126 -0
  13. package/dist/fs/file-lock.js +306 -0
  14. package/dist/fs/guarded-reader.d.ts +60 -0
  15. package/dist/fs/guarded-reader.js +310 -0
  16. package/dist/fs/index.d.ts +8 -0
  17. package/dist/fs/index.js +9 -0
  18. package/dist/fs/strict-containment.d.ts +5 -0
  19. package/dist/fs/strict-containment.js +13 -0
  20. package/dist/fs.js +2 -3
  21. package/dist/http/guarded/client.d.ts +13 -0
  22. package/dist/http/guarded/client.js +240 -0
  23. package/dist/http/guarded/errors.d.ts +21 -0
  24. package/dist/http/guarded/errors.js +20 -0
  25. package/dist/http/guarded/fetch-adapter.d.ts +23 -0
  26. package/dist/http/guarded/fetch-adapter.js +30 -0
  27. package/dist/http/guarded/index.d.ts +12 -0
  28. package/dist/http/guarded/index.js +7 -0
  29. package/dist/http/guarded/node-ports.d.ts +8 -0
  30. package/dist/http/guarded/node-ports.js +11 -0
  31. package/dist/http/guarded/ports.d.ts +51 -0
  32. package/dist/http/guarded/ports.js +2 -0
  33. package/dist/http/guarded/transport.fetch.d.ts +11 -0
  34. package/dist/http/guarded/transport.fetch.js +109 -0
  35. package/dist/http/guarded/types.d.ts +8 -0
  36. package/dist/http/guarded/types.js +2 -0
  37. package/dist/index.d.ts +61 -1
  38. package/dist/index.js +61 -1
  39. package/dist/mail/adapter-module.d.ts +20 -0
  40. package/dist/mail/adapter-module.js +2 -0
  41. package/dist/mail/delivery-availability.d.ts +7 -0
  42. package/dist/mail/delivery-availability.js +6 -0
  43. package/dist/mail/index.d.ts +7 -0
  44. package/dist/mail/index.js +3 -0
  45. package/dist/mail/ports.d.ts +43 -0
  46. package/dist/mail/ports.js +2 -0
  47. package/dist/mail/purpose-scoped-mailer.d.ts +18 -0
  48. package/dist/mail/purpose-scoped-mailer.js +50 -0
  49. package/dist/mail/smtp.d.ts +79 -0
  50. package/dist/mail/smtp.js +102 -0
  51. package/dist/mail/types.d.ts +83 -0
  52. package/dist/mail/types.js +2 -0
  53. package/dist/messages.d.ts +22 -0
  54. package/dist/messages.js +15 -0
  55. package/dist/net/address.d.ts +38 -0
  56. package/dist/net/address.js +241 -0
  57. package/dist/net/index.d.ts +2 -0
  58. package/dist/net/index.js +2 -0
  59. package/dist/process.d.ts +2 -1
  60. package/dist/process.js +2 -1
  61. package/dist/proxy-env.js +1 -1
  62. package/dist/resource-paths.js +1 -1
  63. package/dist/sandbox-env.d.ts +1 -1
  64. package/dist/sandbox-env.js +1 -1
  65. package/dist/secrets/aad.d.ts +7 -0
  66. package/dist/secrets/aad.js +5 -0
  67. package/dist/secrets/credential-sets/aad.d.ts +7 -0
  68. package/dist/secrets/credential-sets/aad.js +7 -0
  69. package/dist/secrets/credential-sets/index.d.ts +4 -0
  70. package/dist/secrets/credential-sets/index.js +3 -0
  71. package/dist/secrets/credential-sets/repo.memory.d.ts +36 -0
  72. package/dist/secrets/credential-sets/repo.memory.js +72 -0
  73. package/dist/secrets/credential-sets/types.d.ts +67 -0
  74. package/dist/secrets/credential-sets/types.js +6 -0
  75. package/dist/secrets/index.d.ts +9 -0
  76. package/dist/secrets/index.js +5 -0
  77. package/dist/secrets/keyring.env.d.ts +176 -0
  78. package/dist/secrets/keyring.env.js +271 -0
  79. package/dist/secrets/keyring.memory.d.ts +10 -0
  80. package/dist/secrets/keyring.memory.js +9 -0
  81. package/dist/secrets/ports.d.ts +33 -0
  82. package/dist/secrets/ports.js +2 -0
  83. package/dist/secrets/secret-sealer.aesgcm.d.ts +22 -0
  84. package/dist/secrets/secret-sealer.aesgcm.js +64 -0
  85. package/dist/secrets/testing.d.ts +4 -0
  86. package/dist/secrets/testing.js +4 -0
  87. package/dist/secrets/types.d.ts +10 -0
  88. package/dist/secrets/types.js +2 -0
  89. package/dist/shell.d.ts +10 -6
  90. package/dist/shell.js +10 -6
  91. package/dist/terminal.d.ts +2 -2
  92. package/dist/terminal.js +9 -5
  93. package/package.json +79 -4
  94. package/dist/asset-cache.d.ts.map +0 -1
  95. package/dist/asset-cache.js.map +0 -1
  96. package/dist/aws-sigv4.d.ts.map +0 -1
  97. package/dist/aws-sigv4.js.map +0 -1
  98. package/dist/blob-storage.d.ts.map +0 -1
  99. package/dist/blob-storage.js.map +0 -1
  100. package/dist/command.d.ts.map +0 -1
  101. package/dist/command.js.map +0 -1
  102. package/dist/download.d.ts.map +0 -1
  103. package/dist/download.js.map +0 -1
  104. package/dist/fetch-with-timeout.d.ts.map +0 -1
  105. package/dist/fetch-with-timeout.js.map +0 -1
  106. package/dist/fs.d.ts.map +0 -1
  107. package/dist/fs.js.map +0 -1
  108. package/dist/home-expansion.d.ts.map +0 -1
  109. package/dist/home-expansion.js.map +0 -1
  110. package/dist/http.d.ts.map +0 -1
  111. package/dist/http.js.map +0 -1
  112. package/dist/index.d.ts.map +0 -1
  113. package/dist/index.js.map +0 -1
  114. package/dist/process.d.ts.map +0 -1
  115. package/dist/process.js.map +0 -1
  116. package/dist/proxy-env.d.ts.map +0 -1
  117. package/dist/proxy-env.js.map +0 -1
  118. package/dist/resource-paths.d.ts.map +0 -1
  119. package/dist/resource-paths.js.map +0 -1
  120. package/dist/sandbox-env.d.ts.map +0 -1
  121. package/dist/sandbox-env.js.map +0 -1
  122. package/dist/shell.d.ts.map +0 -1
  123. package/dist/shell.js.map +0 -1
  124. package/dist/terminal.d.ts.map +0 -1
  125. package/dist/terminal.js.map +0 -1
  126. package/dist/toolchain.d.ts.map +0 -1
  127. package/dist/toolchain.js.map +0 -1
@@ -0,0 +1,38 @@
1
+ /** Expand an IPv6 literal (with `::` compression and/or an embedded dotted
2
+ * IPv4 tail) into its eight 16-bit groups, or null if it does not parse.
3
+ * Node's URL parser normalizes mapped literals to hex (`::ffff:127.0.0.1` →
4
+ * `::ffff:7f00:1`), so a string regex can't reliably spot the mapped range —
5
+ * we have to canonicalize.
6
+ *
7
+ * Exported (previously module-private) so its parse-failure guards are
8
+ * directly unit-testable: the IP classifier and guarded client gate
9
+ * calls behind `net.isIP(addr) === 6`, and Node's `isIP` already fully
10
+ * validates IPv6 syntax (including embedded-IPv4-tail octet ranges) before
11
+ * returning 6 — empirically re-verified this session by fuzzing ~10k
12
+ * `isIP`-accepted v6 literals through this function's guards with zero
13
+ * hits — so none of these guards are reachable through that path. They stay
14
+ * as real defense-in-depth (fail closed with `null` rather than trust a
15
+ * platform primitive never changing behavior), matching the asset cache's
16
+ * exported-for-direct-testing precedent (`readBodyCapped`'s abort parameter). */
17
+ export declare function expandIpv6({ address: addr }: {
18
+ address: string;
19
+ }): number[] | null;
20
+ /** True for any address an external CDN must never resolve to: loopback,
21
+ * link-local, private, CGNAT, ULA, multicast, or unspecified. An unparseable
22
+ * input is treated as unsafe. */
23
+ export declare function isPrivateAddress({ address: addr }: {
24
+ address: string;
25
+ }): boolean;
26
+ /** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
27
+ export declare function isLoopbackApiHost({ hostname }: {
28
+ hostname: string;
29
+ }): boolean;
30
+ /** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
31
+ export declare function isBlockedExternalApiHostname({ hostname }: {
32
+ hostname: string;
33
+ }): boolean;
34
+ /** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
35
+ export declare function isLoopbackHostname({ hostname }: {
36
+ readonly hostname: unknown;
37
+ }, _optional?: Record<string, never>): boolean;
38
+ //# sourceMappingURL=address.d.ts.map
@@ -0,0 +1,241 @@
1
+ import { isIP } from 'node:net';
2
+ // SSRF peer classification is distinct from core's browser-development LAN origin allow-list.
3
+ /** Expand an IPv6 literal (with `::` compression and/or an embedded dotted
4
+ * IPv4 tail) into its eight 16-bit groups, or null if it does not parse.
5
+ * Node's URL parser normalizes mapped literals to hex (`::ffff:127.0.0.1` →
6
+ * `::ffff:7f00:1`), so a string regex can't reliably spot the mapped range —
7
+ * we have to canonicalize.
8
+ *
9
+ * Exported (previously module-private) so its parse-failure guards are
10
+ * directly unit-testable: the IP classifier and guarded client gate
11
+ * calls behind `net.isIP(addr) === 6`, and Node's `isIP` already fully
12
+ * validates IPv6 syntax (including embedded-IPv4-tail octet ranges) before
13
+ * returning 6 — empirically re-verified this session by fuzzing ~10k
14
+ * `isIP`-accepted v6 literals through this function's guards with zero
15
+ * hits — so none of these guards are reachable through that path. They stay
16
+ * as real defense-in-depth (fail closed with `null` rather than trust a
17
+ * platform primitive never changing behavior), matching the asset cache's
18
+ * exported-for-direct-testing precedent (`readBodyCapped`'s abort parameter). */
19
+ export function expandIpv6({ address: addr }) {
20
+ // `String#split` always returns a non-empty array, so index 0 is never
21
+ // `undefined` — the `!` narrows a `noUncheckedIndexedAccess` false
22
+ // positive, not a real fallback path.
23
+ let s = addr.toLowerCase().split('%')[0];
24
+ if (!s.includes(':'))
25
+ return null;
26
+ // Fold a trailing dotted IPv4 (`…:1.2.3.4`) into two hex groups.
27
+ const v4 = /^(.*:)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(s);
28
+ if (v4) {
29
+ // `v4[2]` is the regex's second (mandatory, non-optional) capture group
30
+ // of a successful match — always defined.
31
+ const o = v4[2].split('.').map((n) => Number(n));
32
+ if (o.length !== 4 || o.some((n) => !Number.isInteger(n) || n < 0 || n > 255))
33
+ return null;
34
+ // `o` is now a validated 4-element array — indices 0-3 always defined.
35
+ const hi = (((o[0] << 8) | o[1])).toString(16);
36
+ const lo = (((o[2] << 8) | o[3])).toString(16);
37
+ s = `${v4[1]}${hi}:${lo}`;
38
+ }
39
+ const halves = s.split('::');
40
+ if (halves.length > 2)
41
+ return null;
42
+ const parse = (p) => p === '' ? [] : p.split(':').map((g) => (/^[0-9a-f]{1,4}$/.test(g) ? parseInt(g, 16) : Number.NaN));
43
+ // `halves` always has at least one element (split guarantee).
44
+ const left = parse(halves[0]);
45
+ let groups;
46
+ if (halves.length === 1) {
47
+ groups = left;
48
+ }
49
+ else {
50
+ // `halves.length === 2` here (the `> 2` case already returned above).
51
+ const right = parse(halves[1]);
52
+ const fill = 8 - left.length - right.length;
53
+ if (fill < 1)
54
+ return null;
55
+ groups = [...left, ...new Array(fill).fill(0), ...right];
56
+ }
57
+ if (groups.length !== 8 || groups.some((g) => Number.isNaN(g) || g < 0 || g > 0xffff))
58
+ return null;
59
+ return groups;
60
+ }
61
+ /** True for any address an external CDN must never resolve to: loopback,
62
+ * link-local, private, CGNAT, ULA, multicast, or unspecified. An unparseable
63
+ * input is treated as unsafe. */
64
+ export function isPrivateAddress({ address: addr }) {
65
+ const fam = isIP(addr);
66
+ if (fam === 4) {
67
+ // `net.isIP(addr) === 4` already guarantees `addr` is a syntactically
68
+ // valid dotted-quad with four 0-255 decimal octets (no leading-zero
69
+ // octal ambiguity, no out-of-range component) — empirically re-verified
70
+ // this session by fuzzing 500k+ candidate strings through `isIP` and
71
+ // this same re-derivation with zero divergences. `o` is therefore always
72
+ // a validated 4-element array.
73
+ const o = addr.split('.').map((n) => Number(n));
74
+ const a = o[0];
75
+ const b = o[1];
76
+ if (a === 0)
77
+ return true; // "this" network / unspecified
78
+ if (a === 10)
79
+ return true; // private
80
+ if (a === 127)
81
+ return true; // loopback
82
+ if (a === 169 && b === 254)
83
+ return true; // link-local
84
+ if (a === 172 && b >= 16 && b <= 31)
85
+ return true; // private
86
+ if (a === 192 && b === 168)
87
+ return true; // private
88
+ if (a === 100 && b >= 64 && b <= 127)
89
+ return true; // CGNAT
90
+ if (a >= 224)
91
+ return true; // multicast + reserved
92
+ return false;
93
+ }
94
+ if (fam === 6) {
95
+ const groups = expandIpv6({ address: addr });
96
+ // `net.isIP(addr) === 6` already guarantees `addr` is syntactically valid
97
+ // IPv6 (Node validates this before classifying the family), and
98
+ // `expandIpv6` implements the same canonicalization Node's own parser
99
+ // does, so it never returns `null` for an `isIP`-accepted literal —
100
+ // empirically re-verified this session by fuzzing ~10k `isIP`-accepted
101
+ // v6 literals through `expandIpv6` with zero `null` results (see
102
+ // `expandIpv6`'s doc comment and `archived provenance ledger`'s 2026-07-22 entry).
103
+ // Kept as a real fail-closed guard (defense-in-depth against a future
104
+ // change to either parser) rather than asserted away, so it stays
105
+ // provably-unreachable-but-intentional instead of a forced test.
106
+ if (!groups)
107
+ return true; // can't canonicalize a "valid" v6 → refuse
108
+ // IPv4-mapped (`::ffff:0:0/96`) and the deprecated IPv4-compatible
109
+ // (`::0:0/96`, excluding :: / ::1) carry an embedded IPv4 — classify it by
110
+ // its real v4 range so `::ffff:7f00:1` (== 127.0.0.1) is caught.
111
+ const topZero = groups.slice(0, 5).every((g) => g === 0);
112
+ if (topZero && (groups[5] === 0xffff || groups[5] === 0)) {
113
+ // `groups` is a validated 8-element array once non-null — indices 6-7
114
+ // always defined.
115
+ const a = groups[6] >> 8;
116
+ const b = groups[6] & 0xff;
117
+ const c = groups[7] >> 8;
118
+ const d = groups[7] & 0xff;
119
+ if (groups[5] === 0xffff || a !== 0 || b !== 0) {
120
+ return isPrivateAddress({ address: `${a}.${b}.${c}.${d}` });
121
+ }
122
+ }
123
+ if (groups.every((g) => g === 0))
124
+ return true; // :: (unspecified)
125
+ if (groups.slice(0, 7).every((g) => g === 0) && groups[7] === 1)
126
+ return true; // ::1 loopback
127
+ const first = groups[0];
128
+ if ((first & 0xffc0) === 0xfe80)
129
+ return true; // link-local fe80::/10 (fe80–febf)
130
+ if ((first & 0xfe00) === 0xfc00)
131
+ return true; // unique-local fc00::/7
132
+ if ((first & 0xff00) === 0xff00)
133
+ return true; // multicast ff00::/8
134
+ return false;
135
+ }
136
+ return true; // not a literal IP — caller must resolve before trusting it
137
+ }
138
+ function normalizeBracketedIpv6(hostname) {
139
+ const stripped = hostname.startsWith('[') && hostname.endsWith(']')
140
+ ? hostname.slice(1, -1)
141
+ : hostname;
142
+ // FQDN trailing-dot form (RFC 1034) resolves identically to the dotless
143
+ // form, so `localhost.` must normalize to `localhost` before the equality
144
+ // check below — and `0.0.0.0.`, `10.0.0.1.`, etc. must normalize before
145
+ // isBlockedIpv4 parses them. Strips one or more trailing dots.
146
+ return stripped.toLowerCase().replace(/\.+$/, '');
147
+ }
148
+ function parseIpv4(hostname) {
149
+ const parts = hostname.split('.');
150
+ if (parts.length !== 4)
151
+ return null;
152
+ const parsed = parts.map((part) => {
153
+ if (!/^\d{1,3}$/.test(part))
154
+ return null;
155
+ const value = Number(part);
156
+ return value >= 0 && value <= 255 ? value : null;
157
+ });
158
+ if (parsed.some((part) => part === null))
159
+ return null;
160
+ return [parsed[0], parsed[1], parsed[2], parsed[3]];
161
+ }
162
+ function isLoopbackIpv4(hostname) {
163
+ const parts = parseIpv4(hostname);
164
+ return Boolean(parts && parts[0] === 127);
165
+ }
166
+ function isBlockedIpv4(hostname) {
167
+ const parts = parseIpv4(hostname);
168
+ if (!parts)
169
+ return false;
170
+ const [a, b] = parts;
171
+ return (a === 0 ||
172
+ (a === 100 && b >= 64 && b <= 127) ||
173
+ (a === 169 && b === 254) ||
174
+ a === 10 ||
175
+ (a === 192 && b === 168) ||
176
+ (a === 172 && b >= 16 && b <= 31) ||
177
+ a >= 224);
178
+ }
179
+ function ipv4MappedToDotted(hostname) {
180
+ const host = normalizeBracketedIpv6(hostname);
181
+ const mapped = /^::ffff:(.+)$/i.exec(host)?.[1];
182
+ if (!mapped)
183
+ return null;
184
+ if (parseIpv4(mapped.toLowerCase()))
185
+ return mapped.toLowerCase();
186
+ const hexParts = mapped.split(':');
187
+ if (hexParts.length !== 2 ||
188
+ !hexParts.every((part) => /^[0-9a-f]{1,4}$/i.test(part))) {
189
+ return null;
190
+ }
191
+ // Non-null assertions, not a runtime guard: the length/regex checks above
192
+ // already guarantee exactly two non-empty hex segments here.
193
+ const hi = hexParts[0];
194
+ const lo = hexParts[1];
195
+ const value = (Number.parseInt(hi, 16) << 16) | Number.parseInt(lo, 16);
196
+ return [
197
+ (value >>> 24) & 255,
198
+ (value >>> 16) & 255,
199
+ (value >>> 8) & 255,
200
+ value & 255,
201
+ ].join('.');
202
+ }
203
+ /** True for `localhost`, `::1`, `127.0.0.0/8`, and their IPv4-mapped-IPv6 forms. */
204
+ export function isLoopbackApiHost({ hostname }) {
205
+ const host = normalizeBracketedIpv6(hostname);
206
+ if (host === 'localhost' || host === '::1')
207
+ return true;
208
+ if (isLoopbackIpv4(host))
209
+ return true;
210
+ const mapped = ipv4MappedToDotted(host);
211
+ return Boolean(mapped && isLoopbackIpv4(mapped));
212
+ }
213
+ /** True for RFC1918/link-local/CGNAT/multicast/unspecified/unique-local-IPv6 addresses — private network space a public caller should never be steered into. */
214
+ export function isBlockedExternalApiHostname({ hostname }) {
215
+ const host = normalizeBracketedIpv6(hostname);
216
+ if (host === '::')
217
+ return true;
218
+ if (isBlockedIpv4(host))
219
+ return true;
220
+ if (/^f[cd][0-9a-f]{2}:/i.test(host))
221
+ return true;
222
+ if (/^fe[89ab][0-9a-f]:/i.test(host))
223
+ return true;
224
+ const mapped = ipv4MappedToDotted(host);
225
+ return Boolean(mapped && isBlockedIpv4(mapped));
226
+ }
227
+ /** True when `hostname` is `localhost`, an IPv6 loopback literal, or a `127.0.0.0/8` IPv4 literal. */
228
+ export function isLoopbackHostname({ hostname }, _optional = {}) {
229
+ const normalized = String(hostname || '')
230
+ .toLowerCase()
231
+ .replace(/^\[|\]$/g, '')
232
+ .replace(/\.$/, '');
233
+ if (normalized === 'localhost')
234
+ return true;
235
+ if (normalized === '::1' || normalized === '0:0:0:0:0:0:0:1')
236
+ return true;
237
+ if (isIP(normalized) === 4)
238
+ return normalized === '127.0.0.1' || normalized.startsWith('127.');
239
+ return false;
240
+ }
241
+ //# sourceMappingURL=address.js.map
@@ -0,0 +1,2 @@
1
+ export { isPrivateAddress, expandIpv6, isLoopbackApiHost, isBlockedExternalApiHostname, isLoopbackHostname } from "./address.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { isPrivateAddress, expandIpv6, isLoopbackApiHost, isBlockedExternalApiHostname, isLoopbackHostname } from "./address.js";
2
+ //# sourceMappingURL=index.js.map
package/dist/process.d.ts CHANGED
@@ -141,7 +141,8 @@ export declare function waitForProcessExit(pid: number | null | undefined, timeo
141
141
  export declare function listProcessSnapshots(): Promise<ProcessSnapshot[]>;
142
142
  /**
143
143
  * Collect the transitive set of descendant PIDs (including the roots) from a
144
- * process snapshot list, returned sorted descending so children precede parents.
144
+ * process snapshot list, returned sorted by numeric PID descending for deterministic ordering.
145
+ * PID reuse and wraparound mean this does not guarantee children precede parents.
145
146
  *
146
147
  * @param processes - The full process snapshot list to walk.
147
148
  * @param rootPids - The root PIDs whose subtrees to collect (non-numbers ignored).
package/dist/process.js CHANGED
@@ -285,7 +285,8 @@ export async function listProcessSnapshots() {
285
285
  }
286
286
  /**
287
287
  * Collect the transitive set of descendant PIDs (including the roots) from a
288
- * process snapshot list, returned sorted descending so children precede parents.
288
+ * process snapshot list, returned sorted by numeric PID descending for deterministic ordering.
289
+ * PID reuse and wraparound mean this does not guarantee children precede parents.
289
290
  *
290
291
  * @param processes - The full process snapshot list to walk.
291
292
  * @param rootPids - The root PIDs whose subtrees to collect (non-numbers ignored).
package/dist/proxy-env.js CHANGED
@@ -365,7 +365,7 @@ export function resolveSystemProxyEnv(options = {}) {
365
365
  // `RegExp` construction from untrusted input. Empirically re-verified
366
366
  // this session: 200k fuzzed `stdout`/registry-value strings (control
367
367
  // characters, huge lengths, null bytes) through both parse functions,
368
- // zero throws — see source-map.md's 2026-07-22 entry.
368
+ // zero throws — see archived provenance ledger's 2026-07-22 entry.
369
369
  return {};
370
370
  }
371
371
  return {};
@@ -10,7 +10,7 @@
10
10
  * Generalized from an upstream flat daemon module: every hardcoded env-var
11
11
  * name, the host CLI's own package specifier, the default data-directory
12
12
  * name, and the Windows packaged-resources marker segment are now fields on
13
- * {@link ResourcePathsConfig} — see `source-map.md` for the exact mapping.
13
+ * {@link ResourcePathsConfig} — see `archived provenance ledger` for the exact mapping.
14
14
  */
15
15
  import fs from 'node:fs';
16
16
  import { createRequire } from 'node:module';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Names the environment variables and on-disk naming this port reads/writes.
3
3
  * A consumer supplies its own env-var names and config-directory name — see
4
- * `source-map.md` for the values a real adapter (e.g. the OD adapter) uses.
4
+ * `archived provenance ledger` for the values a real adapter (e.g. the OD adapter) uses.
5
5
  */
6
6
  export interface SandboxEnvConfig {
7
7
  /** Env var gating whether sandboxing is on (truthy/falsy string). */
@@ -10,7 +10,7 @@
10
10
  * Generalized from an upstream flat daemon module: the origin hardcoded its
11
11
  * host product's env-var names and a product-branded config sub-directory.
12
12
  * Every one of those is now a field on {@link SandboxEnvConfig} so a
13
- * consumer isn't branded — see `source-map.md` for the exact mapping.
13
+ * consumer isn't branded — see `archived provenance ledger` for the exact mapping.
14
14
  */
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
@@ -0,0 +1,7 @@
1
+ /** Formats an existing colon-separated AAD lineage without escaping or changing its bytes. */
2
+ export declare function formatAad({ kind, version, parts }: {
3
+ kind: string;
4
+ version: string;
5
+ parts: readonly string[];
6
+ }): string;
7
+ //# sourceMappingURL=aad.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** Formats an existing colon-separated AAD lineage without escaping or changing its bytes. */
2
+ export function formatAad({ kind, version, parts }) {
3
+ return [kind, version, ...parts].join(":");
4
+ }
5
+ //# sourceMappingURL=aad.js.map
@@ -0,0 +1,7 @@
1
+ /** Stable v1 AAD binding workspace, vendor, and credential identity. */
2
+ export declare function buildVendorCredentialAad(input: {
3
+ workspaceId: string;
4
+ vendorId: string;
5
+ id: string;
6
+ }): string;
7
+ //# sourceMappingURL=aad.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { formatAad } from "../aad.js";
2
+ const AAD_VERSION = "v1";
3
+ /** Stable v1 AAD binding workspace, vendor, and credential identity. */
4
+ export function buildVendorCredentialAad(input) {
5
+ return formatAad({ kind: "vendor-credential-set", version: AAD_VERSION, parts: [input.workspaceId, input.vendorId, input.id] });
6
+ }
7
+ //# sourceMappingURL=aad.js.map
@@ -0,0 +1,4 @@
1
+ export type { VendorCredentialSetRecord, VendorCredentialSetSummary, VendorCredentialSetRepoPort } from "./types.js";
2
+ export { buildVendorCredentialAad } from "./aad.js";
3
+ export { InMemoryVendorCredentialSetRepo } from "./repo.memory.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { buildVendorCredentialAad } from "./aad.js";
2
+ export { InMemoryVendorCredentialSetRepo } from "./repo.memory.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,36 @@
1
+ import type { VendorCredentialSetRecord, VendorCredentialSetRepoPort } from "./types.js";
2
+ /** In-memory repository preserving label uniqueness and group default promotion. */
3
+ export declare class InMemoryVendorCredentialSetRepo implements VendorCredentialSetRepoPort {
4
+ constructor(_required: Record<string, never>);
5
+ private readonly rows;
6
+ private static rowKey;
7
+ private assertLabelAvailable;
8
+ private clearOtherDefaults;
9
+ insert(record: VendorCredentialSetRecord): Promise<void>;
10
+ update(record: VendorCredentialSetRecord): Promise<void>;
11
+ findById(input: {
12
+ workspaceId: string;
13
+ id: string;
14
+ }): Promise<VendorCredentialSetRecord | null>;
15
+ findDefaultByVendor(input: {
16
+ workspaceId: string;
17
+ vendorId: string;
18
+ }): Promise<VendorCredentialSetRecord | null>;
19
+ listByVendor(input: {
20
+ workspaceId: string;
21
+ vendorId: string;
22
+ }): Promise<VendorCredentialSetRecord[]>;
23
+ listByWorkspace(input: {
24
+ workspaceId: string;
25
+ }): Promise<VendorCredentialSetRecord[]>;
26
+ delete(input: {
27
+ workspaceId: string;
28
+ id: string;
29
+ }): Promise<void>;
30
+ updateAccountLabel(input: {
31
+ workspaceId: string;
32
+ id: string;
33
+ accountLabel: string;
34
+ }): Promise<void>;
35
+ }
36
+ //# sourceMappingURL=repo.memory.d.ts.map
@@ -0,0 +1,72 @@
1
+ /** In-memory repository preserving label uniqueness and group default promotion. */
2
+ export class InMemoryVendorCredentialSetRepo {
3
+ constructor(_required) { }
4
+ rows = new Map();
5
+ static rowKey(workspaceId, id) {
6
+ return `${workspaceId}::${id}`;
7
+ }
8
+ assertLabelAvailable(workspaceId, vendorId, label, excludingId) {
9
+ for (const row of this.rows.values()) {
10
+ if (row.workspaceId !== workspaceId || row.vendorId !== vendorId || row.label !== label)
11
+ continue;
12
+ if (excludingId !== undefined && row.id === excludingId)
13
+ continue;
14
+ throw new Error(`UNIQUE constraint failed: vendor_credential_sets.workspace_id, vendor_credential_sets.vendor_id, vendor_credential_sets.label`);
15
+ }
16
+ }
17
+ clearOtherDefaults(workspaceId, vendorId, keepId) {
18
+ for (const [key, row] of this.rows) {
19
+ if (row.workspaceId !== workspaceId || row.vendorId !== vendorId || row.id === keepId || !row.isDefault)
20
+ continue;
21
+ this.rows.set(key, { ...row, isDefault: false });
22
+ }
23
+ }
24
+ async insert(record) {
25
+ this.assertLabelAvailable(record.workspaceId, record.vendorId, record.label);
26
+ this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(record.workspaceId, record.id), { ...record });
27
+ if (record.isDefault)
28
+ this.clearOtherDefaults(record.workspaceId, record.vendorId, record.id);
29
+ }
30
+ async update(record) {
31
+ this.assertLabelAvailable(record.workspaceId, record.vendorId, record.label, record.id);
32
+ this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(record.workspaceId, record.id), { ...record });
33
+ if (record.isDefault)
34
+ this.clearOtherDefaults(record.workspaceId, record.vendorId, record.id);
35
+ }
36
+ async findById(input) {
37
+ return this.rows.get(InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id)) ?? null;
38
+ }
39
+ async findDefaultByVendor(input) {
40
+ for (const row of this.rows.values()) {
41
+ if (row.workspaceId === input.workspaceId && row.vendorId === input.vendorId && row.isDefault)
42
+ return row;
43
+ }
44
+ return null;
45
+ }
46
+ async listByVendor(input) {
47
+ return [...this.rows.values()].filter((row) => row.workspaceId === input.workspaceId && row.vendorId === input.vendorId);
48
+ }
49
+ async listByWorkspace(input) {
50
+ return [...this.rows.values()].filter((row) => row.workspaceId === input.workspaceId);
51
+ }
52
+ async delete(input) {
53
+ const key = InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id);
54
+ const removed = this.rows.get(key);
55
+ this.rows.delete(key);
56
+ if (!removed?.isDefault)
57
+ return;
58
+ const remaining = [...this.rows.values()].filter((row) => row.workspaceId === removed.workspaceId && row.vendorId === removed.vendorId);
59
+ if (remaining.length === 0)
60
+ return;
61
+ const promoted = remaining.reduce((latest, row) => (row.updatedAt > latest.updatedAt ? row : latest));
62
+ this.rows.set(InMemoryVendorCredentialSetRepo.rowKey(promoted.workspaceId, promoted.id), { ...promoted, isDefault: true });
63
+ }
64
+ async updateAccountLabel(input) {
65
+ const key = InMemoryVendorCredentialSetRepo.rowKey(input.workspaceId, input.id);
66
+ const existing = this.rows.get(key);
67
+ if (!existing)
68
+ return;
69
+ this.rows.set(key, { ...existing, accountLabel: input.accountLabel });
70
+ }
71
+ }
72
+ //# sourceMappingURL=repo.memory.js.map
@@ -0,0 +1,67 @@
1
+ /** Host-neutral credential records and workspace-scoped repository contracts.
2
+ * insert/update clear other defaults in the same vendor group; deleting a default promotes
3
+ * the most recently updated remaining row. Summaries never include sealed payloads.
4
+ */
5
+ import type { SealedSecret } from "../types.js";
6
+ export interface VendorCredentialSetRecord {
7
+ readonly workspaceId: string;
8
+ readonly id: string;
9
+ /** Open string so plugin hosts can declare vendors beyond the built-in validators. */
10
+ readonly vendorId: string;
11
+ readonly label: string;
12
+ readonly sealed: SealedSecret;
13
+ /** Last four secret characters for recognition, stored in clear; a suffix alone is not the token. */
14
+ readonly tokenTail: string;
15
+ /** At most one default per (workspaceId, vendorId), maintained by the store/repository write path. */
16
+ readonly isDefault: boolean;
17
+ /** Only a verified public login belongs here; null until verification populates it. */
18
+ readonly accountLabel: string | null;
19
+ readonly createdAt: string;
20
+ readonly updatedAt: string;
21
+ }
22
+ export interface VendorCredentialSetSummary {
23
+ readonly id: string;
24
+ readonly vendorId: string;
25
+ readonly label: string;
26
+ readonly configured: true;
27
+ readonly isDefault: boolean;
28
+ readonly tokenTail: string;
29
+ readonly accountLabel: string | null;
30
+ readonly createdAt: string;
31
+ readonly updatedAt: string;
32
+ }
33
+ export interface VendorCredentialSetRepoPort {
34
+ insert(record: VendorCredentialSetRecord): Promise<void>;
35
+ /** Full-row replacement supports rename and secret rotation; label uniqueness can still reject it. */
36
+ update(record: VendorCredentialSetRecord): Promise<void>;
37
+ findById(input: {
38
+ workspaceId: string;
39
+ id: string;
40
+ }): Promise<VendorCredentialSetRecord | null>;
41
+ findDefaultByVendor(input: {
42
+ workspaceId: string;
43
+ vendorId: string;
44
+ }): Promise<VendorCredentialSetRecord | null>;
45
+ /** Lets the store recognize a group's first row and choose a promotion candidate before deletion. */
46
+ listByVendor(input: {
47
+ workspaceId: string;
48
+ vendorId: string;
49
+ }): Promise<VendorCredentialSetRecord[]>;
50
+ /** Intentionally unpaginated for a human-managed connection list, rather than a bulk credential inventory. */
51
+ listByWorkspace(input: {
52
+ workspaceId: string;
53
+ }): Promise<VendorCredentialSetRecord[]>;
54
+ /** Missing (workspaceId, id) is a no-op so repeated deletes remain idempotent. */
55
+ delete(input: {
56
+ workspaceId: string;
57
+ id: string;
58
+ }): Promise<void>;
59
+ /** Target only accountLabel: verification must not rewrite sealed/default/tail/updatedAt state.
60
+ * A missing row is a no-op, as with delete. */
61
+ updateAccountLabel(input: {
62
+ workspaceId: string;
63
+ id: string;
64
+ accountLabel: string;
65
+ }): Promise<void>;
66
+ }
67
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Host-neutral credential records and workspace-scoped repository contracts.
2
+ * insert/update clear other defaults in the same vendor group; deleting a default promotes
3
+ * the most recently updated remaining row. Summaries never include sealed payloads.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,9 @@
1
+ export type { SealedSecret } from "./types.js";
2
+ export type { RootKeyHandle, KeyringPort, SecretSealerPort } from "./ports.js";
3
+ export { AesGcmSecretSealer } from "./secret-sealer.aesgcm.js";
4
+ export { EnvOrFileKeyring, FixedRootKeyKeyring, UnusableRootKeyError, RootKeyFileAlreadyExistsError, parseRootKeyHex, fingerprintRootKeyHex, inspectRootKeyMaterial, revealRootKeyMaterial, generateFileRootKey, deriveFromRootKey, deriveSigningSecretFromRootKey, } from "./keyring.env.js";
5
+ export type { EnvOrFileKeyringRequired, RootKeyEnvironmentPort, EnvOrFileKeyringOptions, RootKeyRejection, ParsedRootKeyHex, RootKeyStatus, RootKeyReveal, InspectRootKeyMaterialOptions, GeneratedFileRootKey, } from "./keyring.env.js";
6
+ export { formatAad } from "./aad.js";
7
+ export { defaultPlatformMessages } from "../messages.js";
8
+ export type { PlatformMessages } from "../messages.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export { AesGcmSecretSealer } from "./secret-sealer.aesgcm.js";
2
+ export { EnvOrFileKeyring, FixedRootKeyKeyring, UnusableRootKeyError, RootKeyFileAlreadyExistsError, parseRootKeyHex, fingerprintRootKeyHex, inspectRootKeyMaterial, revealRootKeyMaterial, generateFileRootKey, deriveFromRootKey, deriveSigningSecretFromRootKey, } from "./keyring.env.js";
3
+ export { formatAad } from "./aad.js";
4
+ export { defaultPlatformMessages } from "../messages.js";
5
+ //# sourceMappingURL=index.js.map