@warmdrift/kgauto-compiler 2.0.0-alpha.7 → 2.0.0-alpha.71

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 (55) hide show
  1. package/README.md +176 -46
  2. package/dist/brain-proxy.d.mts +113 -0
  3. package/dist/brain-proxy.d.ts +113 -0
  4. package/dist/brain-proxy.js +193 -0
  5. package/dist/brain-proxy.mjs +6 -0
  6. package/dist/chunk-4UO4CCSP.mjs +1620 -0
  7. package/dist/chunk-65ZMX5OT.mjs +169 -0
  8. package/dist/{chunk-5TI6PNSK.mjs → chunk-BVEXV5KC.mjs} +11 -0
  9. package/dist/chunk-NBO4R5PC.mjs +313 -0
  10. package/dist/chunk-P3TOAEG4.mjs +56 -0
  11. package/dist/chunk-RO22VFIF.mjs +29 -0
  12. package/dist/chunk-SBFSYCQG.mjs +719 -0
  13. package/dist/chunk-URFQR3SB.mjs +203 -0
  14. package/dist/dialect.d.mts +41 -3
  15. package/dist/dialect.d.ts +41 -3
  16. package/dist/dialect.js +14 -2
  17. package/dist/dialect.mjs +5 -3
  18. package/dist/glassbox/index.d.mts +59 -0
  19. package/dist/glassbox/index.d.ts +59 -0
  20. package/dist/glassbox/index.js +312 -0
  21. package/dist/glassbox/index.mjs +12 -0
  22. package/dist/glassbox-routes/format.d.mts +24 -0
  23. package/dist/glassbox-routes/format.d.ts +24 -0
  24. package/dist/glassbox-routes/format.js +86 -0
  25. package/dist/glassbox-routes/format.mjs +18 -0
  26. package/dist/glassbox-routes/index.d.mts +191 -0
  27. package/dist/glassbox-routes/index.d.ts +191 -0
  28. package/dist/glassbox-routes/index.js +2888 -0
  29. package/dist/glassbox-routes/index.mjs +667 -0
  30. package/dist/glassbox-routes/react/index.d.mts +74 -0
  31. package/dist/glassbox-routes/react/index.d.ts +74 -0
  32. package/dist/glassbox-routes/react/index.js +819 -0
  33. package/dist/glassbox-routes/react/index.mjs +754 -0
  34. package/dist/index.d.mts +2745 -17
  35. package/dist/index.d.ts +2745 -17
  36. package/dist/index.js +9006 -1651
  37. package/dist/index.mjs +5103 -367
  38. package/dist/ir-BEQ28muo.d.ts +1608 -0
  39. package/dist/ir-CnnJST_N.d.mts +1608 -0
  40. package/dist/key-health.d.mts +131 -0
  41. package/dist/key-health.d.ts +131 -0
  42. package/dist/key-health.js +228 -0
  43. package/dist/key-health.mjs +6 -0
  44. package/dist/profiles.d.mts +292 -2
  45. package/dist/profiles.d.ts +292 -2
  46. package/dist/profiles.js +1231 -16
  47. package/dist/profiles.mjs +9 -1
  48. package/dist/types-B--CYzMo.d.ts +131 -0
  49. package/dist/types-BDFrJkma.d.mts +131 -0
  50. package/dist/types-DSeJJ6tt.d.ts +149 -0
  51. package/dist/types-DeGRCTlJ.d.mts +149 -0
  52. package/package.json +54 -8
  53. package/dist/chunk-MBEI5UOM.mjs +0 -409
  54. package/dist/profiles-B3eNQ2py.d.ts +0 -619
  55. package/dist/profiles-Py8c7zjJ.d.mts +0 -619
package/README.md CHANGED
@@ -1,28 +1,49 @@
1
- # @warmdrift/kgauto-compiler — v2.0.0-alpha.6
1
+ # @warmdrift/kgauto-compiler — v2 (alpha)
2
2
 
3
- > Prompt compiler + central learning brain for multi-model AI apps.
3
+ > Prompt compiler with executable provider knowledge for multi-model AI apps.
4
4
  > **Swap models without rewriting prompts.**
5
5
 
