@tokenfactory/acc-runner 0.43.0 → 0.44.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 (110) hide show
  1. package/README.md +75 -3
  2. package/dist/companion-run.d.ts.map +1 -1
  3. package/dist/companion-run.js +20 -0
  4. package/dist/companion-run.js.map +1 -1
  5. package/dist/config.d.ts +72 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +117 -3
  8. package/dist/config.js.map +1 -1
  9. package/dist/cost-pricing.d.ts +45 -0
  10. package/dist/cost-pricing.d.ts.map +1 -1
  11. package/dist/cost-pricing.js +57 -0
  12. package/dist/cost-pricing.js.map +1 -1
  13. package/dist/doctor.d.ts +4 -0
  14. package/dist/doctor.d.ts.map +1 -1
  15. package/dist/doctor.js +32 -3
  16. package/dist/doctor.js.map +1 -1
  17. package/dist/engines/registry.d.ts +17 -6
  18. package/dist/engines/registry.d.ts.map +1 -1
  19. package/dist/engines/registry.js +38 -13
  20. package/dist/engines/registry.js.map +1 -1
  21. package/dist/git-auth/github-token-source.d.ts +54 -0
  22. package/dist/git-auth/github-token-source.d.ts.map +1 -0
  23. package/dist/git-auth/github-token-source.js +44 -0
  24. package/dist/git-auth/github-token-source.js.map +1 -0
  25. package/dist/git-auth/index.d.ts +4 -0
  26. package/dist/git-auth/index.d.ts.map +1 -1
  27. package/dist/git-auth/index.js +4 -0
  28. package/dist/git-auth/index.js.map +1 -1
  29. package/dist/git-auth/installation-token.d.ts +95 -0
  30. package/dist/git-auth/installation-token.d.ts.map +1 -0
  31. package/dist/git-auth/installation-token.js +192 -0
  32. package/dist/git-auth/installation-token.js.map +1 -0
  33. package/dist/instance.d.ts +30 -0
  34. package/dist/instance.d.ts.map +1 -0
  35. package/dist/instance.js +51 -0
  36. package/dist/instance.js.map +1 -0
  37. package/dist/keychain.d.ts +4 -8
  38. package/dist/keychain.d.ts.map +1 -1
  39. package/dist/keychain.js +10 -4
  40. package/dist/keychain.js.map +1 -1
  41. package/dist/local-repo/command.d.ts +28 -0
  42. package/dist/local-repo/command.d.ts.map +1 -0
  43. package/dist/local-repo/command.js +178 -0
  44. package/dist/local-repo/command.js.map +1 -0
  45. package/dist/local-repo/index.d.ts +119 -0
  46. package/dist/local-repo/index.d.ts.map +1 -0
  47. package/dist/local-repo/index.js +141 -0
  48. package/dist/local-repo/index.js.map +1 -0
  49. package/dist/login.d.ts +4 -0
  50. package/dist/login.d.ts.map +1 -1
  51. package/dist/login.js +6 -1
  52. package/dist/login.js.map +1 -1
  53. package/dist/mcp-spawn.d.ts +19 -0
  54. package/dist/mcp-spawn.d.ts.map +1 -1
  55. package/dist/mcp-spawn.js +52 -26
  56. package/dist/mcp-spawn.js.map +1 -1
  57. package/dist/memory-context.d.ts +106 -0
  58. package/dist/memory-context.d.ts.map +1 -0
  59. package/dist/memory-context.js +220 -0
  60. package/dist/memory-context.js.map +1 -0
  61. package/dist/memory-curation.d.ts +172 -0
  62. package/dist/memory-curation.d.ts.map +1 -0
  63. package/dist/memory-curation.js +277 -0
  64. package/dist/memory-curation.js.map +1 -0
  65. package/dist/program.d.ts.map +1 -1
  66. package/dist/program.js +34 -0
  67. package/dist/program.js.map +1 -1
  68. package/dist/provider-auth.d.ts +70 -1
  69. package/dist/provider-auth.d.ts.map +1 -1
  70. package/dist/provider-auth.js +106 -2
  71. package/dist/provider-auth.js.map +1 -1
  72. package/dist/runtime/cache-dir.d.ts.map +1 -1
  73. package/dist/runtime/cache-dir.js +8 -3
  74. package/dist/runtime/cache-dir.js.map +1 -1
  75. package/dist/runtime/decision-provenance.d.ts +63 -0
  76. package/dist/runtime/decision-provenance.d.ts.map +1 -0
  77. package/dist/runtime/decision-provenance.js +107 -0
  78. package/dist/runtime/decision-provenance.js.map +1 -0
  79. package/dist/runtime/prompt-substitute.d.ts +100 -0
  80. package/dist/runtime/prompt-substitute.d.ts.map +1 -0
  81. package/dist/runtime/prompt-substitute.js +124 -0
  82. package/dist/runtime/prompt-substitute.js.map +1 -0
  83. package/dist/runtime/review-cost-cap.d.ts +167 -0
  84. package/dist/runtime/review-cost-cap.d.ts.map +1 -0
  85. package/dist/runtime/review-cost-cap.js +182 -0
  86. package/dist/runtime/review-cost-cap.js.map +1 -0
  87. package/dist/runtime/reviewer-abstain.d.ts +101 -0
  88. package/dist/runtime/reviewer-abstain.d.ts.map +1 -0
  89. package/dist/runtime/reviewer-abstain.js +138 -0
  90. package/dist/runtime/reviewer-abstain.js.map +1 -0
  91. package/dist/runtime/reviewer.d.ts +75 -0
  92. package/dist/runtime/reviewer.d.ts.map +1 -1
  93. package/dist/runtime/reviewer.js +235 -81
  94. package/dist/runtime/reviewer.js.map +1 -1
  95. package/dist/runtime/singleton.d.ts.map +1 -1
  96. package/dist/runtime/singleton.js +9 -1
  97. package/dist/runtime/singleton.js.map +1 -1
  98. package/dist/serving-auth/index.d.ts +79 -0
  99. package/dist/serving-auth/index.d.ts.map +1 -0
  100. package/dist/serving-auth/index.js +169 -0
  101. package/dist/serving-auth/index.js.map +1 -0
  102. package/dist/task-runner.d.ts +19 -2
  103. package/dist/task-runner.d.ts.map +1 -1
  104. package/dist/task-runner.js +110 -28
  105. package/dist/task-runner.js.map +1 -1
  106. package/dist/watch.d.ts +13 -0
  107. package/dist/watch.d.ts.map +1 -1
  108. package/dist/watch.js +128 -20
  109. package/dist/watch.js.map +1 -1
  110. package/package.json +3 -2
