@cello-protocol/gateway 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/bin/cello-gateway.d.ts +3 -0
  2. package/dist/bin/cello-gateway.d.ts.map +1 -0
  3. package/dist/bin/cello-gateway.js +174 -0
  4. package/dist/bin/cello-gateway.js.map +1 -0
  5. package/dist/client.d.ts +16 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +182 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/config/config-store.d.ts +42 -0
  10. package/dist/config/config-store.d.ts.map +1 -0
  11. package/dist/config/config-store.js +198 -0
  12. package/dist/config/config-store.js.map +1 -0
  13. package/dist/detect/deberta-model-manifest.d.ts +54 -0
  14. package/dist/detect/deberta-model-manifest.d.ts.map +1 -0
  15. package/dist/detect/deberta-model-manifest.js +17 -0
  16. package/dist/detect/deberta-model-manifest.js.map +1 -0
  17. package/dist/detect/exfil.d.ts +14 -0
  18. package/dist/detect/exfil.d.ts.map +1 -0
  19. package/dist/detect/exfil.js +69 -0
  20. package/dist/detect/exfil.js.map +1 -0
  21. package/dist/detect/gitleaks-rules.d.ts +18 -0
  22. package/dist/detect/gitleaks-rules.d.ts.map +1 -0
  23. package/dist/detect/gitleaks-rules.js +229 -0
  24. package/dist/detect/gitleaks-rules.js.map +1 -0
  25. package/dist/detect/injection-patterns.d.ts +10 -0
  26. package/dist/detect/injection-patterns.d.ts.map +1 -0
  27. package/dist/detect/injection-patterns.js +53 -0
  28. package/dist/detect/injection-patterns.js.map +1 -0
  29. package/dist/detect/injection-scanner.d.ts +44 -0
  30. package/dist/detect/injection-scanner.d.ts.map +1 -0
  31. package/dist/detect/injection-scanner.js +45 -0
  32. package/dist/detect/injection-scanner.js.map +1 -0
  33. package/dist/detect/language.d.ts +37 -0
  34. package/dist/detect/language.d.ts.map +1 -0
  35. package/dist/detect/language.js +85 -0
  36. package/dist/detect/language.js.map +1 -0
  37. package/dist/detect/linear-regex.d.ts +20 -0
  38. package/dist/detect/linear-regex.d.ts.map +1 -0
  39. package/dist/detect/linear-regex.js +70 -0
  40. package/dist/detect/linear-regex.js.map +1 -0
  41. package/dist/detect/model-installer.d.ts +32 -0
  42. package/dist/detect/model-installer.d.ts.map +1 -0
  43. package/dist/detect/model-installer.js +94 -0
  44. package/dist/detect/model-installer.js.map +1 -0
  45. package/dist/detect/pii.d.ts +27 -0
  46. package/dist/detect/pii.d.ts.map +1 -0
  47. package/dist/detect/pii.js +119 -0
  48. package/dist/detect/pii.js.map +1 -0
  49. package/dist/detect/rate-limit.d.ts +44 -0
  50. package/dist/detect/rate-limit.d.ts.map +1 -0
  51. package/dist/detect/rate-limit.js +56 -0
  52. package/dist/detect/rate-limit.js.map +1 -0
  53. package/dist/detect/sanitize.d.ts +50 -0
  54. package/dist/detect/sanitize.d.ts.map +1 -0
  55. package/dist/detect/sanitize.js +211 -0
  56. package/dist/detect/sanitize.js.map +1 -0
  57. package/dist/detect/secrets.d.ts +20 -0
  58. package/dist/detect/secrets.d.ts.map +1 -0
  59. package/dist/detect/secrets.js +104 -0
  60. package/dist/detect/secrets.js.map +1 -0
  61. package/dist/index.d.ts +38 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +25 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/passthrough.d.ts +18 -0
  66. package/dist/passthrough.d.ts.map +1 -0
  67. package/dist/passthrough.js +21 -0
  68. package/dist/passthrough.js.map +1 -0
  69. package/dist/protocol.d.ts +53 -0
  70. package/dist/protocol.d.ts.map +1 -0
  71. package/dist/protocol.js +52 -0
  72. package/dist/protocol.js.map +1 -0
  73. package/dist/records/record-store.d.ts +28 -0
  74. package/dist/records/record-store.d.ts.map +1 -0
  75. package/dist/records/record-store.js +134 -0
  76. package/dist/records/record-store.js.map +1 -0
  77. package/dist/screen/inbound.d.ts +29 -0
  78. package/dist/screen/inbound.d.ts.map +1 -0
  79. package/dist/screen/inbound.js +127 -0
  80. package/dist/screen/inbound.js.map +1 -0
  81. package/dist/screen/inbound.test.d.ts +2 -0
  82. package/dist/screen/inbound.test.d.ts.map +1 -0
  83. package/dist/screen/inbound.test.js +49 -0
  84. package/dist/screen/inbound.test.js.map +1 -0
  85. package/dist/screen/outbound.d.ts +57 -0
  86. package/dist/screen/outbound.d.ts.map +1 -0
  87. package/dist/screen/outbound.js +183 -0
  88. package/dist/screen/outbound.js.map +1 -0
  89. package/dist/screen/outbound.test.d.ts +2 -0
  90. package/dist/screen/outbound.test.d.ts.map +1 -0
  91. package/dist/screen/outbound.test.js +62 -0
  92. package/dist/screen/outbound.test.js.map +1 -0
  93. package/dist/server.d.ts +34 -0
  94. package/dist/server.d.ts.map +1 -0
  95. package/dist/server.js +129 -0
  96. package/dist/server.js.map +1 -0
  97. package/dist/spawn.d.ts +32 -0
  98. package/dist/spawn.d.ts.map +1 -0
  99. package/dist/spawn.js +84 -0
  100. package/dist/spawn.js.map +1 -0
  101. package/dist/types.d.ts +121 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/types.js +39 -0
  104. package/dist/types.js.map +1 -0
  105. package/package.json +42 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * M9-IN-003 — inbound language allowlist (default: English).