6
- Greenfield rewrite of `@warmdrift/kgauto` v1. v1 was a behavioral patcher
7
- with telemetry; v2 is a real prompt compiler with a self-improving learning
8
- layer designed for cross-app pollination.
9
-
10
- The "compiler" name is deliberate every optimization is a pass on a
11
- structured Intermediate Representation (IR), not string surgery on a
12
- rendered prompt. This unlocks slicing, dedupe, intent-aware tool relevance,
13
- target-correct lowering with cache markers, and (in v2.1) outcome-driven
14
- mutations.
6
+ What this package delivers today, and nothing it doesn't:
7
+
8
+ - **A prompt compiler** — every optimization is a pass on a structured
9
+ Intermediate Representation (IR), not string surgery on a rendered prompt:
10
+ slicing, dedupe, intent-aware tool relevance, history compression,
11
+ target-correct lowering with cache markers.
12
+ - **Executable provider knowledge** model cliffs, lowering rules, and
13
+ recovery handlers as code that fires at compile time, curated against
14
+ provider docs with grounding labels. Seven-plus frontier model transitions
15
+ absorbed centrally with zero consumer prompt rewrites.
16
+ - **Normalized multi-provider transport** — `call()` walks a fallback chain
17
+ across Anthropic / Google / OpenAI / DeepSeek with a provider-error
18
+ taxonomy (429 / 404 / 401-auth-walk / parse-fail / MAX_TOKENS), recompiling
19
+ per fallback target. (Scope: the `call()` path. Streaming consumers drive
20
+ their own wire and get compile-time guards, not transport resilience.)
21
+ - **A flight recorder** — opt-in telemetry (`record()` / `probeShadow()` /
22
+ key-health) that has repeatedly caught real production defects: empty-response
23
+ cliffs, dead API keys, cache-miss regressions, silent model deprecations.
24
+ - **A curated model registry** — capability + pricing data with verification
25
+ stamps, a release watcher, and price-truth test gates.
26
+
27
+ **What it does not do (yet):** routing does not change itself from measured
28
+ outcomes. There is no automatic learning loop in the shipped package — model
29
+ promotion is evidence-*informed* but operator-*driven*. What DOES ship as of
30
+ alpha.62 is the eval spine (Stage 1 of that architecture): golden-set capture,
31
+ a pairwise judge, and a non-inferiority eval whose verdict lands as evidence
32
+ on the advisory surface — the eval runs, a human clicks promote. Automatic
33
+ promotion (Stages 2–3) returns to this README when it is code, not before.
34
+
35
+ Greenfield rewrite of `@warmdrift/kgauto` v1 (a behavioral patcher with
36
+ telemetry).
15
37
 
16
38
  ## Status
17
39
 
18
- - **Package:** alpha — coexists with v1 (`@warmdrift/kgauto@1.2.0`) under
19
- the temporary name `@warmdrift/kgauto-compiler`. Renames to v2 final once
20
- v1 is fully retired from production.
21
- - **Tests:** 201/201 passing
22
- - **Build:** clean (47KB ESM, 68KB CJS)
23
- - **Brain:** schema ready (see `brain/migrations/001_initial_schema.sql`);
24
- awaiting dedicated Supabase provisioning.
25
- - **Mutation engine:** v2.1 (after enough outcome data accumulates).
40
+ - **Package:** alpha — coexists with v1 (`@warmdrift/kgauto`) under the
41
+ temporary name `@warmdrift/kgauto-compiler`. Renames to v2 final once v1
42
+ is fully retired from production.
43
+ - **Tests:** 1400+ passing (see CI for the current count)
44
+ - **Brain:** live multi-tenant Supabase substrate (telemetry + curated
45
+ config tables), consumed by four production apps.
46
+ - **Learning engine:** not shipped. See "What's next."
26
47
 
27
48
  ## Quickstart
28
49
 
@@ -89,6 +110,101 @@ await record({
89
110
  });