@@ -29,9 +29,53 @@
29
29
  import os from "node:os";
30
30
  import path from "node:path";
31
31
  import { RUNNER_KEYCHAIN_SERVICE } from "./types.js";
32
- /** Trimmed ANTHROPIC_API_KEY from `env`, or "" when unset/blank. */
32
+ /**
33
+ * NS RUN-3: the vault-sourced key cached by hydrateAnthropicApiKey (below),
34
+ * when ACC_RUNNER_ANTHROPIC_KEY_SOURCE=secret-store resolved one at startup.
35
+ * Deliberately NOT process.env — the ambient-key guard
36
+ * (scripts/ci/no-ambient-anthropic-key.mjs) is STRICT for this tree: zero
37
+ * literal `process.env.ANTHROPIC_API_KEY` reads OR writes, no allowlist. A
38
+ * module-level cache, consulted only through the parameterised
39
+ * anthropicApiKey() below, keeps every consumer (resolveAuthMode,
40
+ * withAnthropicAuth, authPrecedenceHint) on the same sanctioned resolution
41
+ * path without ever touching the ambient global.
42
+ */
43
+ let vaultAnthropicApiKey = null;
44
+ /** Test-only: clear the vault-sourced key cache between tests. */
45
+ export function __resetVaultAnthropicApiKeyForTests() {
46
+ vaultAnthropicApiKey = null;
47
+ }
48
+ /**
49
+ * RUNNER-SERVING-AUTH: seed the SAME module cache from a source other than
50
+ * the vault fetcher — specifically the org's serving-auth
51
+ * broker (`serving-auth/index.ts`), which hands back the sealed BYOK key when
52
+ * an admin has chosen api_key serving in Settings.
53
+ *
54
+ * This is deliberately a second door onto ONE cache rather than a second
55
+ * mechanism: every consumer (anthropicApiKey → resolveAuthMode →
56
+ * withAnthropicAuth) keeps resolving through the same sanctioned path, and the
57
+ * runner still never reads or writes `process.env.ANTHROPIC_API_KEY`, which the
58
+ * ambient-key guard (scripts/ci/no-ambient-anthropic-key.mjs) forbids outright
59
+ * in this tree.
60
+ *
61
+ * A blank/whitespace key is IGNORED rather than cached: caching "" would make
62
+ * `anthropicApiKey()` fall through to a stale earlier value, and clearing a key
63
+ * is not something this function is for (choose subscription for that — the
64
+ * kind, not the key, is what stops a spawn from being billed).
65
+ */
66
+ export function cacheServingApiKey(key) {
67
+ const trimmed = (key ?? "").trim();
68
+ if (trimmed)
69
+ vaultAnthropicApiKey = trimmed;
70
+ }
71
+ /**
72
+ * Trimmed ANTHROPIC_API_KEY from `env`, or the NS RUN-3 vault-sourced key
73
+ * (see hydrateAnthropicApiKey) when `env` doesn't carry one, or "" when
74
+ * neither is set. An explicit value in `env` always wins.
75
+ */
33
76
  export function anthropicApiKey(env = process.env) {
34
- return (env.ANTHROPIC_API_KEY ?? "").trim();
77
+ const fromEnv = (env.ANTHROPIC_API_KEY ?? "").trim();
78
+ return fromEnv || vaultAnthropicApiKey || "";
35
79
  }