3
+ *
4
+ * Holds inbound content confidently in a non-allowlisted language, so a jailbreak phrased in a
5
+ * low-resource language can't dodge English-trained screening — WITHOUT blocking legitimate short
6
+ * messages on a guess (AC-002), and with no model and no network call (INV-1).
7
+ *
8
+ * v1 uses dominant Unicode SCRIPT (the story's "script inspection covers most of it"): a message
9
+ * confidently dominated by a non-Latin script (Cyrillic, Greek, Arabic, Hebrew, CJK, Devanagari,
10
+ * Thai, …) is outside the {English=Latin} allowlist and is held. Latin-script text is allowed —
11
+ * distinguishing Latin-script LANGUAGES (English vs French/Spanish) needs a trained classifier and
12
+ * is a separate design session (Andre, 2026-06-23). Short / low-letter-count messages are allowed.
13
+ *
14
+ * This is the DETECTOR. Delivering the held note to the operator + the terminal-block inbound
15
+ * handling (ack-and-record without delivering, distinct from the transient fail-closed hold) is the
16
+ * integration step (M9-FEED-001 / the gate / the L4 terminal-vs-transient split).
17
+ */
18
+ function scriptOf(cp) {
19
+ // Returns the script for a LETTER codepoint, or null for non-letters (digits/punct/space/emoji).
20
+ if ((cp >= 0x41 && cp <= 0x5a) || (cp >= 0x61 && cp <= 0x7a) ||
21
+ (cp >= 0xc0 && cp <= 0x24f) || (cp >= 0x1e00 && cp <= 0x1eff))
22
+ return "latin";
23
+ if (cp >= 0x400 && cp <= 0x4ff)
24
+ return "cyrillic";
25
+ if ((cp >= 0x370 && cp <= 0x3ff) || (cp >= 0x1f00 && cp <= 0x1fff))
26
+ return "greek";
27
+ if ((cp >= 0x600 && cp <= 0x6ff) || (cp >= 0x750 && cp <= 0x77f))
28
+ return "arabic";
29
+ if (cp >= 0x590 && cp <= 0x5ff)
30
+ return "hebrew";
31
+ if ((cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0x3400 && cp <= 0x4dbf))
32
+ return "han";
33
+ if (cp >= 0x3040 && cp <= 0x30ff)
34
+ return "kana";
35
+ if ((cp >= 0xac00 && cp <= 0xd7af) || (cp >= 0x1100 && cp <= 0x11ff))
36
+ return "hangul";
37
+ if (cp >= 0x900 && cp <= 0x97f)
38
+ return "devanagari";
39
+ if (cp >= 0xe00 && cp <= 0xe7f)
40
+ return "thai";
41
+ // A letter we don't bucket finely — count it as "other" only if it is clearly a letter.
42
+ if (cp > 0x2bf && !(cp >= 0x2000 && cp <= 0x2bff))
43
+ return "other";
44
+ return null;
45
+ }
46
+ const SCRIPT_LABEL = {
47
+ latin: "Latin", cyrillic: "Cyrillic", greek: "Greek", arabic: "Arabic", hebrew: "Hebrew",
48
+ han: "Han/CJK", kana: "Japanese kana", hangul: "Korean Hangul", devanagari: "Devanagari",
49
+ thai: "Thai", other: "a non-Latin",
50
+ };
51
+ export function screenInboundLanguage(text, opts = {}) {
52
+ const allow = new Set(opts.allow ?? ["latin"]);
53
+ const minLetters = opts.minLetters ?? 12;
54
+ const minShare = opts.minShare ?? 0.5;
55
+ const counts = new Map();
56
+ let total = 0;
57
+ for (const ch of text) {
58
+ const s = scriptOf(ch.codePointAt(0));
59
+ if (!s)
60
+ continue;
61
+ counts.set(s, (counts.get(s) ?? 0) + 1);
62
+ total++;
63
+ }
64
+ // Not enough letters to be confident → allow (don't block short/ambiguous, AC-002).
65
+ if (total < minLetters)
66
+ return { allowed: true };
67
+ let dominant = "latin";
68
+ let max = 0;
69
+ for (const [s, n] of counts)
70
+ if (n > max) {
71
+ max = n;
72
+ dominant = s;
73
+ }
74
+ // Allowed script dominant, or the dominant non-allowed script isn't a clear majority → allow.
75
+ if (allow.has(dominant) || max / total < minShare)
76
+ return { allowed: true };
77
+ return {
78
+ allowed: false,
79
+ script: dominant,
80
+ reason: `This message is predominantly ${SCRIPT_LABEL[dominant]} script, outside the gateway's ` +
81
+ `language allowlist (default: English). It was held. To allow this language, add its script ` +
82
+ `to the gateway language allowlist.`,
83
+ };
84
+ }
85
+ //# sourceMappingURL=language.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/detect/language.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Resolve the RE2 engine: native first, WASM fallback. Idempotent. Returns which engine is live.
3
+ * Throws only if NEITHER engine can load (re2-wasm is a hard dependency, so that should not happen).
4
+ */
5
+ export declare function initLinearRegex(): Promise<"native" | "wasm">;
6
+ /** Which RE2 engine is live (`native`, `wasm`, or `uninitialized`). For the gateway `doctor` report. */
7
+ export declare function linearRegexEngine(): "native" | "wasm" | "uninitialized";
8
+ /** A compiled linear-time pattern. Construct only after `initLinearRegex()` has resolved. */
9
+ export declare class LinearRegex {
10
+ #private;
11
+ /** @param flags RegExp-style flags (e.g. "i"). The unicode flag is forced on for the WASM engine. */
12
+ constructor(pattern: string, flags?: string);
13
+ /** True iff the pattern matches anywhere in `s`. Linear time, guaranteed. */
14
+ test(s: string): boolean;
15
+ /** The first matched substring, or null. */
16
+ match(s: string): string | null;
17
+ /** The first match as a `RegExpExecArray` (with `.index` and capture groups), or null. */
18
+ exec(s: string): RegExpExecArray | null;
19
+ }
20
+ //# sourceMappingURL=linear-regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-regex.d.ts","sourceRoot":"","sources":["../../src/detect/linear-regex.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,CAoBlE;AAED,wGAAwG;AACxG,wBAAgB,iBAAiB,IAAI,QAAQ,GAAG,MAAM,GAAG,eAAe,CAEvE;AAED,6FAA6F;AAC7F,qBAAa,WAAW;;IAGtB,qGAAqG;gBACzF,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK;IAOvC,6EAA6E;IAC7E,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO;IAIxB,4CAA4C;IAC5C,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK/B,0FAA0F;IAC1F,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;CAGxC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * LinearRegex — a linear-time (RE2) regular expression behind ONE interface, used for any pattern
3
+ * that runs against adversary-controlled content (injection-pattern matching, the gitleaks set).
4
+ *
5
+ * RE2 guarantees time linear in the input — no catastrophic backtracking, so a crafted message can
6
+ * never peg the gateway (ReDoS), which would otherwise be both a DoS and a way to force a timeout
7
+ * verdict. We prefer the MAINTAINED native engine (`re2`, an optionalDependency) and fall back to
8
+ * the WASM build (`re2-wasm`, a regular dependency that always installs with no compile) when the
9
+ * native addon did not build on the operator's machine. Either way: real RE2, same guarantee.
10
+ *
11
+ * `initLinearRegex()` MUST be awaited once at gateway startup (the WASM fallback is an async import).
12
+ * After that, `new LinearRegex(...)` is synchronous.
13
+ */
14
+ import { createRequire } from "node:module";
15
+ let factory = null;
16
+ let engine = "uninitialized";
17
+ /**
18
+ * Resolve the RE2 engine: native first, WASM fallback. Idempotent. Returns which engine is live.
19
+ * Throws only if NEITHER engine can load (re2-wasm is a hard dependency, so that should not happen).
20
+ */
21
+ export async function initLinearRegex() {
22
+ if (factory)
23
+ return engine;
24
+ // Native `re2` is CommonJS; load it through createRequire so this ESM module can require() it.
25
+ // A missing/failed-to-build optional addon throws here — caught, and we fall through to WASM.
26
+ try {
27
+ const require = createRequire(import.meta.url);
28
+ const NativeRE2 = require("re2");
29
+ factory = (pattern, flags) => new NativeRE2(pattern, flags);
30
+ engine = "native";
31
+ return "native";
32
+ }
33
+ catch {
34
+ /* native not available — fall back to WASM */
35
+ }
36
+ // `re2-wasm` is ESM and requires the unicode ('u') flag.
37
+ const wasm = (await import("re2-wasm"));
38
+ factory = (pattern, flags) => new wasm.RE2(pattern, flags.includes("u") ? flags : flags + "u");
39
+ engine = "wasm";
40
+ return "wasm";
41
+ }
42
+ /** Which RE2 engine is live (`native`, `wasm`, or `uninitialized`). For the gateway `doctor` report. */
43
+ export function linearRegexEngine() {
44
+ return engine;
45
+ }
46
+ /** A compiled linear-time pattern. Construct only after `initLinearRegex()` has resolved. */
47
+ export class LinearRegex {
48
+ #re;
49
+ /** @param flags RegExp-style flags (e.g. "i"). The unicode flag is forced on for the WASM engine. */
50
+ constructor(pattern, flags = "") {
51
+ if (!factory) {
52
+ throw new Error("LinearRegex: call and await initLinearRegex() before constructing patterns");
53
+ }
54
+ this.#re = factory(pattern, flags);
55
+ }
56
+ /** True iff the pattern matches anywhere in `s`. Linear time, guaranteed. */
57
+ test(s) {
58
+ return this.#re.test(s);
59
+ }
60
+ /** The first matched substring, or null. */
61
+ match(s) {
62
+ const m = this.#re.exec(s);
63
+ return m ? m[0] : null;
64
+ }
65
+ /** The first match as a `RegExpExecArray` (with `.index` and capture groups), or null. */
66
+ exec(s) {
67
+ return this.#re.exec(s);
68
+ }
69
+ }
70
+ //# sourceMappingURL=linear-regex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-regex.js","sourceRoot":"","sources":["../../src/detect/linear-regex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C,IAAI,OAAO,GAAsB,IAAI,CAAC;AACtC,IAAI,MAAM,GAAwC,eAAe,CAAC;AAElE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,OAAO;QAAE,OAAO,MAA2B,CAAC;IAEhD,+FAA+F;IAC/F,8FAA8F;IAC9F,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAA+C,CAAC;QAC/E,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5D,MAAM,GAAG,QAAQ,CAAC;QAClB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;IAChD,CAAC;IAED,yDAAyD;IACzD,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAAuD,CAAC;IAC9F,OAAO,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IAC/F,MAAM,GAAG,MAAM,CAAC;IAChB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,iBAAiB;IAC/B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6FAA6F;AAC7F,MAAM,OAAO,WAAW;IACb,GAAG,CAAc;IAE1B,qGAAqG;IACrG,YAAY,OAAe,EAAE,KAAK,GAAG,EAAE;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAChG,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,6EAA6E;IAC7E,IAAI,CAAC,CAAS;QACZ,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAED,4CAA4C;IAC5C,KAAK,CAAC,CAAS;QACb,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,0FAA0F;IAC1F,IAAI,CAAC,CAAS;QACZ,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,32 @@
1
+ /** SHA-256 of a file on disk (streamed — never loads the whole 568 MB into memory). */
2
+ export declare function sha256File(path: string): Promise<string>;
3
+ /** Are all the model files present under `dir`? (Existence check; integrity is the SHA verify.) */
4
+ export declare function isModelInstalled(dir: string): Promise<boolean>;
5
+ /** Re-verify the pinned-SHA files of an existing install. Returns the first mismatch, or null. */
6
+ export declare function verifyModel(dir: string): Promise<{
7
+ ok: true;
8
+ } | {
9
+ ok: false;
10
+ badFile: string;
11
+ }>;
12
+ export interface InstallResult {
13
+ installed: boolean;
14
+ /** true when the model was absent and consent was withheld — nothing was downloaded. */
15
+ needsConsent?: boolean;
16
+ error?: string;
17
+ }
18
+ export interface InstallOptions {
19
+ dir: string;
20
+ /** The operator's explicit consent to download ~568 MB. Without it, nothing is fetched. */
21
+ consent: boolean;
22
+ /** Injectable fetch (defaults to global fetch) — tests pass a fake; production uses the network. */
23
+ fetchImpl?: typeof fetch;
24
+ onProgress?: (file: string, index: number, total: number) => void;
25
+ }
26
+ /**
27
+ * Install the model under `dir`. No-ops (returns installed) if already present. Requires `consent`
28
+ * to download; verifies every pinned SHA-256 and deletes + fails on any mismatch (no partial/tampered
29
+ * model is ever left in place).
30
+ */
31
+ export declare function installModel(opts: InstallOptions): Promise<InstallResult>;
32
+ //# sourceMappingURL=model-installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-installer.d.ts","sourceRoot":"","sources":["../../src/detect/model-installer.ts"],"names":[],"mappings":"AAiBA,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxD;AAED,mGAAmG;AACnG,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASpE;AAED,kGAAkG;AAClG,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAOrG;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,wFAAwF;IACxF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,2FAA2F;IAC3F,OAAO,EAAE,OAAO,CAAC;IACjB,oGAAoG;IACpG,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmC/E"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * M9-IN-002 — the DeBERTa model installer.
3
+ *
4
+ * The model is NOT bundled (it is ~568 MB): the gateway downloads it ONCE, only with explicit
5
+ * operator consent, verifies the pinned SHA-256 of the weights (so a compromised mirror cannot swap
6
+ * the model), and caches it locally. When it is absent, Layer-2 is simply off — the gateway never
7
+ * fails closed on a missing OPTIONAL model (Layer-1 still runs). Consent surfaces via the CLI
8
+ * (`cello gateway install-model`), an actionable daemon guidance field, or the portal.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { createReadStream, createWriteStream } from "node:fs";
12
+ import { mkdir, stat, rm } from "node:fs/promises";
13
+ import { dirname, join } from "node:path";
14
+ import { Readable } from "node:stream";
15
+ import { pipeline } from "node:stream/promises";
16
+ import { DEBERTA_MODEL } from "./deberta-model-manifest.js";
17
+ /** SHA-256 of a file on disk (streamed — never loads the whole 568 MB into memory). */
18
+ export function sha256File(path) {
19
+ return new Promise((resolve, reject) => {
20
+ const hash = createHash("sha256");
21
+ const rs = createReadStream(path);
22
+ rs.on("error", reject);
23
+ rs.on("data", (chunk) => hash.update(chunk));
24
+ rs.on("end", () => resolve(hash.digest("hex")));
25
+ });
26
+ }
27
+ /** Are all the model files present under `dir`? (Existence check; integrity is the SHA verify.) */
28
+ export async function isModelInstalled(dir) {
29
+ for (const f of DEBERTA_MODEL.files) {
30
+ try {
31
+ await stat(join(dir, f.path));
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ return true;
38
+ }
39
+ /** Re-verify the pinned-SHA files of an existing install. Returns the first mismatch, or null. */
40
+ export async function verifyModel(dir) {
41
+ for (const f of DEBERTA_MODEL.files) {
42
+ if (!f.sha256)
43
+ continue;
44
+ const got = await sha256File(join(dir, f.path)).catch(() => "");
45
+ if (got !== f.sha256)
46
+ return { ok: false, badFile: f.path };
47
+ }
48
+ return { ok: true };
49
+ }
50
+ /**
51
+ * Install the model under `dir`. No-ops (returns installed) if already present. Requires `consent`
52
+ * to download; verifies every pinned SHA-256 and deletes + fails on any mismatch (no partial/tampered
53
+ * model is ever left in place).
54
+ */
55
+ export async function installModel(opts) {
56
+ if (await isModelInstalled(opts.dir))
57
+ return { installed: true };
58
+ if (!opts.consent)
59
+ return { installed: false, needsConsent: true };
60
+ const doFetch = opts.fetchImpl ?? fetch;
61
+ const total = DEBERTA_MODEL.files.length;
62
+ for (let i = 0; i < total; i++) {
63
+ const f = DEBERTA_MODEL.files[i];
64
+ opts.onProgress?.(f.path, i, total);
65
+ const dest = join(opts.dir, f.path);
66
+ await mkdir(dirname(dest), { recursive: true });
67
+ let res;
68
+ try {
69
+ res = await doFetch(DEBERTA_MODEL.baseUrl + f.path);
70
+ }
71
+ catch (err) {
72
+ return { installed: false, error: `download failed for ${f.path}: ${err instanceof Error ? err.message : String(err)}` };
73
+ }
74
+ if (!res.ok || !res.body)
75
+ return { installed: false, error: `download failed for ${f.path}: HTTP ${res.status}` };
76
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
77
+ // Integrity: the committed SIZE catches truncation / a wrong file. (Full SHA-256 pinning is the
78
+ // intended hardening — see the manifest note; the pinned digest is verified here when present.)
79
+ const downloaded = await stat(dest);
80
+ if (downloaded.size !== f.size) {
81
+ await rm(dest, { force: true });
82
+ return { installed: false, error: `size mismatch for ${f.path}: expected ${f.size}, got ${downloaded.size} — removed` };
83
+ }
84
+ if (f.sha256) {
85
+ const got = await sha256File(dest);
86
+ if (got !== f.sha256) {
87
+ await rm(dest, { force: true });
88
+ return { installed: false, error: `checksum mismatch for ${f.path} — the file was NOT trusted and has been removed` };
89
+ }
90
+ }
91
+ }
92
+ return { installed: true };
93
+ }
94
+ //# sourceMappingURL=model-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-installer.js","sourceRoot":"","sources":["../../src/detect/model-installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export type PIICategory = "pii:email" | "pii:ip" | "pii:ssn" | "pii:credit_card" | "pii:phone";
2
+ export interface PIIEvent {
3
+ category: PIICategory;
4
+ value: string;
5
+ /** Deterministic id over (category, value) — the handle the agent passes on a governance re-send. */
6
+ flagId: string;
7
+ }
8
+ export interface PIIResult {
9
+ disposition: "allow" | "warn";
10
+ severity?: "normal" | "bulk";
11
+ events: PIIEvent[];
12
+ count: number;
13
+ }
14
+ export interface PIIScreenerOptions {
15
+ /** The operator's whitelisted PII values (own email/phone …). Normalized on construction. */
16
+ whitelist: string[];
17
+ /** Distinct non-whitelisted values in ONE message at/above which the warn is bulk. Default 5. */
18
+ bulkThreshold?: number;
19
+ /** Distinct non-whitelisted values across a SESSION at/above which warns escalate to bulk (drip). Default 20. */
20
+ cumulativeThreshold?: number;
21
+ }
22
+ export declare class OutboundPIIScreener {
23
+ #private;
24
+ constructor(opts: PIIScreenerOptions);
25
+ screen(content: Uint8Array, sessionId: string): PIIResult;
26
+ }
27
+ //# sourceMappingURL=pii.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../../src/detect/pii.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAE/F,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC7B,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iGAAiG;IACjG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iHAAiH;IACjH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAwCD,qBAAa,mBAAmB;;gBAOlB,IAAI,EAAE,kBAAkB;IAUpC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS;CA8C1D"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * M9-OUT-002 — outbound PII: whitelist + warn (NOT blanket redaction).
3
+ *
4
+ * Detects email / phone / SSN / credit-card / IP, passes whitelisted values (the operator's own
5
+ * registered contact details) silently, and warns on the rest — so sharing your own contact is
6
+ * frictionless while a contact-list dump is caught. A bulk dump is ONE escalated warn (severity),
7
+ * and a sustained per-session drip escalates too (SI-001). Pure detection + deterministic flag ids;
8
+ * the warn UX / allow-once / WebAuthn-whitelist-add re-send is M9-FEED-001.
9
+ *
10
+ * All patterns are anchored / negated-class (linear time — no RE2 engine needed). Cross-category
11
+ * overlaps (a phone pattern also matching an SSN/IP/card span) are resolved by priority so each
12
+ * value is counted once, in its most specific category.
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ const EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
16
+ const IP_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
17
+ const SSN_RE = /\b\d{3}-\d{2}-\d{4}\b/g;
18
+ const CC_RE = /\b(?:\d[ -]?){13,19}\b/g;
19
+ const PHONE_RE = /\+?\d[\d\s().-]{7,}\d/g;
20
+ function flagIdFor(category, value) {
21
+ return createHash("sha256").update(`${category}:${value}`).digest("hex").slice(0, 12);
22
+ }
23
+ function luhnValid(digits) {
24
+ let sum = 0;
25
+ let alt = false;
26
+ for (let i = digits.length - 1; i >= 0; i--) {
27
+ let d = digits.charCodeAt(i) - 48;
28
+ if (d < 0 || d > 9)
29
+ return false;
30
+ if (alt) {
31
+ d *= 2;
32
+ if (d > 9)
33
+ d -= 9;
34
+ }
35
+ sum += d;
36
+ alt = !alt;
37
+ }
38
+ return digits.length > 0 && sum % 10 === 0;
39
+ }
40
+ function validIp(value) {
41
+ const parts = value.split(".");
42
+ return parts.length === 4 && parts.every((p) => { const n = Number(p); return p.length > 0 && n >= 0 && n <= 255; });
43
+ }
44
+ function normalizeEmail(v) { return v.toLowerCase(); }
45
+ function normalizePhone(v) { return v.replace(/\D/g, ""); }
46
+ function normalizeForWhitelist(category, value) {
47
+ if (category === "pii:email")
48
+ return normalizeEmail(value);
49
+ if (category === "pii:phone")
50
+ return normalizePhone(value);
51
+ return value;
52
+ }
53
+ export class OutboundPIIScreener {
54
+ #whitelist;
55
+ #bulkThreshold;
56
+ #cumulativeThreshold;
57
+ // sessionId → the distinct (category:value) keys of non-whitelisted PII seen across the session.
58
+ #sessionDistinct = new Map();
59
+ constructor(opts) {
60
+ // Whitelist is stored normalized two ways (email lowercase, phone digits) so a raw value matches.
61
+ this.#whitelist = new Set([
62
+ ...opts.whitelist.map(normalizeEmail),
63
+ ...opts.whitelist.map(normalizePhone).filter((p) => p.length > 0),
64
+ ]);
65
+ this.#bulkThreshold = opts.bulkThreshold ?? 5;
66
+ this.#cumulativeThreshold = opts.cumulativeThreshold ?? 20;
67
+ }
68
+ screen(content, sessionId) {
69
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(content);
70
+ const candidates = [];
71
+ const collect = (re, category, priority, accept) => {
72
+ for (const m of text.matchAll(re)) {
73
+ const value = m[0];
74
+ if (!accept(value))
75
+ continue;
76
+ candidates.push({ start: m.index ?? 0, end: (m.index ?? 0) + value.length, category, value, priority });
77
+ }
78
+ };
79
+ collect(EMAIL_RE, "pii:email", 0, () => true);
80
+ collect(IP_RE, "pii:ip", 1, validIp);
81
+ collect(SSN_RE, "pii:ssn", 2, () => true);
82
+ collect(CC_RE, "pii:credit_card", 3, (v) => luhnValid(v.replace(/[^0-9]/g, "")));
83
+ collect(PHONE_RE, "pii:phone", 4, (v) => v.replace(/\D/g, "").length >= 7);
84
+ // Resolve overlaps: a higher-priority (more specific) match wins its character span.
85
+ candidates.sort((a, b) => a.priority - b.priority || a.start - b.start);
86
+ const taken = [];
87
+ const accepted = [];
88
+ for (const c of candidates) {
89
+ if (taken.some(([s, e]) => c.start < e && c.end > s))
90
+ continue;
91
+ taken.push([c.start, c.end]);
92
+ accepted.push(c);
93
+ }
94
+ // Drop whitelisted values; dedupe identical (category,value) within the message.
95
+ const byKey = new Map();
96
+ for (const c of accepted) {
97
+ if (this.#whitelist.has(normalizeForWhitelist(c.category, c.value)))
98
+ continue;
99
+ const key = `${c.category}:${c.value}`;
100
+ if (!byKey.has(key))
101
+ byKey.set(key, { category: c.category, value: c.value, flagId: flagIdFor(c.category, c.value) });
102
+ }
103
+ const events = [...byKey.values()];
104
+ if (events.length === 0)
105
+ return { disposition: "allow", events: [], count: 0 };
106
+ // Per-session cumulative view (SI-001 drip).
107
+ let distinct = this.#sessionDistinct.get(sessionId);
108
+ if (!distinct) {
109
+ distinct = new Set();
110
+ this.#sessionDistinct.set(sessionId, distinct);
111
+ }
112
+ for (const e of events)
113
+ distinct.add(`${e.category}:${e.value}`);
114
+ const count = events.length;
115
+ const bulk = count >= this.#bulkThreshold || distinct.size >= this.#cumulativeThreshold;
116
+ return { disposition: "warn", severity: bulk ? "bulk" : "normal", events, count };
117
+ }
118
+ }
119
+ //# sourceMappingURL=pii.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.js","sourceRoot":"","sources":["../../src/detect/pii.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * M9-OUT-004 — per-agent-identity outbound rate limiting.
3
+ *
4
+ * A sliding-window counter keyed on the AGENT IDENTITY (peer-id / agent name), never a source IP —
5
+ * so a compromised or runaway agent cannot flood a peer or use volume as an exfiltration channel,
6
+ * while a normal agent's traffic is never throttled or delayed. Pure in-memory, deterministic with
7
+ * an injectable clock.
8
+ *
9
+ * The cap is a configured value (M9-CFG-001 owns the config; this is the enforcement). A throttled
10
+ * verdict carries a distinct `rate_limited` reason and a retry-after, surfaced to the agent through
11
+ * the outbound seam's never-hang feedback (M9-CORE-001 / M9-FEED-001).
12
+ */
13
+ export interface RateLimitConfig {
14
+ /** Max messages allowed per agent within the window. */
15
+ maxPerWindow: number;
16
+ /** The sliding-window length, in milliseconds. */
17
+ windowMs: number;
18
+ }
19
+ export interface RateLimitVerdict {
20
+ limited: boolean;
21
+ reason?: "rate_limited";
22
+ guidance?: string;
23
+ /** Milliseconds until the agent may send again (only when limited). */
24
+ retryAfterMs?: number;
25
+ }
26
+ export declare class OutboundRateLimiter {
27
+ #private;
28
+ constructor(config: RateLimitConfig, now?: () => number);
29
+ /**
30
+ * READ-ONLY rate check — does NOT consume a slot. Returns a throttle verdict when the agent is
31
+ * at/over the cap, else `{limited:false}`. Use this to GATE a send; commit the slot with
32
+ * `record()` only once the message actually reaches the wire (M2 review — block/warn must not
33
+ * count, and a warn→re-send must not double-count).
34
+ */
35
+ peek(agentIdentity: string): RateLimitVerdict;
36
+ /** Commit one sent message against `agentIdentity`'s cap (call only when it reaches the wire). */
37
+ record(agentIdentity: string): void;
38
+ /**
39
+ * Account one send attempt: `peek()` then `record()` when under the cap. A throttled attempt
40
+ * records nothing. (Convenience for standalone use; the composition uses peek + conditional record.)
41
+ */
42
+ check(agentIdentity: string): RateLimitVerdict;
43
+ }
44
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/detect/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,mBAAmB;;gBAMlB,MAAM,EAAE,eAAe,EAAE,GAAG,GAAE,MAAM,MAAyB;IAWzE;;;;;OAKG;IACH,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,gBAAgB;IAmB7C,kGAAkG;IAClG,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAOnC;;;OAGG;IACH,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,gBAAgB;CAK/C"}
@@ -0,0 +1,56 @@
1
+ export class OutboundRateLimiter {
2
+ #config;
3
+ #now;
4
+ // agent identity → ascending timestamps of ALLOWED sends still inside the window.
5
+ #sends = new Map();
6
+ constructor(config, now = () => Date.now()) {
7
+ this.#config = config;
8
+ this.#now = now;
9
+ }
10
+ /** The in-window timestamps for `agentIdentity` (read-only — prunes aged-out entries). */
11
+ #live(agentIdentity, now) {
12
+ const all = this.#sends.get(agentIdentity);
13
+ return all ? all.filter((t) => t > now - this.#config.windowMs) : [];
14
+ }
15
+ /**
16
+ * READ-ONLY rate check — does NOT consume a slot. Returns a throttle verdict when the agent is
17
+ * at/over the cap, else `{limited:false}`. Use this to GATE a send; commit the slot with
18
+ * `record()` only once the message actually reaches the wire (M2 review — block/warn must not
19
+ * count, and a warn→re-send must not double-count).
20
+ */
21
+ peek(agentIdentity) {
22
+ const now = this.#now();
23
+ const live = this.#live(agentIdentity, now);
24
+ if (live.length >= this.#config.maxPerWindow) {
25
+ const oldest = live[0];
26
+ const retryAfterMs = Math.max(1, oldest + this.#config.windowMs - now);
27
+ return {
28
+ limited: true,
29
+ reason: "rate_limited",
30
+ guidance: `Outbound rate limit reached for this agent (${this.#config.maxPerWindow} messages per ` +
31
+ `${Math.round(this.#config.windowMs / 1000)}s). This message was not sent. ` +
32
+ `Wait about ${Math.ceil(retryAfterMs / 1000)}s and retry; the window slides as older messages age out.`,
33
+ retryAfterMs,
34
+ };
35
+ }
36
+ return { limited: false };
37
+ }
38
+ /** Commit one sent message against `agentIdentity`'s cap (call only when it reaches the wire). */
39
+ record(agentIdentity) {
40
+ const now = this.#now();
41
+ const live = this.#live(agentIdentity, now);
42
+ live.push(now);
43
+ this.#sends.set(agentIdentity, live);
44
+ }
45
+ /**
46
+ * Account one send attempt: `peek()` then `record()` when under the cap. A throttled attempt
47
+ * records nothing. (Convenience for standalone use; the composition uses peek + conditional record.)
48
+ */
49
+ check(agentIdentity) {
50
+ const v = this.peek(agentIdentity);
51
+ if (!v.limited)
52
+ this.record(agentIdentity);
53
+ return v;
54
+ }
55
+ }
56
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/detect/rate-limit.ts"],"names":[],"mappings":"AA2BA,MAAM,OAAO,mBAAmB;IACrB,OAAO,CAAkB;IACzB,IAAI,CAAe;IAC5B,kFAAkF;IACzE,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE9C,YAAY,MAAuB,EAAE,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QACvE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,0FAA0F;IAC1F,KAAK,CAAC,aAAqB,EAAE,GAAW;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC3C,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,aAAqB;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5C,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACvB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC;YACvE,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,cAAc;gBACtB,QAAQ,EACN,+CAA+C,IAAI,CAAC,OAAO,CAAC,YAAY,gBAAgB;oBACxF,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,iCAAiC;oBAC5E,cAAc,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,2DAA2D;gBACzG,YAAY;aACb,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;IAED,kGAAkG;IAClG,MAAM,CAAC,aAAqB;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAqB;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,OAAO;YAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC;IACX,CAAC;CACF"}