90
111
  ```
91
112
 
113
+ ### `probeShadow()` — measure a swap candidate from an AI-SDK route (alpha.48)
114
+
115
+ `call()` runs a full-IR shadow probe internally (`shadowProbe` option). AI-SDK consumers who drive `streamText()` themselves never call `call()`, so they reach the same probe via the standalone `probeShadow()` export — fire it from `onFinish`, passing the served leg you already have, wrapped in `waitUntil()`/`after()` so it never blocks the response.
116
+
117
+ ```ts
118
+ import { probeShadow, configureBrain } from '@warmdrift/kgauto-compiler';
119
+ import { after } from 'next/server'; // or the platform's waitUntil()
120
+
121
+ configureBrain({ endpoint: 'https://your-app.com/api/kgauto/v2', apiKey: '...' });
122
+
123
+ const t0 = Date.now();
124
+ const result = streamText({
125
+ model, system, messages,
126
+ onFinish: ({ text, usage }) => {
127
+ after(() => probeShadow(ir, {
128
+ served: {
129
+ model: servedModelId,
130
+ responseText: text,
131
+ tokensIn: usage.promptTokens,
132
+ tokensOut: usage.completionTokens,
133
+ latencyMs: Date.now() - t0,
134
+ },
135
+ candidates: ['deepseek-v4-pro'], // what you'd consider swapping to
136
+ sampleRate: 0.05, // pin 1 for a dogfood window
137
+ }));
138
+ },
139
+ });
140
+ ```
141
+
142
+ kgauto runs each candidate on the **same IR** and persists a `probe_outcomes` row (`replay_source='inline-full-ir'`, `prompt_fidelity=1.0`, both latency legs). Phase 1 is `judge:'off'` — rows accumulate for an offline verdict rollup; inline Opus judging is Phase 2. Fire-and-forget: never throws into your `onFinish`.
143
+
144
+ #### Sync-mode latency protection (`call()` + `BrainConfig.sync`)
145
+
146
+ A `probeShadow()` consumer fires from `after()`, so the probe is always off the user-facing path — slow candidates are fine there (it's the right place to evaluate a slow reasoner). But a **`call()`-inline** consumer that sets `BrainConfig.sync: true` (PB-class Edge routes, so the brain write reliably lands before teardown — L-086) *awaits* the probe, putting the candidate's latency on the user's critical path. A `latency_tier='slow'` candidate (deepseek-v4-pro ~78–84s) then blows the route's time budget and the user sees an empty response. Two knobs on `shadowProbe` protect the sync path — **both are no-ops off the critical path** (non-sync `call()` and every `probeShadow()` consumer):
147
+
148
+ ```ts
149
+ await call(ir, {
150
+ shadowProbe: {
151
+ candidates: ['deepseek-v4-pro'],
152
+ sampleRate: 1,
153
+ maxLatencyMs: 15000, // default 15000. Sync-mode budget: if the probe
154
+ // overruns, abort the candidate, return the served
155
+ // response NOW, record outcome='aborted_latency_budget'.
156
+ skipSlowTierInSync: true, // default true. Don't even START a latency_tier='slow'
157
+ // candidate inline; record outcome='skipped_slow_tier_sync'.
158
+ },
159
+ });
160
+ ```
161
+
162
+ Aborted/skipped probes write a **diagnostic** `probe_outcomes` row (`outcome != 'completed'`, no candidate response, no verdict) so the offline rollup can tell "tried but too slow" from "never armed." A verdict rollup must filter `outcome='completed'`.
163
+
164
+ **The deeper rule: inline shadow-probe = FAST candidates only.** To evaluate a *slow* reasoner, use the offline/async probe path (the Phase-2 batch direction, off the response entirely) or `probeShadow()` from `after()` — never the inline sync probe.
165
+
166
+ ## Structured advisories API (alpha.29+)
167
+
168
+ kgauto's compile-time advisories (`caching-off-on-claude`, `tool-bloat`,
169
+ `archetype-perf-floor-breach`, etc.) used to vanish after the consumer read
170
+ the compile result. alpha.29 ships a structured query + resolution surface
171
+ so Admin UIs can render "what's open right now?" without log-scraping.
172
+
173
+ ```ts
174
+ import {
175
+ getActionableAdvisories,
176
+ markAdvisoryResolved,
177
+ } from '@warmdrift/kgauto-compiler';
178
+
179
+ // Query open advisories for this app
180
+ const open = await getActionableAdvisories({
181
+ appId: 'playbacksam',
182
+ brainEndpoint: process.env.KGAUTO_BRAIN_ENDPOINT!,
183
+ brainJwt: process.env.KGAUTO_BRAIN_JWT!,
184
+ brainAnonKey: process.env.KGAUTO_BRAIN_ANON_KEY!,
185
+ });
186
+
187
+ for (const a of open) {
188
+ console.log(`[${a.severity}] ${a.rule} — ${a.observationCount} firings since ${a.openedAt}`);
189
+ console.log(` ${a.message}`);
190
+ if (a.suggestedFix?.docsLink) console.log(` ↳ ${a.suggestedFix.docsLink}`);
191
+ }
192
+
193
+ // After the operator fixes the issue, mark it resolved:
194
+ const r = await markAdvisoryResolved({
195
+ id: open[0].id,
196
+ resolutionNote: 'wired historyCachePolicy in compile() — PR #99',
197
+ brainEndpoint: process.env.KGAUTO_BRAIN_ENDPOINT!,
198
+ brainJwt: process.env.KGAUTO_BRAIN_JWT!,
199
+ brainAnonKey: process.env.KGAUTO_BRAIN_ANON_KEY!,
200
+ });
201
+ if (!r.ok) console.error(r.reason);
202
+ ```
203
+
204
+ The view enforces server-side auto-resolution: advisories with no firing
205
+ in the last 14 days flip to `status='resolved'` automatically; the next
206
+ firing reopens the rule with a fresh stable `id`.
207
+
92
208
  ## Architecture
93
209
 
94
210
  ```