36
80
  /**
37
81
  * Opt-in claude-config isolation. Even with ANTHROPIC_API_KEY
@@ -172,6 +216,66 @@ export function authPrecedenceHint(env = process.env) {
172
216
  return ("if you still hit 429 session caps, a claude.ai login is shadowing the API key — " +
173
217
  "run `claude /logout` or set ACC_RUNNER_ISOLATE_CLAUDE_CONFIG=1");
174
218
  }
219
+ export const ANTHROPIC_KEY_SOURCE_ENV = "ACC_RUNNER_ANTHROPIC_KEY_SOURCE";
220
+ /** Vault secret name the operator stores the Anthropic key under. */
221
+ export const ANTHROPIC_API_KEY_SECRET_NAME = "ANTHROPIC_API_KEY";
222
+ /**
223
+ * Resolve which source ANTHROPIC_API_KEY should come from. Defaults to
224
+ * "env" (today's behaviour, unchanged) so existing desktop runners and
225
+ * containers with the key baked into their launch env see no change — the
226
+ * secret-store path is opt-in via ACC_RUNNER_ANTHROPIC_KEY_SOURCE=secret-store.
227
+ */
228
+ export function resolveAnthropicKeySource(env = process.env) {
229
+ const v = (env[ANTHROPIC_KEY_SOURCE_ENV] ?? "").trim().toLowerCase();
230
+ return v === "secret-store" || v === "secret_store" || v === "vault" ? "secret-store" : "env";
231
+ }
232
+ /**
233
+ * Fetch ANTHROPIC_API_KEY from the vault via the supplied fetcher and trim
234
+ * it. Propagates the fetcher's `secret_unavailable: …` error verbatim on
235
+ * failure — never adds the plaintext to a message.
236
+ */
237
+ export async function anthropicApiKeyFromSecretStore(opts) {
238
+ const plaintext = await opts.fetcher(ANTHROPIC_API_KEY_SECRET_NAME, opts.agentId, opts.toolName);
239
+ return plaintext.trim();
240
+ }
241
+ /**
242
+ * Startup-time env hydration: when ACC_RUNNER_ANTHROPIC_KEY_SOURCE is
243
+ * "secret-store", resolves ANTHROPIC_API_KEY from the vault, caches it (see
244
+ * vaultAnthropicApiKey above) so anthropicApiKey()'s fallback picks it up for
245
+ * every later call, and returns `base` with it set too; otherwise returns
246
+ * `base` unchanged. An ANTHROPIC_API_KEY already present in `base` always
247
+ * wins and is never clobbered, so a local override for debugging still works
248
+ * even with the source flag flipped. Never mutates `base`.
249
+ *
250
+ * Wired into watch.ts's and companion-run.ts's startup paths (both call this
251
+ * ONCE at process startup, before the first withAnthropicAuth call / claude
252
+ * spawn, and rely on the module cache rather than assigning the result to
253
+ * process.env — see the ambient-key guard note above anthropicApiKey()).
254
+ */
255
+ export async function hydrateAnthropicApiKey(base = process.env, storeOpts) {
256
+ if (resolveAnthropicKeySource(base) !== "secret-store")
257
+ return base;
258
+ // Local-override check: base's OWN property, not the vault-cache fallback
259
+ // anthropicApiKey() also consults — an explicit env value always wins here,
260
+ // regardless of anything already cached from a prior hydration.
261
+ if ((base.ANTHROPIC_API_KEY ?? "").trim())
262
+ return base;
263
+ if (!storeOpts) {
264
+ throw new Error(`${ANTHROPIC_KEY_SOURCE_ENV}=secret-store requires a vault fetcher at startup (agentId + fetcher)`);
265
+ }
266
+ const key = await anthropicApiKeyFromSecretStore(storeOpts);
267
+ if (!key) {
268
+ throw new Error(`anthropic_secret_unavailable: ${ANTHROPIC_API_KEY_SECRET_NAME} resolved to an empty value`);
269
+ }
270
+ // NS RUN-3: cache it for anthropicApiKey()'s fallback so downstream
271
+ // consumers (withAnthropicAuth, resolveAuthMode, …) resolve the vault key
272
+ // WITHOUT the runner ever writing process.env.ANTHROPIC_API_KEY directly
273
+ // (see the module-cache doc comment above anthropicApiKey()). The returned
274
+ // env object still carries it explicitly too, for a caller that reads the
275
+ // return value directly.
276
+ vaultAnthropicApiKey = key;
277
+ return { ...base, ANTHROPIC_API_KEY: key };
278
+ }
175
279
  /**
176
280
  * The per-provider keychain account under the frozen `acc-runner` service.
177
281
  * FROZEN-CONTRACT EXTENSION (Track-A note): this ADDS a namespaced account
@@ -1 +1 @@
1
- {"version":3,"file":"provider-auth.js","sourceRoot":"","sources":["../src/provider-auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider-auth.js","sourceRoot":"","sources":["../src/provider-auth.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cache-dir.d.ts","sourceRoot":"","sources":["../../src/runtime/cache-dir.ts"],"names":[],"mappings":"AAmBA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM3E;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAMjC"}
1
+ {"version":3,"file":"cache-dir.d.ts","sourceRoot":"","sources":["../../src/runtime/cache-dir.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAU3E;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAMjC"}
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import os from "node:os";
18
18
  import path from "node:path";
19
+ import { resolveInstance } from "../instance.js";
19
20
  /**
20
21
  * The filesystem-safe slug that namespaces the default cache root. Returns
21
22
  * the sanitised ACC_RUNNER_ID when set, else "" (no namespacing). A minted
@@ -25,11 +26,15 @@ import path from "node:path";
25
26
  */