@@ -108,10 +224,10 @@ APP (any consumer)
108
224
  └── kg.record(handle, outcome) ── async POST to brain
109
225
 
110
226
  BRAIN (centralized Supabase)
111
- ├── compile_outcomes (multi-tenant from day 1)
112
- ├── mutations (active rules empty in v2.0; engine in v2.1)
113
- ├── apps (consumer registry)
114
- └── digest_runs (weekly summary audit trail)
227
+ ├── compile_outcomes (multi-tenant telemetry)
228
+ ├── probe_outcomes / compile_outcome_quality (shadow-probe + quality rows)
229
+ ├── kgauto_models / kgauto_chains / kgauto_pricing (curated config, operator-maintained)
230
+ └── apps (consumer registry)
115
231
  ```
116
232
 
117
233
  ## Dialect-v1 (cross-app vocabulary)
@@ -121,12 +237,10 @@ Apps tag every call with an **intent archetype** (`ask`, `hunt`, `classify`,
121
237
  compiler computes a **shape signature** (context bucket × tool count × history
122
238
  depth × output mode × examples flag).
123
239
 
124
- The `(archetype, model, shape)` tuple is the **learning key**. Apps that
125
- declare the same tuple inherit each other's mutations even apps that have
126
- never seen each other's data.
127
-
128
- That's how *"what works for the dashboard, should be insights for the next
129
- dashboard"* becomes mechanical instead of aspirational.
240
+ The `(archetype, model, shape)` tuple is the **learning key** the shared
241
+ grouping vocabulary for telemetry aggregation across apps. It is the
242
+ substrate a future cross-app learning engine would key on; today it powers
243
+ per-tuple cost/latency observability, nothing more.
130
244
 
131
245
  ## Profiles — executable model knowledge
132
246
 
@@ -211,29 +325,45 @@ For staging without a dedicated brain, point consumers at the same Supabase
211
325
  they already use — the schema is identical and migration to a dedicated brain
212
326
  is a `pg_dump` away.
213
327
 
328
+ ## Golden-set eval (alpha.62 — Stage 1, shipped)
329
+
330
+ The eval-driven quality-floor-guarded swap architecture (specced 2026-07-17
331
+ after an adversarial promise review) began shipping in alpha.62:
332
+
333
+ - **Capture:** set `KGAUTO_GOLDEN_CAPTURE=0.1` on a consumer and successful
334
+ `call()`s sample their full IR + served output into a per-surface replay
335
+ corpus. Explicit opt-in — golden rows carry raw prompts (documented posture
336
+ change; consent provenance on every row; reads strictly app-scoped). Or
337
+ hand-curate with `scripts/seed-golden-set.mjs`.
338
+ - **Eval:** `scripts/run-golden-eval.mjs --app X --archetype Y --candidate M`
339
+ replays the corpus on incumbent AND candidate (fresh outputs both sides),
340
+ applies mechanical hard floors (empty / schema / candidate-error / latency),
341
+ and runs an order-swapped pairwise judge (`judge` archetype, per-archetype
342
+ rubrics). Non-inferiority rule: wins-or-ties ≥ 80% + zero floor violations
343
+ → `promote-ready`.
344
+ - **Evidence:** every run lands as brain rows (`kgauto_golden_eval_runs` +
345
+ per-case `probe_outcomes` `golden-replay` rows) and a `golden-eval-verdict`
346
+ advisory on the consumer's advisory surface.
347
+
214
348
  ## What's next
215
349
 
216
- - **v2.0.x:** real-app integrations (tt-intelligence, inspire-central,
217
- playbacksam, inspirato/incantato). Brain accumulates outcome data.
218
- - **v2.1:** mutation engine. Shadow-test statistical gate promote → auto-rollback.
219
- - **v2.2:** weekly digest reporting back to the operator.
220
- - **v2.x:** dialect-v2 expanded with archetypes that emerge from real usage.
350
+ Stages 2–3 of the trust ladder (auto-promote downswaps behind a 7-day
351
+ rollback guard, then upswaps) and trigger wiring (model-release-watch /
352
+ price-truth-watch / live-telemetry driftauto-eval), then the same harness
353
+ pointed at compile variants (Tier D). Each stage lands in this README **when
354
+ it ships**, with its gate evidence the promise follows the code.
221
355
 
222
356
  ## Why this exists
223
357
 
224
358
  The previous version (v1) treated prompts as opaque strings and could only
225
- *append* behavioral patches. It also tried to learn quality from structural
226
- signals (token counts) but quality is semantic, not structural.
227
-
228
- v2 treats prompts as structured IR, makes every model-specific quirk
229
- *executable* (cliffs, lowering, recovery), and makes oracle scoring a
230
- first-class contract so the brain learns from quality data, not its proxies.
231
-
232
- The whole point: every multi-model AI app needs a compiler. Building it
233
- inline ships one app's value. Building it portable with a shared brain
234
- ships every app's value to every other app.
235
-
236
- Communicating vessels — finally accurate to the name.
359
+ *append* behavioral patches. v2 treats prompts as structured IR and makes
360
+ every model-specific quirk *executable* (cliffs, lowering, recovery).
361
+
362
+ The felt pain this solves is real and recurring: every frontier model
363
+ release, deprecation, price change, or silent alias flip lands on every
364
+ multi-model app at once. Building the absorption layer inline ships one
365
+ app's fix. Building it portable — one compiler, one curated registry, one
366
+ watcher set, one telemetry substrate ships the fix to every app at once.
237
367
 
238
368
  ## License
239
369
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `@warmdrift/kgauto-compiler/brain-proxy` — consumer brain-forward factory.
3
+ *
4
+ * ## Why this exists (the 4-consumer silent-drop class, 2026-07-06 s62)
5
+ *
6
+ * The library's `record()` / `recordOutcome()` / `recordShadowProbe()` /
7
+ * advisory paths all POST to a consumer-hosted proxy that forwards the row to
8
+ * the shared Supabase brain (the consumer holds the service-role key; kgauto's
9
+ * library never sees it). Every consumer hand-rolled that proxy layer — and a
10
+ * 4-consumer audit found every one silently broken in a *different* shape:
11
+ *
12
+ * - tt-intelligence: middleware blocked 2 of 3 routes — server-to-server
13
+ * POSTs 307-redirected to /login (missing public-path allowlist entry).
14
+ * - inspire-central: the `/outcomes` proxy carried a column whitelist frozen
15
+ * at alpha.7 that stripped `fell_over_from` / `fallback_reason`
16
+ * (migration 018 columns) — Glass-Box fallback telemetry silently NULL.
17
+ * - playbacksam: the `/probe_outcomes` whitelist would strip alpha.53's
18
+ * `error_class` — every future migration column drops on the floor.
19
+ * - global-expansion: mounted 1 of 4 routes — advisory POSTs 404 silently.
20
+ *
21
+ * Same L-142 / L-117 silent-drop family, four independent shapes, all in the
22
+ * layer consumers hand-roll. This factory kills the *class*: kgauto owns the
23
+ * forward handler, the consumer mounts ONE catch-all route. No column
24
+ * whitelist — the body is forwarded verbatim (`{ ...body }`), so any future
25
+ * migration column passes through untouched. One middleware/public-path entry
26
+ * (`/api/kgauto/v2/`) covers all four segments, which structurally prevents
27
+ * the tt-intel middleware-drift failure.
28
+ *
29
+ * ## Mounting recipes
30
+ *
31
+ * ### (a) Next.js app-router catch-all — ONE file, ONE middleware entry
32
+ *
33
+ * // app/api/kgauto/v2/[segment]/route.ts
34
+ * import { createBrainForwardRoutes } from '@warmdrift/kgauto-compiler/brain-proxy';
35
+ * const routes = createBrainForwardRoutes({
36
+ * ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
37
+ * brainUrl: process.env.KGAUTO_BRAIN_URL!, // https://xxxx.supabase.co
38
+ * serviceKey: process.env.KGAUTO_BRAIN_SERVICE_KEY!,
39
+ * });
40
+ * export const POST = (req: Request, { params }: { params: { segment: string } }) =>
41
+ * routes.handle(req, params.segment);
42
+ *
43
+ * // middleware.ts — ONE public-path entry covers ALL four segments.
44
+ * // This is the structural fix for the tt-intel drift: you cannot mount
45
+ * // 3-of-4 routes and forget the 4th, because the catch-all is one file
46
+ * // and the allowlist is one prefix.
47
+ * const PUBLIC_PATHS = [ ... '/api/kgauto/v2/' ];
48
+ *
49
+ * ### (b) Plain Vercel edge function, per segment
50
+ *
51
+ * // api/kgauto/probe_outcomes.ts
52
+ * export const config = { runtime: 'edge' };
53
+ * const routes = createBrainForwardRoutes({ ... });
54
+ * export default (req: Request) => routes.handle(req, 'probe_outcomes');
55
+ *
56
+ * The library appends the segment path to the consumer's `endpoint`, so the
57
+ * consumer's `KGAUTO_V2_BRAIN_URL` (the value passed to `configureBrain`)
58
+ * must be the base that resolves to `.../api/kgauto/v2` — i.e. the last path
59
+ * segment of each library POST (`outcomes`, `probe_outcomes`, …) is what
60
+ * `handle()` receives as `segment`.
61
+ *
62
+ * ## Prefer-header relay (alpha.56)
63
+ *
64
+ * `record()` sends `Prefer: return=representation` on its `/outcomes` POST and
65
+ * parses the inserted row id from the PROXY's response to fire the secondary
66
+ * `compile_outcome_advisories` POST. alpha.54/.55 hardcoded
67
+ * `Prefer: return=minimal` toward the brain and returned `{ok:true}` — so the
68
+ * id-parse found nothing and the advisory secondary silently never fired for
69
+ * factory-mounted consumers (PB + GE convergent finding, 2026-07-06). The
70
+ * factory now honors a caller `Prefer: return=representation` header: it is
71
+ * forwarded to the brain and the brain's response body (the inserted row(s))
72
+ * is relayed verbatim on success. Callers that don't send the header keep the
73
+ * `return=minimal` + `{ok:true}` behavior.
74
+ *
75
+ * ## Quality-segment handle resolution (alpha.55)
76
+ *
77
+ * `recordOutcome()` sends `outcome_id` as `CompileResult.handle` (a string),
78
+ * but `compile_outcome_quality.outcome_id` is a BIGINT FK →
79
+ * `compile_outcomes(id)`. A verbatim forward would 400 every handle-shaped
80
+ * verdict (tt-intel + IC convergent finding, 2026-07-06). On the
81
+ * `compile_outcome_quality` segment the factory resolves non-numeric
82
+ * `outcome_id` values to their BIGINT row ids via
83
+ * `compile_outcomes?handle=eq.<v>` (latest row wins) before forwarding.
84
+ * Numeric ids pass through untouched; an unresolvable handle returns
85
+ * `404 handle_not_found` so the library's route-404 observability names the
86
+ * failure instead of the brain's opaque FK error.
87
+ */
88
+ interface BrainForwardConfig {
89
+ /** Bearer token consumers' callers must present (usually KGAUTO_INGEST_SECRET). */
90
+ ingestSecret: string;
91
+ /** Brain Supabase project URL (e.g. https://xxxx.supabase.co). */
92
+ brainUrl: string;
93
+ /** Brain service-role key (server-side only). */
94
+ serviceKey: string;
95
+ /** Optional fetch impl for tests. */
96
+ fetchImpl?: typeof fetch;
97
+ }
98
+ interface BrainForwardRoutes {
99
+ /**
100
+ * Web-standard handler. `segment` is the last path segment of the library's
101
+ * POST URL (e.g. 'outcomes', 'probe_outcomes'). In a Next.js catch-all this
102
+ * is `params.segment`. Never throws — every error path returns a `Response`.
103
+ */
104
+ handle(req: Request, segment: string): Promise<Response>;
105
+ /**
106
+ * The path segments this factory serves. Consumers can use it for route
107
+ * generation, allowlist construction, or tests.
108
+ */
109
+ segments: readonly string[];
110
+ }
111
+ declare function createBrainForwardRoutes(config: BrainForwardConfig): BrainForwardRoutes;
112
+
113
+ export { type BrainForwardConfig, type BrainForwardRoutes, createBrainForwardRoutes };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * `@warmdrift/kgauto-compiler/brain-proxy` — consumer brain-forward factory.
3
+ *
4
+ * ## Why this exists (the 4-consumer silent-drop class, 2026-07-06 s62)
5
+ *
6
+ * The library's `record()` / `recordOutcome()` / `recordShadowProbe()` /
7
+ * advisory paths all POST to a consumer-hosted proxy that forwards the row to
8
+ * the shared Supabase brain (the consumer holds the service-role key; kgauto's
9
+ * library never sees it). Every consumer hand-rolled that proxy layer — and a
10
+ * 4-consumer audit found every one silently broken in a *different* shape:
11
+ *
12
+ * - tt-intelligence: middleware blocked 2 of 3 routes — server-to-server
13
+ * POSTs 307-redirected to /login (missing public-path allowlist entry).
14
+ * - inspire-central: the `/outcomes` proxy carried a column whitelist frozen
15
+ * at alpha.7 that stripped `fell_over_from` / `fallback_reason`
16
+ * (migration 018 columns) — Glass-Box fallback telemetry silently NULL.
17
+ * - playbacksam: the `/probe_outcomes` whitelist would strip alpha.53's
18
+ * `error_class` — every future migration column drops on the floor.
19
+ * - global-expansion: mounted 1 of 4 routes — advisory POSTs 404 silently.
20
+ *
21
+ * Same L-142 / L-117 silent-drop family, four independent shapes, all in the
22
+ * layer consumers hand-roll. This factory kills the *class*: kgauto owns the
23
+ * forward handler, the consumer mounts ONE catch-all route. No column
24
+ * whitelist — the body is forwarded verbatim (`{ ...body }`), so any future
25
+ * migration column passes through untouched. One middleware/public-path entry
26
+ * (`/api/kgauto/v2/`) covers all four segments, which structurally prevents
27
+ * the tt-intel middleware-drift failure.
28
+ *
29
+ * ## Mounting recipes
30
+ *
31
+ * ### (a) Next.js app-router catch-all — ONE file, ONE middleware entry
32
+ *
33
+ * // app/api/kgauto/v2/[segment]/route.ts
34
+ * import { createBrainForwardRoutes } from '@warmdrift/kgauto-compiler/brain-proxy';
35
+ * const routes = createBrainForwardRoutes({
36
+ * ingestSecret: process.env.KGAUTO_INGEST_SECRET!,
37
+ * brainUrl: process.env.KGAUTO_BRAIN_URL!, // https://xxxx.supabase.co
38
+ * serviceKey: process.env.KGAUTO_BRAIN_SERVICE_KEY!,
39
+ * });
40
+ * export const POST = (req: Request, { params }: { params: { segment: string } }) =>
41
+ * routes.handle(req, params.segment);
42
+ *
43
+ * // middleware.ts — ONE public-path entry covers ALL four segments.
44
+ * // This is the structural fix for the tt-intel drift: you cannot mount
45
+ * // 3-of-4 routes and forget the 4th, because the catch-all is one file
46
+ * // and the allowlist is one prefix.
47
+ * const PUBLIC_PATHS = [ ... '/api/kgauto/v2/' ];
48
+ *
49
+ * ### (b) Plain Vercel edge function, per segment
50
+ *
51
+ * // api/kgauto/probe_outcomes.ts
52
+ * export const config = { runtime: 'edge' };
53
+ * const routes = createBrainForwardRoutes({ ... });
54
+ * export default (req: Request) => routes.handle(req, 'probe_outcomes');
55
+ *
56
+ * The library appends the segment path to the consumer's `endpoint`, so the
57
+ * consumer's `KGAUTO_V2_BRAIN_URL` (the value passed to `configureBrain`)
58
+ * must be the base that resolves to `.../api/kgauto/v2` — i.e. the last path
59
+ * segment of each library POST (`outcomes`, `probe_outcomes`, …) is what
60
+ * `handle()` receives as `segment`.
61
+ *
62
+ * ## Prefer-header relay (alpha.56)
63
+ *
64
+ * `record()` sends `Prefer: return=representation` on its `/outcomes` POST and
65
+ * parses the inserted row id from the PROXY's response to fire the secondary
66
+ * `compile_outcome_advisories` POST. alpha.54/.55 hardcoded
67
+ * `Prefer: return=minimal` toward the brain and returned `{ok:true}` — so the
68
+ * id-parse found nothing and the advisory secondary silently never fired for
69
+ * factory-mounted consumers (PB + GE convergent finding, 2026-07-06). The
70
+ * factory now honors a caller `Prefer: return=representation` header: it is
71
+ * forwarded to the brain and the brain's response body (the inserted row(s))
72
+ * is relayed verbatim on success. Callers that don't send the header keep the
73
+ * `return=minimal` + `{ok:true}` behavior.
74
+ *
75
+ * ## Quality-segment handle resolution (alpha.55)
76
+ *
77
+ * `recordOutcome()` sends `outcome_id` as `CompileResult.handle` (a string),
78
+ * but `compile_outcome_quality.outcome_id` is a BIGINT FK →
79
+ * `compile_outcomes(id)`. A verbatim forward would 400 every handle-shaped
80
+ * verdict (tt-intel + IC convergent finding, 2026-07-06). On the
81
+ * `compile_outcome_quality` segment the factory resolves non-numeric
82
+ * `outcome_id` values to their BIGINT row ids via
83
+ * `compile_outcomes?handle=eq.<v>` (latest row wins) before forwarding.
84
+ * Numeric ids pass through untouched; an unresolvable handle returns
85
+ * `404 handle_not_found` so the library's route-404 observability names the
86
+ * failure instead of the brain's opaque FK error.
87
+ */
88
+ interface BrainForwardConfig {
89
+ /** Bearer token consumers' callers must present (usually KGAUTO_INGEST_SECRET). */
90
+ ingestSecret: string;
91
+ /** Brain Supabase project URL (e.g. https://xxxx.supabase.co). */
92
+ brainUrl: string;
93
+ /** Brain service-role key (server-side only). */
94
+ serviceKey: string;
95
+ /** Optional fetch impl for tests. */
96
+ fetchImpl?: typeof fetch;
97
+ }
98
+ interface BrainForwardRoutes {
99
+ /**
100
+ * Web-standard handler. `segment` is the last path segment of the library's
101
+ * POST URL (e.g. 'outcomes', 'probe_outcomes'). In a Next.js catch-all this
102
+ * is `params.segment`. Never throws — every error path returns a `Response`.
103
+ */
104
+ handle(req: Request, segment: string): Promise<Response>;
105
+ /**
106
+ * The path segments this factory serves. Consumers can use it for route
107
+ * generation, allowlist construction, or tests.
108
+ */
109
+ segments: readonly string[];
110
+ }
111
+ declare function createBrainForwardRoutes(config: BrainForwardConfig): BrainForwardRoutes;
112
+
113
+ export { type BrainForwardConfig, type BrainForwardRoutes, createBrainForwardRoutes };