26
27
  export function cacheNamespace(env = process.env) {
27
28
  const id = env.ACC_RUNNER_ID?.trim();
28
- if (!id)
29
- return "";
30
29
  // Keep the id readable on disk but strip anything that could escape the
31
30
  // directory or trip a shell (/, .., spaces, etc.).
32
- return id.replace(/[^A-Za-z0-9._-]/g, "_");
31
+ if (id)
32
+ return id.replace(/[^A-Za-z0-9._-]/g, "_");
33
+ // NAMED-INSTANCES-1: a named instance namespaces the root too (isolated
34
+ // locks/worktrees/quarantine, no hand-assigned id/dir). ACC_RUNNER_ID still
35
+ // wins; unnamed stays "". Name is path-safe; `instance-` avoids id slugs.
36
+ const instance = resolveInstance(env);
37
+ return instance ? `instance-${instance}` : "";
33
38
  }
34
39
  export function cacheDir() {
35
40
  const override = process.env.ACC_RUNNER_CACHE_DIR?.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"cache-dir.js","sourceRoot":"","sources":["../../src/runtime/cache-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG;IACjE,MAAM,EAAE,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IACnB,wEAAwE;IACxE,mDAAmD;IACnD,OAAO,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"cache-dir.js","sourceRoot":"","sources":["../../src/runtime/cache-dir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG;IACjE,MAAM,EAAE,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IACrC,wEAAwE;IACxE,mDAAmD;IACnD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACnD,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,QAAQ,CAAC,CAAC,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * REVIEW-DECISION-PROVENANCE (runner half; mirrors
3
+ * api/_lib/reviewer/decision-provenance.ts, which carries the full rationale).
4
+ *
5
+ * MEASURED FAULT (PR #1913): `decision = "reject"` was persisted while every
6
+ * entry in `reasons[]` concluded "Met." — a full-pass analysis — with nothing on
7
+ * the row explaining the disagreement. That verdict was not asserted by the
8
+ * reviewer; the prose fallback in `extractReviewerDecision` SYNTHESIZED it from
9
+ * the first approve/reject keyword when the model answered in markdown, and left
10
+ * no trace. The contrast case (PR #1911) was a legitimate forced reject that
11
+ * named its rationale in `reasons[]`.
12
+ *
13
+ * INVARIANT: a forced or synthesized decision carries its justification in
14
+ * `reasons[]` plus a machine-readable marker naming the override source. The
15
+ * runner is the hot-path writer (acc.submit_review), so the guard runs here,
16
+ * immediately before the row is persisted.
17
+ *
18
+ * JUDGEMENT IS NEVER ALTERED — nothing here flips approve↔reject. `reasons[]` is
19
+ * the only channel (acc.review_queue stores `reasons text[]` and submit_review
20
+ * takes no extra argument), so the marker rides there in a parseable form, the
21
+ * same way the read-coverage telemetry line does.
22
+ *
23
+ * PURE. No I/O, no clock, no client.
24
+ */
25
+ /** Where a decision came from when it was not asserted by the analysis itself. */
26
+ export type DecisionSource = "prose_keyword" | "reconciled_contradiction";
27
+ export interface DecisionProvenance {
28
+ decision_source: DecisionSource;
29
+ override_reason: string;
30
+ }
31
+ /** Stable prefix for cheap row detection. */
32
+ export declare const DECISION_PROVENANCE_PREFIX = "decision_provenance:";
33
+ /** Rationale stamped when the prose fallback synthesizes the verdict. */
34
+ export declare const PROSE_KEYWORD_OVERRIDE_REASON: string;
35
+ /** Rationale stamped when a reject is contradicted by its own reasons. */
36
+ export declare const CONTRADICTION_OVERRIDE_REASON: string;
37
+ /** Render a provenance record as the reasons[] line that persists it. */
38
+ export declare function decisionProvenanceReason(p: DecisionProvenance): string;
39
+ /** True when a single reason IS the provenance marker (never a finding). */
40
+ export declare function isDecisionProvenanceReason(reason: string | null | undefined): boolean;
41
+ /** The first provenance marker carried by a verdict's reasons, or null. */
42
+ export declare function parseDecisionProvenance(reasons: readonly (string | null | undefined)[] | null | undefined): DecisionProvenance | null;
43
+ /**
44
+ * True when a verdict's reasons read as a FULL-PASS analysis: at least one
45
+ * criterion recorded as met and no defect language anywhere. Lines that are
46
+ * neither (telemetry such as the coverage line) are tolerated.
47
+ */
48
+ export declare function allCriteriaMet(reasons: readonly (string | null | undefined)[] | null | undefined): boolean;
49
+ /**
50
+ * The submit-seam guard. A reject whose reasons record a clean full pass and
51
+ * which carries no provenance marker gets one appended, so the #1913 shape can
52
+ * never be persisted unexplained. Every other verdict is returned untouched and
53
+ * the DECISION IS NEVER CHANGED. Idempotent.
54
+ */
55
+ export declare function reconcileReviewDecision<D extends string>(verdict: {
56
+ decision: D;
57
+ reasons?: readonly (string | null | undefined)[] | null;
58
+ }): {
59
+ decision: D;
60
+ reasons: string[];
61
+ provenance: DecisionProvenance | null;
62
+ };
63
+ //# sourceMappingURL=decision-provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-provenance.d.ts","sourceRoot":"","sources":["../../src/runtime/decision-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,kFAAkF;AAClF,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,0BAA0B,CAAC;AAE1E,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,cAAc,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AAKjE,yEAAyE;AACzE,eAAO,MAAM,6BAA6B,QAGe,CAAC;AAE1D,0EAA0E;AAC1E,eAAO,MAAM,6BAA6B,QAGK,CAAC;AAEhD,yEAAyE;AACzE,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAGtE;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAErF;AAED,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GACjE,kBAAkB,GAAG,IAAI,CAS3B;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GACjE,OAAO,CAST;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE;IACjE,QAAQ,EAAE,CAAC,CAAC;IACZ,OAAO,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,IAAI,CAAC;CACzD,GAAG;IAAE,QAAQ,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,CAiB5E"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * REVIEW-DECISION-PROVENANCE (runner half; mirrors
3
+ * api/_lib/reviewer/decision-provenance.ts, which carries the full rationale).
4
+ *
5
+ * MEASURED FAULT (PR #1913): `decision = "reject"` was persisted while every
6
+ * entry in `reasons[]` concluded "Met." — a full-pass analysis — with nothing on
7
+ * the row explaining the disagreement. That verdict was not asserted by the
8
+ * reviewer; the prose fallback in `extractReviewerDecision` SYNTHESIZED it from
9
+ * the first approve/reject keyword when the model answered in markdown, and left
10
+ * no trace. The contrast case (PR #1911) was a legitimate forced reject that
11
+ * named its rationale in `reasons[]`.
12
+ *
13
+ * INVARIANT: a forced or synthesized decision carries its justification in
14
+ * `reasons[]` plus a machine-readable marker naming the override source. The
15
+ * runner is the hot-path writer (acc.submit_review), so the guard runs here,
16
+ * immediately before the row is persisted.
17
+ *
18
+ * JUDGEMENT IS NEVER ALTERED — nothing here flips approve↔reject. `reasons[]` is
19
+ * the only channel (acc.review_queue stores `reasons text[]` and submit_review
20
+ * takes no extra argument), so the marker rides there in a parseable form, the
21
+ * same way the read-coverage telemetry line does.
22
+ *
23
+ * PURE. No I/O, no clock, no client.
24
+ */
25
+ /** Stable prefix for cheap row detection. */
26
+ export const DECISION_PROVENANCE_PREFIX = "decision_provenance:";
27
+ const PROVENANCE_RE = /^decision_provenance:\s*decision_source=(\S+)\s+override_reason=([\s\S]*)$/i;
28
+ /** Rationale stamped when the prose fallback synthesizes the verdict. */
29
+ export const PROSE_KEYWORD_OVERRIDE_REASON = "the reviewer answered in prose instead of the structured verdict object, so " +
30
+ "this decision was synthesized by the parser from the first approve/reject " +
31
+ "keyword in the text rather than asserted as a verdict";
32
+ /** Rationale stamped when a reject is contradicted by its own reasons. */
33
+ export const CONTRADICTION_OVERRIDE_REASON = "the analysis recorded every acceptance criterion as met, yet the verdict is " +
34
+ "reject and the row states no override rationale — the verdict is kept as " +
35
+ "submitted and flagged here for the operator";
36
+ /** Render a provenance record as the reasons[] line that persists it. */
37
+ export function decisionProvenanceReason(p) {
38
+ const rationale = p.override_reason.replace(/\s+/g, " ").trim() || "(unstated)";
39
+ return `${DECISION_PROVENANCE_PREFIX} decision_source=${p.decision_source} override_reason=${rationale}`;
40
+ }
41
+ /** True when a single reason IS the provenance marker (never a finding). */
42
+ export function isDecisionProvenanceReason(reason) {
43
+ return typeof reason === "string" && PROVENANCE_RE.test(reason.trim());
44
+ }
45
+ /** The first provenance marker carried by a verdict's reasons, or null. */
46
+ export function parseDecisionProvenance(reasons) {
47
+ for (const raw of reasons ?? []) {
48
+ if (typeof raw !== "string")
49
+ continue;
50
+ const m = PROVENANCE_RE.exec(raw.trim());
51
+ if (m) {
52
+ return { decision_source: m[1], override_reason: m[2].trim() };
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /** Severity tag the server-side calibrator stamps on stored findings. */
58
+ const SEVERITY_TAG_RE = /^\s*\[(?:blocking|advisory)\]\s*/i;
59
+ /** A finding that concludes the criterion it names was satisfied. */
60
+ const MET_RE = /\bmet\b\s*[.)\]]?\s*$/i;
61
+ /**
62
+ * Defect / non-satisfaction language. Present anywhere ⇒ the analysis is not a
63
+ * clean full pass and the guard stands down — deliberately biased toward NOT
64
+ * stamping, since a missed stamp is an audit gap while a wrong one would
65
+ * editorialize a real reject.
66
+ */
67
+ const DEFECT_RE = /\b(?:not\s+met|unmet|partially\s+met|fail(?:s|ed|ing|ure)?|missing|absent|violat\w*|blocking|defect|regression|broken|incorrect|unsafe|does\s+not|is\s+not|are\s+not|was\s+not|cannot|can['’]?t)\b/i;
68
+ /**
69
+ * True when a verdict's reasons read as a FULL-PASS analysis: at least one
70
+ * criterion recorded as met and no defect language anywhere. Lines that are
71
+ * neither (telemetry such as the coverage line) are tolerated.
72
+ */
73
+ export function allCriteriaMet(reasons) {
74
+ const findings = (reasons ?? [])
75
+ .filter((r) => typeof r === "string")
76
+ .filter((r) => !isDecisionProvenanceReason(r))
77
+ .map((r) => r.replace(SEVERITY_TAG_RE, "").trim())
78
+ .filter((r) => r.length > 0);
79
+ if (findings.length === 0)
80
+ return false;
81
+ if (findings.some((r) => DEFECT_RE.test(r)))
82
+ return false;
83
+ return findings.some((r) => MET_RE.test(r));
84
+ }
85
+ /**
86
+ * The submit-seam guard. A reject whose reasons record a clean full pass and
87
+ * which carries no provenance marker gets one appended, so the #1913 shape can
88
+ * never be persisted unexplained. Every other verdict is returned untouched and
89
+ * the DECISION IS NEVER CHANGED. Idempotent.
90
+ */
91
+ export function reconcileReviewDecision(verdict) {
92
+ const reasons = (verdict.reasons ?? []).filter((r) => typeof r === "string");
93
+ const existing = parseDecisionProvenance(reasons);
94
+ if (verdict.decision !== "reject" || existing !== null || !allCriteriaMet(reasons)) {
95
+ return { decision: verdict.decision, reasons, provenance: existing };
96
+ }
97
+ const provenance = {
98
+ decision_source: "reconciled_contradiction",
99
+ override_reason: CONTRADICTION_OVERRIDE_REASON,
100
+ };
101
+ return {
102
+ decision: verdict.decision,
103
+ reasons: [...reasons, decisionProvenanceReason(provenance)],
104
+ provenance,
105
+ };
106
+ }
107
+ //# sourceMappingURL=decision-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-provenance.js","sourceRoot":"","sources":["../../src/runtime/decision-provenance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * S15-FU/REVIEW-1 (AC1) — literal-safe reviewer-prompt placeholder substitution.
3
+ *
4
+ * THE FAULT THIS CLOSES (the LEDGER-1 #1933 episode)
5
+ * ----------------------------------------------------------------------------
6
+ * `renderReviewerPrompt` interpolated the PR diff into the reviewer prompt with
7
+ *
8
+ * template.replace("{{pr_diff}}", diff)
9
+ *
10
+ * `String.prototype.replace` treats a STRING replacement as a pattern: `$$`,
11
+ * `$&`, `` $` ``, `$'` and `$1`…`$9` are expanded, not copied. So a diff whose
12
+ * text contains `$'` — which every one of these clean SQL literals does —
13
+ *
14
+ * select seq, slug, sha from acc.applied_migrations where seq ~ '^[0-9]+$' order by 1
15
+ * ^^ $'
16
+ *
17
+ * made `replace` splice in "the portion of the template AFTER the match": the
18
+ * reviewer's OWN instruction text, starting at
19
+ * "Now answer in this exact JSON shape, with no surrounding prose:". The
20
+ * reviewer then read that prose INSIDE the SQL literal and — correctly, given
21
+ * what it was shown — reported "prose spliced into the literal" as a corruption
22
+ * defect, at 0.75–0.85 confidence, across several independent sessions. The
23
+ * diff on disk was clean the whole time; the harness corrupted it on the way in.
24
+ *
25
+ * (Same root cause as the 3× `gatherAppliedLedger` corruption in
26
+ * `scripts/ci/migration-lint.mjs`: `$'` expansion in a string replacement, not
27
+ * context bleed.)
28
+ *
29
+ * THE RULE: NEVER pass untrusted text as `replace`'s string replacement. A
30
+ * FUNCTION replacement is copied verbatim — `$` has no meaning in its return
31
+ * value — so `substitutePlaceholder` below is the only sanctioned way to put a
32
+ * diff, PR body, title, description, or acceptance criterion into a prompt.
33
+ *
34
+ * Mirrored, byte-for-byte in behaviour, from
35
+ * `api/_lib/reviewer/prompt-substitute.ts` (the server path). The runner ships as
36
+ * a published npm package and cannot import from `api/_lib`, so the module is
37
+ * duplicated and pinned by a cross-implementation parity test.
38
+ * // keep in sync with api/_lib/reviewer/prompt-substitute.ts
39
+ */
40
+ /**
41
+ * The `$`-sequences `String.prototype.replace` expands inside a STRING
42
+ * replacement. Documented here because the whole module exists to defeat them.
43
+ *
44
+ * `$$` → a literal `$`
45
+ * `$&` → the matched substring
46
+ * ``$` `` → everything BEFORE the match
47
+ * `$'` → everything AFTER the match ← the #1933 corruption
48
+ * `$n` → capture group n
49
+ */
50
+ export declare const REPLACEMENT_EXPANSION_SEQUENCES: readonly ["$$", "$&", "$`", "$'"];
51
+ /**
52
+ * True when `value` contains a sequence that `String.prototype.replace` would
53
+ * expand if it were used as a string replacement. Diagnostic only — the
54
+ * substitution below is safe either way — but it lets the render seam log that a
55
+ * value was in the dangerous class without ever emitting the value itself.
56
+ */
57
+ export declare function hasReplacementExpansion(value: string | null | undefined): boolean;
58
+ /**
59
+ * Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
60
+ *
61
+ * The replacement is a function, so no `$`-sequence in `value` is expanded and
62
+ * no template text can bleed into it. An absent placeholder is a no-op (the
63
+ * template is returned unchanged) — same as the string form it replaces.
64
+ */
65
+ export declare function substitutePlaceholder(template: string, placeholder: string, value: string | null | undefined): string;
66
+ /**
67
+ * Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
68
+ * that each pass sees the output of the previous one; because values are
69
+ * inserted verbatim, a value that happens to contain a later placeholder's
70
+ * literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
71
+ * away — the later pass still matches the TEMPLATE's own placeholder first,
72
+ * since it appears earlier in the string only if the template put it there.
73
+ */
74
+ export declare function substitutePlaceholders(template: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): string;
75
+ /** One value that did not survive substitution verbatim. */
76
+ export interface PromptRenderBleed {
77
+ /** The placeholder whose value was corrupted (e.g. `{{pr_diff}}`). */
78
+ placeholder: string;
79
+ /** Length of the value we expected to find verbatim (never the value itself). */
80
+ expected_chars: number;
81
+ }
82
+ /**
83
+ * INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
84
+ * ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
85
+ * means the render mangled file content — the #1933 class — and the review must
86
+ * NOT proceed on that prompt (the reviewer would be judging text the repo does
87
+ * not contain).
88
+ *
89
+ * `template` is required precisely so a placeholder the template OMITS is not
90
+ * reported: a prompt that never asks for the PR body was not corrupted by the
91
+ * body's absence, it simply does not carry it. Only a value the template asked
92
+ * for and did not receive verbatim is a bleed.
93
+ *
94
+ * Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
95
+ * the result, so a bleed report is safe to log.
96
+ */
97
+ export declare function findPromptRenderBleed(template: string, rendered: string, pairs: ReadonlyArray<readonly [string, string | null | undefined]>): PromptRenderBleed[];
98
+ /** Operator-facing one-liner naming the corrupted placeholders (never the values). */
99
+ export declare function describePromptRenderBleed(bleeds: PromptRenderBleed[]): string;
100
+ //# sourceMappingURL=prompt-substitute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-substitute.d.ts","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,mCAAoC,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGjF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,MAAM,CAMR;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACjE,iBAAiB,EAAE,CAarB;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAI7E"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * S15-FU/REVIEW-1 (AC1) — literal-safe reviewer-prompt placeholder substitution.
3
+ *
4
+ * THE FAULT THIS CLOSES (the LEDGER-1 #1933 episode)
5
+ * ----------------------------------------------------------------------------
6
+ * `renderReviewerPrompt` interpolated the PR diff into the reviewer prompt with
7
+ *
8
+ * template.replace("{{pr_diff}}", diff)
9
+ *
10
+ * `String.prototype.replace` treats a STRING replacement as a pattern: `$$`,
11
+ * `$&`, `` $` ``, `$'` and `$1`…`$9` are expanded, not copied. So a diff whose
12
+ * text contains `$'` — which every one of these clean SQL literals does —
13
+ *
14
+ * select seq, slug, sha from acc.applied_migrations where seq ~ '^[0-9]+$' order by 1
15
+ * ^^ $'
16
+ *
17
+ * made `replace` splice in "the portion of the template AFTER the match": the
18
+ * reviewer's OWN instruction text, starting at
19
+ * "Now answer in this exact JSON shape, with no surrounding prose:". The
20
+ * reviewer then read that prose INSIDE the SQL literal and — correctly, given
21
+ * what it was shown — reported "prose spliced into the literal" as a corruption
22
+ * defect, at 0.75–0.85 confidence, across several independent sessions. The
23
+ * diff on disk was clean the whole time; the harness corrupted it on the way in.
24
+ *
25
+ * (Same root cause as the 3× `gatherAppliedLedger` corruption in
26
+ * `scripts/ci/migration-lint.mjs`: `$'` expansion in a string replacement, not
27
+ * context bleed.)
28
+ *
29
+ * THE RULE: NEVER pass untrusted text as `replace`'s string replacement. A
30
+ * FUNCTION replacement is copied verbatim — `$` has no meaning in its return
31
+ * value — so `substitutePlaceholder` below is the only sanctioned way to put a
32
+ * diff, PR body, title, description, or acceptance criterion into a prompt.
33
+ *
34
+ * Mirrored, byte-for-byte in behaviour, from
35
+ * `api/_lib/reviewer/prompt-substitute.ts` (the server path). The runner ships as
36
+ * a published npm package and cannot import from `api/_lib`, so the module is
37
+ * duplicated and pinned by a cross-implementation parity test.
38
+ * // keep in sync with api/_lib/reviewer/prompt-substitute.ts
39
+ */
40
+ /**
41
+ * The `$`-sequences `String.prototype.replace` expands inside a STRING
42
+ * replacement. Documented here because the whole module exists to defeat them.
43
+ *
44
+ * `$$` → a literal `$`
45
+ * `$&` → the matched substring
46
+ * ``$` `` → everything BEFORE the match
47
+ * `$'` → everything AFTER the match ← the #1933 corruption
48
+ * `$n` → capture group n
49
+ */
50
+ export const REPLACEMENT_EXPANSION_SEQUENCES = ["$$", "$&", "$`", "$'"];
51
+ /**
52
+ * True when `value` contains a sequence that `String.prototype.replace` would
53
+ * expand if it were used as a string replacement. Diagnostic only — the
54
+ * substitution below is safe either way — but it lets the render seam log that a
55
+ * value was in the dangerous class without ever emitting the value itself.
56
+ */
57
+ export function hasReplacementExpansion(value) {
58
+ const text = value ?? "";
59
+ return /\$[$&`'\d]/.test(text);
60
+ }
61
+ /**
62
+ * Substitute the FIRST occurrence of `placeholder` with `value`, VERBATIM.
63
+ *
64
+ * The replacement is a function, so no `$`-sequence in `value` is expanded and
65
+ * no template text can bleed into it. An absent placeholder is a no-op (the
66
+ * template is returned unchanged) — same as the string form it replaces.
67
+ */
68
+ export function substitutePlaceholder(template, placeholder, value) {
69
+ const text = value ?? "";
70
+ return (template ?? "").replace(placeholder, () => text);
71
+ }
72
+ /**
73
+ * Apply an ORDERED list of `[placeholder, value]` pairs. Order matters only in
74
+ * that each pass sees the output of the previous one; because values are
75
+ * inserted verbatim, a value that happens to contain a later placeholder's
76
+ * literal text (`"{{pr_diff}}"` inside a PR body, say) is NOT re-substituted
77
+ * away — the later pass still matches the TEMPLATE's own placeholder first,
78
+ * since it appears earlier in the string only if the template put it there.
79
+ */
80
+ export function substitutePlaceholders(template, pairs) {
81
+ let out = template ?? "";
82
+ for (const [placeholder, value] of pairs) {
83
+ out = substitutePlaceholder(out, placeholder, value);
84
+ }
85
+ return out;
86
+ }
87
+ /**
88
+ * INTEGRITY CHECK: every value substituted for a placeholder THE TEMPLATE
89
+ * ACTUALLY CONTAINS must appear VERBATIM in the rendered output. Anything else
90
+ * means the render mangled file content — the #1933 class — and the review must
91
+ * NOT proceed on that prompt (the reviewer would be judging text the repo does
92
+ * not contain).
93
+ *
94
+ * `template` is required precisely so a placeholder the template OMITS is not
95
+ * reported: a prompt that never asks for the PR body was not corrupted by the
96
+ * body's absence, it simply does not carry it. Only a value the template asked
97
+ * for and did not receive verbatim is a bleed.
98
+ *
99
+ * Pure and allocation-cheap (`includes`, no regex). Values are never echoed into
100
+ * the result, so a bleed report is safe to log.
101
+ */
102
+ export function findPromptRenderBleed(template, rendered, pairs) {
103
+ const out = [];
104
+ const tpl = template ?? "";
105
+ const text = rendered ?? "";
106
+ for (const [placeholder, value] of pairs) {
107
+ const expected = value ?? "";
108
+ if (expected.length === 0)
109
+ continue;
110
+ if (!tpl.includes(placeholder))
111
+ continue;
112
+ if (!text.includes(expected)) {
113
+ out.push({ placeholder, expected_chars: expected.length });
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+ /** Operator-facing one-liner naming the corrupted placeholders (never the values). */
119
+ export function describePromptRenderBleed(bleeds) {
120
+ return bleeds
121
+ .map((b) => `${b.placeholder} (${b.expected_chars} chars did not survive verbatim)`)
122
+ .join("; ");
123
+ }
124
+ //# sourceMappingURL=prompt-substitute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-substitute.js","sourceRoot":"","sources":["../../src/runtime/prompt-substitute.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAgC;IACtE,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,WAAmB,EACnB,KAAgC;IAEhC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;IACzB,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAgB,EAChB,KAAkE;IAElE,IAAI,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAUD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAgB,EAChB,QAAgB,EAChB,KAAkE;IAElE,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,IAAI,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAS;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,yBAAyB,CAAC,MAA2B;IACnE,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,cAAc,kCAAkC,CAAC;SACnF,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}