@warmdrift/kgauto-compiler 2.0.0-alpha.37 → 2.0.0-alpha.39

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.
package/dist/index.d.mts CHANGED
@@ -203,11 +203,28 @@ interface BrainQueryConfig {
203
203
  pricing?: boolean;
204
204
  /** Default true when endpoint set. Brain-driven model registry + aliases. */
205
205
  models?: boolean;
206
+ /**
207
+ * alpha.38 — opt-in compile-time advisor for stale exclusions. Default
208
+ * `true` when endpoint is set. When enabled, the library reads
209
+ * `exclusion_findings` from the central kgauto-dashboard for the consumer's
210
+ * `app_id` (no consumer-side proxy work required) and surfaces a
211
+ * `stale-exclusion-candidate` advisory when compile()'s archetype has a
212
+ * recommend-probe finding open. Set `false` to silence (no fetch, no
213
+ * advisory).
214
+ */
215
+ findingsExclusions?: boolean;
206
216
  /** SWR window in ms. Default 300_000 (5 min). */
207
217
  cacheTtlMs?: number;
208
218
  /** Override the GET URL when the read endpoint differs from the write one.
209
219
  * Defaults to `${endpoint}/v2/config` when omitted. */
210
220
  configEndpoint?: string;
221
+ /**
222
+ * alpha.38 — override the findings endpoint when the consumer wants to
223
+ * proxy `exclusion_findings` reads through their own service rather than
224
+ * reading directly from the kgauto-dashboard. Library appends `?app_id=X`.
225
+ * Defaults to `https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions`.
226
+ */
227
+ findingsEndpoint?: string;
211
228
  }
212
229
  interface BrainConfig {
213
230
  /** Brain HTTP endpoint base URL (e.g., https://kgauto-brain.vercel.app/api). */
@@ -751,6 +768,80 @@ declare function markAdvisoryResolved(opts: MarkAdvisoryResolvedOptions): Promis
751
768
  ok: false;
752
769
  reason: string;
753
770
  }>;
771
+ /**
772
+ * Resolution sources supported by the alpha.39 `markExclusionFindingHandled`
773
+ * API. Mirrors the subset of `exclusion_findings.resolution_source` CHECK
774
+ * constraint values that consumers can self-set:
775
+ *
776
+ * - `'consumer-marked'` — generic "we've handled this; stop reminding."
777
+ * Use this for "we evaluated and chose not to act" or "we'll act when
778
+ * ready but acknowledge the finding now."
779
+ * - `'declined'` — explicit "this exclusion is intentional; do not surface
780
+ * again." Stronger signal than consumer-marked; useful for compliance
781
+ * exclusions, brand-promise exclusions, etc.
782
+ * - `'probed-unblock'` — alpha.40+ probe-path verdict: consumer ran a live
783
+ * probe and decided to unblock the excluded model. Library accepts the
784
+ * value today so the probe path doesn't need a follow-up API release.
785
+ * - `'probed-stay-excluded'` — alpha.40+ probe-path verdict: consumer
786
+ * probed and confirmed the exclusion. Same forward-compat shape as
787
+ * `'probed-unblock'`.
788
+ *
789
+ * The `'auto'` resolution_source is NOT exposed here — that's reserved
790
+ * for the brain's own automatic resolution (e.g. when the detection
791
+ * conditions no longer hold). Consumer code sets only the four values
792
+ * above.
793
+ */
794
+ type ExclusionResolutionSource = 'consumer-marked' | 'declined' | 'probed-unblock' | 'probed-stay-excluded';
795
+ interface MarkExclusionFindingHandledOptions {
796
+ /** App id the finding belongs to. Required (RLS scopes writes by this). */
797
+ appId: string;
798
+ /** Archetype the finding applies to (e.g. 'hunt', 'classify'). */
799
+ archetype: string;
800
+ /** Canonical model id that the consumer had excluded. */
801
+ excludedModel: string;
802
+ /** Resolution semantics — see `ExclusionResolutionSource` for the four
803
+ * values consumers can set. */
804
+ resolution: ExclusionResolutionSource;
805
+ /** Optional free-form note explaining the decision. Surfaces in operator
806
+ * digests + dashboards alongside the resolution_source. */
807
+ resolutionNote?: string;
808
+ /** Brain Supabase URL base (e.g. `https://<project>.supabase.co`). The
809
+ * function appends `/rest/v1/exclusion_findings?...`. */
810
+ brainEndpoint: string;
811
+ /** Consumer-scoped JWT carrying the `app_id` claim. The brain's RLS
812
+ * policy enforces that the JWT's app_id matches the finding's app_id;
813
+ * cross-tenant writes fail at the database layer regardless of input. */
814
+ brainJwt: string;
815
+ /** Supabase anon key for the `apikey` header (PostgREST requires it). */
816
+ brainAnonKey: string;
817
+ /** Injected fetch for tests. Defaults to global fetch. */
818
+ fetch?: typeof fetch;
819
+ }
820
+ /**
821
+ * Mark a stale-exclusion finding as handled. Returns an `ok/reason` envelope
822
+ * matching `markAdvisoryResolved` — consumer Admin UIs typically render the
823
+ * failure inline rather than crash.
824
+ *
825
+ * Idempotent: if no row matches the (app_id, archetype, excludedModel)
826
+ * tuple (already resolved, never existed, cron not yet UPSERTed), PostgREST
827
+ * returns 200 with zero affected rows and we return `{ ok: true }`. The
828
+ * caller's "I marked it as handled" intent is satisfied regardless of
829
+ * whether the row was already in that state.
830
+ *
831
+ * Reasons surfaced on failure:
832
+ * - `app_id_required` / `archetype_required` / `excluded_model_required`
833
+ * - `resolution_invalid` — `resolution` not one of the four documented values
834
+ * - `brain_auth_misconfig` — 401/403 (JWT / anon key wrong)
835
+ * - `brain_unavailable` — 5xx
836
+ * - `network_error:<message>` — fetch threw
837
+ * - `patch_failed:<status>` — anything else non-2xx
838
+ */
839
+ declare function markExclusionFindingHandled(opts: MarkExclusionFindingHandledOptions): Promise<{
840
+ ok: true;
841
+ } | {
842
+ ok: false;
843
+ reason: string;
844
+ }>;
754
845
 
755
846
  /**
756
847
  * Archetype-cliff compatibility — alpha.28 (tt-intel-Cairn ratified).
@@ -1465,6 +1556,105 @@ declare const loadModelsFromBrain: () => Map<string, ModelProfile>;
1465
1556
  */
1466
1557
  declare const loadAliasesFromBrain: () => Record<string, string>;
1467
1558
 
1559
+ /**
1560
+ * exclusion-findings-brain — alpha.38 KG-19 adapter.
1561
+ *
1562
+ * Per-tenant SWR cache for the `exclusion_findings` table populated by the
1563
+ * `kgauto-exclusion-re-evaluation-watch` cron (alpha.37 substrate). Distinct
1564
+ * from the cross-tenant `brain-query.ts` snapshot because exclusion findings
1565
+ * are scoped per `app_id` — one process = one consumer = one appId, but the
1566
+ * cache key supports multi-app processes by keying snapshots by appId.
1567
+ *
1568
+ * Architecture mirrors `brain-query.ts` but with a per-(appId) snapshot:
1569
+ *
1570
+ * - **Sync API surface.** `getStaleExclusionFindings({ appId, archetype })`
1571
+ * returns `ExclusionFindingRow[]` immediately. First call returns the
1572
+ * bundled fallback (empty array); async refresh fires in background;
1573
+ * subsequent calls within TTL return brain data.
1574
+ *
1575
+ * - **Per-appId snapshot.** Each appId gets its own cache entry. One fetch
1576
+ * per appId per TTL window. Tests can reset between cases.
1577
+ *
1578
+ * - **Tolerant.** Brain down / endpoint misconfigured / unexpected shape →
1579
+ * silent bundled fallback (empty array). Never throws. Warns once per
1580
+ * process per error to avoid log spam.
1581
+ *
1582
+ * - **Opt-in.** Activation gated on `configureExclusionFindingsBrain()`
1583
+ * having been called with a runtime. The public `configureBrain()` in
1584
+ * brain.ts wires this up automatically when `BrainConfig.brainQuery
1585
+ * .findingsExclusions !== false`.
1586
+ *
1587
+ * Default endpoint:
1588
+ * `https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions`
1589
+ * The route accepts `?app_id=X` and returns a JSON array of findings
1590
+ * for that app filtered to `resolved_at IS NULL`. Public read; data
1591
+ * carries no consumer PII (app id, archetype, model id, cost ratio,
1592
+ * savings estimate).
1593
+ */
1594
+ /**
1595
+ * Shape of one row from the `exclusion_findings` cache table. Mirrors the
1596
+ * snake_case JSONB returned by the brain RPC + the dashboard endpoint.
1597
+ * Camel-case mapping happens at the boundary (rowToFinding) so the in-memory
1598
+ * shape stays consistent with the rest of the library.
1599
+ */
1600
+ interface ExclusionFindingRow {
1601
+ /** Intent archetype the finding applies to (e.g. 'hunt', 'classify'). */
1602
+ archetype: string;
1603
+ /** Canonical model id that the consumer has excluded (zero traffic in window). */
1604
+ excludedModel: string;
1605
+ /** Provider of the excluded model (e.g. 'deepseek', 'openai'). */
1606
+ excludedProvider: string;
1607
+ /** Detection verdict — alpha.37 ships 'recommend-probe' only. */
1608
+ verdict: 'recommend-probe' | 'unblock' | 'stay-excluded' | 'inconclusive';
1609
+ /**
1610
+ * Estimated 30-day savings in USD if the excluded model becomes primary
1611
+ * on this archetype. Null when pricing is incomplete or excluded model is
1612
+ * more expensive than the leader.
1613
+ */
1614
+ estimatedSavingsUsd30d: number | null;
1615
+ /** Renderable summary; produced by the detector / RPC. */
1616
+ message: string;
1617
+ /** Actionable recommendation; produced by the detector / RPC. */
1618
+ suggestion: string;
1619
+ /** Detector confidence tier. */
1620
+ confidence: 'high' | 'medium' | 'low';
1621
+ /**
1622
+ * Free-form evidence pack (cost ratio, leader info, promo flags, parallel-
1623
+ * tool hint, etc.). Surfaced verbatim to advisor rules for richer messages.
1624
+ * Optional — older detector runs may not populate every field.
1625
+ */
1626
+ evidence?: Record<string, unknown>;
1627
+ }
1628
+ /**
1629
+ * Default endpoint hosted on the kgauto-dashboard. Public read — data carries
1630
+ * no PII (app id, archetype, model id, cost ratio, savings estimate). Mirrors
1631
+ * the cross-tenant `/api/kgauto-v2/config` pattern.
1632
+ */
1633
+ declare const DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
1634
+ /**
1635
+ * Sync introspection — is the findings-brain active? Used by advisor rules
1636
+ * to decide whether to even attempt a read.
1637
+ */
1638
+ declare function isExclusionFindingsBrainActive(): boolean;
1639
+ interface GetExclusionFindingsOpts {
1640
+ /** App id to read findings for. Required. */
1641
+ appId: string;
1642
+ /**
1643
+ * Optional archetype filter. When set, returns only findings where
1644
+ * `archetype` matches. When omitted, returns all findings for the app.
1645
+ */
1646
+ archetype?: string;
1647
+ }
1648
+ /**
1649
+ * Sync reader. Returns the cached findings for `appId`, optionally filtered
1650
+ * to a single archetype. First call returns empty array and triggers async
1651
+ * refresh; subsequent calls within TTL return brain data.
1652
+ *
1653
+ * NEVER throws. Brain down / endpoint misconfigured / mapper exception →
1654
+ * empty array.
1655
+ */
1656
+ declare function getStaleExclusionFindings(opts: GetExclusionFindingsOpts): ExclusionFindingRow[];
1657
+
1468
1658
  /**
1469
1659
  * @warmdrift/kgauto v2 — prompt compiler + central learning brain.
1470
1660
  *
@@ -1511,4 +1701,4 @@ declare const loadAliasesFromBrain: () => Record<string, string>;
1511
1701
  */
1512
1702
  declare function compile(ir: PromptIR, opts?: CompileOptions): CompileResult;
1513
1703
 
1514
- export { ABSOLUTE_FLOOR, type AISDKConvertedMessage, ARCHETYPE_FLOOR_DEFAULT, type ActionableAdvisory, Adapter, type AdvisoryResolutionSource, type AdvisorySeverity, type AdvisoryStatus, type AdvisorySuggestedFix, ApiKeys, type AppOracle, type ApplySectionRewritesArgs, type ApplySectionRewritesResult, type ArchetypePerfMap, type ArchetypePerfNMap, type ArchetypePerfScoreResult, type AttachCacheControlResult, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, ChainEntry, type CompatibilityIntent, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetActionableAdvisoriesOptions, type GetDefaultFallbackChainOpts, type GetPerAxisMetricsOpts, Grounding, IntentArchetypeName, type LLMJudgeOptions, MEASURED_GROUNDING_MIN_N, type MarkAdvisoryResolvedOptions, type ModelBrainRow, type ModelCompatibility, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, PerAxisMetrics, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, RULE_SEQUENTIAL_TOOL_CLIFF, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type RunAdvisorPhase2Context, SectionRewrite, type SupportedProvider, SystemModelMessage, TRANSLATOR_FLOOR, applySectionRewrites, attachCacheControlToStreamTextInput, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getActionableAdvisories, getAllStarterChains, getAllStarterChainsWithGrounding, getArchetypePerfScore, getDefaultFallbackChain, getDefaultFallbackChainWithGrounding, getModelCompatibility, getPerAxisMetrics, getReachabilityDiagnostic, getSequentialStarterChain, getSequentialStarterChainWithGrounding, getStarterChain, getStarterChainWithGrounding, isBrainQueryActiveFor, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadArchetypePerfNFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, markAdvisoryResolved, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
1704
+ export { ABSOLUTE_FLOOR, type AISDKConvertedMessage, ARCHETYPE_FLOOR_DEFAULT, type ActionableAdvisory, Adapter, type AdvisoryResolutionSource, type AdvisorySeverity, type AdvisoryStatus, type AdvisorySuggestedFix, ApiKeys, type AppOracle, type ApplySectionRewritesArgs, type ApplySectionRewritesResult, type ArchetypePerfMap, type ArchetypePerfNMap, type ArchetypePerfScoreResult, type AttachCacheControlResult, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, ChainEntry, type CompatibilityIntent, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, DEFAULT_FINDINGS_ENDPOINT, type ExclusionFindingRow, type ExclusionResolutionSource, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetActionableAdvisoriesOptions, type GetDefaultFallbackChainOpts, type GetPerAxisMetricsOpts, Grounding, IntentArchetypeName, type LLMJudgeOptions, MEASURED_GROUNDING_MIN_N, type MarkAdvisoryResolvedOptions, type MarkExclusionFindingHandledOptions, type ModelBrainRow, type ModelCompatibility, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, PerAxisMetrics, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, RULE_SEQUENTIAL_TOOL_CLIFF, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type RunAdvisorPhase2Context, SectionRewrite, type SupportedProvider, SystemModelMessage, TRANSLATOR_FLOOR, applySectionRewrites, attachCacheControlToStreamTextInput, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getActionableAdvisories, getAllStarterChains, getAllStarterChainsWithGrounding, getArchetypePerfScore, getDefaultFallbackChain, getDefaultFallbackChainWithGrounding, getModelCompatibility, getPerAxisMetrics, getReachabilityDiagnostic, getSequentialStarterChain, getSequentialStarterChainWithGrounding, getStaleExclusionFindings, getStarterChain, getStarterChainWithGrounding, isBrainQueryActiveFor, isExclusionFindingsBrainActive, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadArchetypePerfNFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, markAdvisoryResolved, markExclusionFindingHandled, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
package/dist/index.d.ts CHANGED
@@ -203,11 +203,28 @@ interface BrainQueryConfig {
203
203
  pricing?: boolean;
204
204
  /** Default true when endpoint set. Brain-driven model registry + aliases. */
205
205
  models?: boolean;
206
+ /**
207
+ * alpha.38 — opt-in compile-time advisor for stale exclusions. Default
208
+ * `true` when endpoint is set. When enabled, the library reads
209
+ * `exclusion_findings` from the central kgauto-dashboard for the consumer's
210
+ * `app_id` (no consumer-side proxy work required) and surfaces a
211
+ * `stale-exclusion-candidate` advisory when compile()'s archetype has a
212
+ * recommend-probe finding open. Set `false` to silence (no fetch, no
213
+ * advisory).
214
+ */
215
+ findingsExclusions?: boolean;
206
216
  /** SWR window in ms. Default 300_000 (5 min). */
207
217
  cacheTtlMs?: number;
208
218
  /** Override the GET URL when the read endpoint differs from the write one.
209
219
  * Defaults to `${endpoint}/v2/config` when omitted. */
210
220
  configEndpoint?: string;
221
+ /**
222
+ * alpha.38 — override the findings endpoint when the consumer wants to
223
+ * proxy `exclusion_findings` reads through their own service rather than
224
+ * reading directly from the kgauto-dashboard. Library appends `?app_id=X`.
225
+ * Defaults to `https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions`.
226
+ */
227
+ findingsEndpoint?: string;
211
228
  }
212
229
  interface BrainConfig {
213
230
  /** Brain HTTP endpoint base URL (e.g., https://kgauto-brain.vercel.app/api). */
@@ -751,6 +768,80 @@ declare function markAdvisoryResolved(opts: MarkAdvisoryResolvedOptions): Promis
751
768
  ok: false;
752
769
  reason: string;
753
770
  }>;
771
+ /**
772
+ * Resolution sources supported by the alpha.39 `markExclusionFindingHandled`
773
+ * API. Mirrors the subset of `exclusion_findings.resolution_source` CHECK
774
+ * constraint values that consumers can self-set:
775
+ *
776
+ * - `'consumer-marked'` — generic "we've handled this; stop reminding."
777
+ * Use this for "we evaluated and chose not to act" or "we'll act when
778
+ * ready but acknowledge the finding now."
779
+ * - `'declined'` — explicit "this exclusion is intentional; do not surface
780
+ * again." Stronger signal than consumer-marked; useful for compliance
781
+ * exclusions, brand-promise exclusions, etc.
782
+ * - `'probed-unblock'` — alpha.40+ probe-path verdict: consumer ran a live
783
+ * probe and decided to unblock the excluded model. Library accepts the
784
+ * value today so the probe path doesn't need a follow-up API release.
785
+ * - `'probed-stay-excluded'` — alpha.40+ probe-path verdict: consumer
786
+ * probed and confirmed the exclusion. Same forward-compat shape as
787
+ * `'probed-unblock'`.
788
+ *
789
+ * The `'auto'` resolution_source is NOT exposed here — that's reserved
790
+ * for the brain's own automatic resolution (e.g. when the detection
791
+ * conditions no longer hold). Consumer code sets only the four values
792
+ * above.
793
+ */
794
+ type ExclusionResolutionSource = 'consumer-marked' | 'declined' | 'probed-unblock' | 'probed-stay-excluded';
795
+ interface MarkExclusionFindingHandledOptions {
796
+ /** App id the finding belongs to. Required (RLS scopes writes by this). */
797
+ appId: string;
798
+ /** Archetype the finding applies to (e.g. 'hunt', 'classify'). */
799
+ archetype: string;
800
+ /** Canonical model id that the consumer had excluded. */
801
+ excludedModel: string;
802
+ /** Resolution semantics — see `ExclusionResolutionSource` for the four
803
+ * values consumers can set. */
804
+ resolution: ExclusionResolutionSource;
805
+ /** Optional free-form note explaining the decision. Surfaces in operator
806
+ * digests + dashboards alongside the resolution_source. */
807
+ resolutionNote?: string;
808
+ /** Brain Supabase URL base (e.g. `https://<project>.supabase.co`). The
809
+ * function appends `/rest/v1/exclusion_findings?...`. */
810
+ brainEndpoint: string;
811
+ /** Consumer-scoped JWT carrying the `app_id` claim. The brain's RLS
812
+ * policy enforces that the JWT's app_id matches the finding's app_id;
813
+ * cross-tenant writes fail at the database layer regardless of input. */
814
+ brainJwt: string;
815
+ /** Supabase anon key for the `apikey` header (PostgREST requires it). */
816
+ brainAnonKey: string;
817
+ /** Injected fetch for tests. Defaults to global fetch. */
818
+ fetch?: typeof fetch;
819
+ }
820
+ /**
821
+ * Mark a stale-exclusion finding as handled. Returns an `ok/reason` envelope
822
+ * matching `markAdvisoryResolved` — consumer Admin UIs typically render the
823
+ * failure inline rather than crash.
824
+ *
825
+ * Idempotent: if no row matches the (app_id, archetype, excludedModel)
826
+ * tuple (already resolved, never existed, cron not yet UPSERTed), PostgREST
827
+ * returns 200 with zero affected rows and we return `{ ok: true }`. The
828
+ * caller's "I marked it as handled" intent is satisfied regardless of
829
+ * whether the row was already in that state.
830
+ *
831
+ * Reasons surfaced on failure:
832
+ * - `app_id_required` / `archetype_required` / `excluded_model_required`
833
+ * - `resolution_invalid` — `resolution` not one of the four documented values
834
+ * - `brain_auth_misconfig` — 401/403 (JWT / anon key wrong)
835
+ * - `brain_unavailable` — 5xx
836
+ * - `network_error:<message>` — fetch threw
837
+ * - `patch_failed:<status>` — anything else non-2xx
838
+ */
839
+ declare function markExclusionFindingHandled(opts: MarkExclusionFindingHandledOptions): Promise<{
840
+ ok: true;
841
+ } | {
842
+ ok: false;
843
+ reason: string;
844
+ }>;
754
845
 
755
846
  /**
756
847
  * Archetype-cliff compatibility — alpha.28 (tt-intel-Cairn ratified).
@@ -1465,6 +1556,105 @@ declare const loadModelsFromBrain: () => Map<string, ModelProfile>;
1465
1556
  */
1466
1557
  declare const loadAliasesFromBrain: () => Record<string, string>;
1467
1558
 
1559
+ /**
1560
+ * exclusion-findings-brain — alpha.38 KG-19 adapter.
1561
+ *
1562
+ * Per-tenant SWR cache for the `exclusion_findings` table populated by the
1563
+ * `kgauto-exclusion-re-evaluation-watch` cron (alpha.37 substrate). Distinct
1564
+ * from the cross-tenant `brain-query.ts` snapshot because exclusion findings
1565
+ * are scoped per `app_id` — one process = one consumer = one appId, but the
1566
+ * cache key supports multi-app processes by keying snapshots by appId.
1567
+ *
1568
+ * Architecture mirrors `brain-query.ts` but with a per-(appId) snapshot:
1569
+ *
1570
+ * - **Sync API surface.** `getStaleExclusionFindings({ appId, archetype })`
1571
+ * returns `ExclusionFindingRow[]` immediately. First call returns the
1572
+ * bundled fallback (empty array); async refresh fires in background;
1573
+ * subsequent calls within TTL return brain data.
1574
+ *
1575
+ * - **Per-appId snapshot.** Each appId gets its own cache entry. One fetch
1576
+ * per appId per TTL window. Tests can reset between cases.
1577
+ *
1578
+ * - **Tolerant.** Brain down / endpoint misconfigured / unexpected shape →
1579
+ * silent bundled fallback (empty array). Never throws. Warns once per
1580
+ * process per error to avoid log spam.
1581
+ *
1582
+ * - **Opt-in.** Activation gated on `configureExclusionFindingsBrain()`
1583
+ * having been called with a runtime. The public `configureBrain()` in
1584
+ * brain.ts wires this up automatically when `BrainConfig.brainQuery
1585
+ * .findingsExclusions !== false`.
1586
+ *
1587
+ * Default endpoint:
1588
+ * `https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions`
1589
+ * The route accepts `?app_id=X` and returns a JSON array of findings
1590
+ * for that app filtered to `resolved_at IS NULL`. Public read; data
1591
+ * carries no consumer PII (app id, archetype, model id, cost ratio,
1592
+ * savings estimate).
1593
+ */
1594
+ /**
1595
+ * Shape of one row from the `exclusion_findings` cache table. Mirrors the
1596
+ * snake_case JSONB returned by the brain RPC + the dashboard endpoint.
1597
+ * Camel-case mapping happens at the boundary (rowToFinding) so the in-memory
1598
+ * shape stays consistent with the rest of the library.
1599
+ */
1600
+ interface ExclusionFindingRow {
1601
+ /** Intent archetype the finding applies to (e.g. 'hunt', 'classify'). */
1602
+ archetype: string;
1603
+ /** Canonical model id that the consumer has excluded (zero traffic in window). */
1604
+ excludedModel: string;
1605
+ /** Provider of the excluded model (e.g. 'deepseek', 'openai'). */
1606
+ excludedProvider: string;
1607
+ /** Detection verdict — alpha.37 ships 'recommend-probe' only. */
1608
+ verdict: 'recommend-probe' | 'unblock' | 'stay-excluded' | 'inconclusive';
1609
+ /**
1610
+ * Estimated 30-day savings in USD if the excluded model becomes primary
1611
+ * on this archetype. Null when pricing is incomplete or excluded model is
1612
+ * more expensive than the leader.
1613
+ */
1614
+ estimatedSavingsUsd30d: number | null;
1615
+ /** Renderable summary; produced by the detector / RPC. */
1616
+ message: string;
1617
+ /** Actionable recommendation; produced by the detector / RPC. */
1618
+ suggestion: string;
1619
+ /** Detector confidence tier. */
1620
+ confidence: 'high' | 'medium' | 'low';
1621
+ /**
1622
+ * Free-form evidence pack (cost ratio, leader info, promo flags, parallel-
1623
+ * tool hint, etc.). Surfaced verbatim to advisor rules for richer messages.
1624
+ * Optional — older detector runs may not populate every field.
1625
+ */
1626
+ evidence?: Record<string, unknown>;
1627
+ }
1628
+ /**
1629
+ * Default endpoint hosted on the kgauto-dashboard. Public read — data carries
1630
+ * no PII (app id, archetype, model id, cost ratio, savings estimate). Mirrors
1631
+ * the cross-tenant `/api/kgauto-v2/config` pattern.
1632
+ */
1633
+ declare const DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
1634
+ /**
1635
+ * Sync introspection — is the findings-brain active? Used by advisor rules
1636
+ * to decide whether to even attempt a read.
1637
+ */
1638
+ declare function isExclusionFindingsBrainActive(): boolean;
1639
+ interface GetExclusionFindingsOpts {
1640
+ /** App id to read findings for. Required. */
1641
+ appId: string;
1642
+ /**
1643
+ * Optional archetype filter. When set, returns only findings where
1644
+ * `archetype` matches. When omitted, returns all findings for the app.
1645
+ */
1646
+ archetype?: string;
1647
+ }
1648
+ /**
1649
+ * Sync reader. Returns the cached findings for `appId`, optionally filtered
1650
+ * to a single archetype. First call returns empty array and triggers async
1651
+ * refresh; subsequent calls within TTL return brain data.
1652
+ *
1653
+ * NEVER throws. Brain down / endpoint misconfigured / mapper exception →
1654
+ * empty array.
1655
+ */
1656
+ declare function getStaleExclusionFindings(opts: GetExclusionFindingsOpts): ExclusionFindingRow[];
1657
+
1468
1658
  /**
1469
1659
  * @warmdrift/kgauto v2 — prompt compiler + central learning brain.
1470
1660
  *
@@ -1511,4 +1701,4 @@ declare const loadAliasesFromBrain: () => Record<string, string>;
1511
1701
  */
1512
1702
  declare function compile(ir: PromptIR, opts?: CompileOptions): CompileResult;
1513
1703
 
1514
- export { ABSOLUTE_FLOOR, type AISDKConvertedMessage, ARCHETYPE_FLOOR_DEFAULT, type ActionableAdvisory, Adapter, type AdvisoryResolutionSource, type AdvisorySeverity, type AdvisoryStatus, type AdvisorySuggestedFix, ApiKeys, type AppOracle, type ApplySectionRewritesArgs, type ApplySectionRewritesResult, type ArchetypePerfMap, type ArchetypePerfNMap, type ArchetypePerfScoreResult, type AttachCacheControlResult, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, ChainEntry, type CompatibilityIntent, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetActionableAdvisoriesOptions, type GetDefaultFallbackChainOpts, type GetPerAxisMetricsOpts, Grounding, IntentArchetypeName, type LLMJudgeOptions, MEASURED_GROUNDING_MIN_N, type MarkAdvisoryResolvedOptions, type ModelBrainRow, type ModelCompatibility, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, PerAxisMetrics, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, RULE_SEQUENTIAL_TOOL_CLIFF, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type RunAdvisorPhase2Context, SectionRewrite, type SupportedProvider, SystemModelMessage, TRANSLATOR_FLOOR, applySectionRewrites, attachCacheControlToStreamTextInput, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getActionableAdvisories, getAllStarterChains, getAllStarterChainsWithGrounding, getArchetypePerfScore, getDefaultFallbackChain, getDefaultFallbackChainWithGrounding, getModelCompatibility, getPerAxisMetrics, getReachabilityDiagnostic, getSequentialStarterChain, getSequentialStarterChainWithGrounding, getStarterChain, getStarterChainWithGrounding, isBrainQueryActiveFor, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadArchetypePerfNFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, markAdvisoryResolved, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
1704
+ export { ABSOLUTE_FLOOR, type AISDKConvertedMessage, ARCHETYPE_FLOOR_DEFAULT, type ActionableAdvisory, Adapter, type AdvisoryResolutionSource, type AdvisorySeverity, type AdvisoryStatus, type AdvisorySuggestedFix, ApiKeys, type AppOracle, type ApplySectionRewritesArgs, type ApplySectionRewritesResult, type ArchetypePerfMap, type ArchetypePerfNMap, type ArchetypePerfScoreResult, type AttachCacheControlResult, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, ChainEntry, type CompatibilityIntent, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, DEFAULT_FINDINGS_ENDPOINT, type ExclusionFindingRow, type ExclusionResolutionSource, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetActionableAdvisoriesOptions, type GetDefaultFallbackChainOpts, type GetPerAxisMetricsOpts, Grounding, IntentArchetypeName, type LLMJudgeOptions, MEASURED_GROUNDING_MIN_N, type MarkAdvisoryResolvedOptions, type MarkExclusionFindingHandledOptions, type ModelBrainRow, type ModelCompatibility, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, PerAxisMetrics, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, RULE_SEQUENTIAL_TOOL_CLIFF, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type RunAdvisorPhase2Context, SectionRewrite, type SupportedProvider, SystemModelMessage, TRANSLATOR_FLOOR, applySectionRewrites, attachCacheControlToStreamTextInput, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getActionableAdvisories, getAllStarterChains, getAllStarterChainsWithGrounding, getArchetypePerfScore, getDefaultFallbackChain, getDefaultFallbackChainWithGrounding, getModelCompatibility, getPerAxisMetrics, getReachabilityDiagnostic, getSequentialStarterChain, getSequentialStarterChainWithGrounding, getStaleExclusionFindings, getStarterChain, getStarterChainWithGrounding, isBrainQueryActiveFor, isExclusionFindingsBrainActive, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadArchetypePerfNFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, markAdvisoryResolved, markExclusionFindingHandled, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  ALL_ARCHETYPES: () => ALL_ARCHETYPES,
26
26
  ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
27
27
  CallError: () => CallError,
28
+ DEFAULT_FINDINGS_ENDPOINT: () => DEFAULT_FINDINGS_ENDPOINT,
28
29
  DIALECT_VERSION: () => DIALECT_VERSION,
29
30
  INTENT_ARCHETYPES: () => INTENT_ARCHETYPES,
30
31
  MEASURED_GROUNDING_MIN_N: () => MEASURED_GROUNDING_MIN_N,
@@ -56,11 +57,13 @@ __export(index_exports, {
56
57
  getReachabilityDiagnostic: () => getReachabilityDiagnostic,
57
58
  getSequentialStarterChain: () => getSequentialStarterChain,
58
59
  getSequentialStarterChainWithGrounding: () => getSequentialStarterChainWithGrounding,
60
+ getStaleExclusionFindings: () => getStaleExclusionFindings,
59
61
  getStarterChain: () => getStarterChain,
60
62
  getStarterChainWithGrounding: () => getStarterChainWithGrounding,
61
63
  hashShape: () => hashShape,
62
64
  isArchetype: () => isArchetype,
63
65
  isBrainQueryActiveFor: () => isBrainQueryActiveFor,
66
+ isExclusionFindingsBrainActive: () => isExclusionFindingsBrainActive,
64
67
  isModelReachable: () => isModelReachable,
65
68
  isProviderReachable: () => isProviderReachable,
66
69
  learningKey: () => learningKey,
@@ -71,6 +74,7 @@ __export(index_exports, {
71
74
  loadModelsFromBrain: () => loadModelsFromBrain,
72
75
  loadPricingFromBrain: () => loadPricingFromBrain,
73
76
  markAdvisoryResolved: () => markAdvisoryResolved,
77
+ markExclusionFindingHandled: () => markExclusionFindingHandled,
74
78
  profileToRow: () => profileToRow,
75
79
  profilesByProvider: () => profilesByProvider,
76
80
  record: () => record,
@@ -2400,6 +2404,125 @@ function getModelCompatibility(modelId, intent) {
2400
2404
  };
2401
2405
  }
2402
2406
 
2407
+ // src/exclusion-findings-brain.ts
2408
+ function isValidVerdict(v) {
2409
+ return v === "recommend-probe" || v === "unblock" || v === "stay-excluded" || v === "inconclusive";
2410
+ }
2411
+ function isValidConfidence(v) {
2412
+ return v === "high" || v === "medium" || v === "low";
2413
+ }
2414
+ function isRawFindingRow(x) {
2415
+ if (!x || typeof x !== "object") return false;
2416
+ const r = x;
2417
+ return typeof r.intent_archetype === "string" && typeof r.excluded_model === "string" && typeof r.excluded_provider === "string" && typeof r.verdict === "string" && typeof r.message === "string" && typeof r.suggestion === "string" && typeof r.confidence === "string";
2418
+ }
2419
+ function mapRowsToFindings(rows) {
2420
+ const out = [];
2421
+ for (const row of rows) {
2422
+ if (!isRawFindingRow(row)) continue;
2423
+ if (!isValidVerdict(row.verdict)) continue;
2424
+ if (!isValidConfidence(row.confidence)) continue;
2425
+ let savings = null;
2426
+ if (typeof row.estimated_savings_usd_30d === "number") {
2427
+ savings = Number.isFinite(row.estimated_savings_usd_30d) ? row.estimated_savings_usd_30d : null;
2428
+ } else if (typeof row.estimated_savings_usd_30d === "string") {
2429
+ const n = Number(row.estimated_savings_usd_30d);
2430
+ savings = Number.isFinite(n) ? n : null;
2431
+ }
2432
+ out.push({
2433
+ archetype: row.intent_archetype,
2434
+ excludedModel: row.excluded_model,
2435
+ excludedProvider: row.excluded_provider,
2436
+ verdict: row.verdict,
2437
+ estimatedSavingsUsd30d: savings,
2438
+ message: row.message,
2439
+ suggestion: row.suggestion,
2440
+ confidence: row.confidence,
2441
+ evidence: row.evidence && typeof row.evidence === "object" ? row.evidence : void 0
2442
+ });
2443
+ }
2444
+ return out;
2445
+ }
2446
+ var snapshots = /* @__PURE__ */ new Map();
2447
+ var runtime2;
2448
+ var warnedOnce = false;
2449
+ var DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
2450
+ function configureExclusionFindingsBrain(rt) {
2451
+ runtime2 = rt;
2452
+ snapshots.clear();
2453
+ warnedOnce = false;
2454
+ }
2455
+ function isExclusionFindingsBrainActive() {
2456
+ return runtime2 !== void 0;
2457
+ }
2458
+ function getStaleExclusionFindings(opts) {
2459
+ const rt = runtime2;
2460
+ if (!rt) return [];
2461
+ const appId = opts.appId;
2462
+ if (!appId) return [];
2463
+ let snap = snapshots.get(appId);
2464
+ if (!snap) {
2465
+ snap = { data: [], expiresAt: 0, refreshing: false };
2466
+ snapshots.set(appId, snap);
2467
+ }
2468
+ const now = Date.now();
2469
+ const stale = snap.expiresAt <= now;
2470
+ if (stale && !snap.refreshing) {
2471
+ snap.refreshing = true;
2472
+ void asyncRefresh2(rt, appId);
2473
+ }
2474
+ if (opts.archetype) {
2475
+ return snap.data.filter((f) => f.archetype === opts.archetype);
2476
+ }
2477
+ return snap.data;
2478
+ }
2479
+ var pendingRefreshes = /* @__PURE__ */ new Map();
2480
+ async function asyncRefresh2(rt, appId) {
2481
+ const promise = doRefresh2(rt, appId);
2482
+ pendingRefreshes.set(appId, promise);
2483
+ try {
2484
+ await promise;
2485
+ } finally {
2486
+ if (pendingRefreshes.get(appId) === promise) {
2487
+ pendingRefreshes.delete(appId);
2488
+ }
2489
+ }
2490
+ }
2491
+ async function doRefresh2(rt, appId) {
2492
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
2493
+ let snap = snapshots.get(appId);
2494
+ if (!snap) {
2495
+ snap = { data: [], expiresAt: 0, refreshing: false };
2496
+ snapshots.set(appId, snap);
2497
+ }
2498
+ try {
2499
+ const res = await rt.fetchImpl(url, { method: "GET" });
2500
+ if (!res.ok) {
2501
+ throw new Error(`findings ${res.status}: ${res.statusText}`);
2502
+ }
2503
+ const body = await res.json();
2504
+ if (runtime2 !== rt) return;
2505
+ const rows = Array.isArray(body) ? mapRowsToFindings(body) : [];
2506
+ snap.data = rows;
2507
+ snap.expiresAt = Date.now() + rt.ttlMs;
2508
+ snap.refreshing = false;
2509
+ } catch (err) {
2510
+ if (runtime2 !== rt) return;
2511
+ snap.refreshing = false;
2512
+ snap.expiresAt = Date.now() + rt.ttlMs;
2513
+ if (!warnedOnce) {
2514
+ warnedOnce = true;
2515
+ (rt.onError ?? defaultOnError2)(err);
2516
+ }
2517
+ }
2518
+ }
2519
+ function defaultOnError2(err) {
2520
+ console.warn(
2521
+ "[kgauto] exclusion-findings fetch failed (using empty fallback):",
2522
+ err
2523
+ );
2524
+ }
2525
+
2403
2526
  // src/advisor.ts
2404
2527
  var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
2405
2528
  var TIER_DOWN_COST_RATIO = 0.5;
@@ -2419,6 +2542,9 @@ function runAdvisor(ir, result, profile, policy, phase2) {
2419
2542
  if (!translatorClearedToolCallCliff(phase2)) {
2420
2543
  out.push(...detectArchetypePerfFloorBreach(ir, profile));
2421
2544
  }
2545
+ if (policy?.posture !== "locked") {
2546
+ out.push(...detectStaleExclusionCandidate(ir));
2547
+ }
2422
2548
  return out;
2423
2549
  }
2424
2550
  function translatorClearedToolCallCliff(phase2) {
@@ -2627,6 +2753,39 @@ function detectArchetypePerfFloorBreach(ir, profile) {
2627
2753
  }
2628
2754
  ];
2629
2755
  }
2756
+ function detectStaleExclusionCandidate(ir) {
2757
+ if (!isExclusionFindingsBrainActive()) return [];
2758
+ if (!ir.appId) return [];
2759
+ const findings = getStaleExclusionFindings({
2760
+ appId: ir.appId,
2761
+ archetype: ir.intent.archetype
2762
+ });
2763
+ if (findings.length === 0) return [];
2764
+ const ranked = [...findings].sort((a, b) => {
2765
+ const sa = a.estimatedSavingsUsd30d ?? -Infinity;
2766
+ const sb = b.estimatedSavingsUsd30d ?? -Infinity;
2767
+ if (sa !== sb) return sb - sa;
2768
+ return confidenceRank(b.confidence) - confidenceRank(a.confidence);
2769
+ });
2770
+ const top = ranked[0];
2771
+ const extraCount = findings.length - 1;
2772
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
2773
+ return [
2774
+ {
2775
+ level: "info",
2776
+ code: "stale-exclusion-candidate",
2777
+ message: `${top.message}${extraNote}`,
2778
+ suggestion: top.suggestion,
2779
+ recommendationType: "tier-down",
2780
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2781
+ }
2782
+ ];
2783
+ }
2784
+ function confidenceRank(c) {
2785
+ if (c === "high") return 3;
2786
+ if (c === "medium") return 2;
2787
+ return 1;
2788
+ }
2630
2789
 
2631
2790
  // src/translator.ts
2632
2791
  var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
@@ -2990,20 +3149,31 @@ function configureBrain(config) {
2990
3149
  }
2991
3150
  if (enabledTables.size === 0) {
2992
3151
  configureBrainQuery(void 0);
2993
- return;
3152
+ } else {
3153
+ configureBrainQuery({
3154
+ endpoint,
3155
+ configEndpoint: bq.configEndpoint,
3156
+ ttlMs: bq.cacheTtlMs ?? 3e5,
3157
+ fetchImpl: config.fetchImpl ?? fetch,
3158
+ enabledTables,
3159
+ onError: config.onError
3160
+ });
3161
+ }
3162
+ if (bq.findingsExclusions !== false) {
3163
+ configureExclusionFindingsBrain({
3164
+ endpoint: bq.findingsEndpoint ?? DEFAULT_FINDINGS_ENDPOINT,
3165
+ ttlMs: bq.cacheTtlMs ?? 3e5,
3166
+ fetchImpl: config.fetchImpl ?? fetch,
3167
+ onError: config.onError
3168
+ });
3169
+ } else {
3170
+ configureExclusionFindingsBrain(void 0);
2994
3171
  }
2995
- configureBrainQuery({
2996
- endpoint,
2997
- configEndpoint: bq.configEndpoint,
2998
- ttlMs: bq.cacheTtlMs ?? 3e5,
2999
- fetchImpl: config.fetchImpl ?? fetch,
3000
- enabledTables,
3001
- onError: config.onError
3002
- });
3003
3172
  }
3004
3173
  function clearBrain() {
3005
3174
  activeConfig = void 0;
3006
3175
  configureBrainQuery(void 0);
3176
+ configureExclusionFindingsBrain(void 0);
3007
3177
  }
3008
3178
  var compileRegistry = /* @__PURE__ */ new Map();
3009
3179
  var REGISTRY_MAX_ENTRIES = 1e4;
@@ -3107,7 +3277,7 @@ async function record(input) {
3107
3277
  }
3108
3278
  outcomeId = await tryExtractOutcomeId(res);
3109
3279
  } catch (err) {
3110
- (config.onError ?? defaultOnError2)(err);
3280
+ (config.onError ?? defaultOnError3)(err);
3111
3281
  return;
3112
3282
  }
3113
3283
  const advisories = input.advisories ?? reg?.advisoriesFromCompile;
@@ -3129,7 +3299,7 @@ async function record(input) {
3129
3299
  throw new Error(`brain advisories ${res.status}: ${text}`);
3130
3300
  }
3131
3301
  } catch (err) {
3132
- (config.onError ?? defaultOnError2)(err);
3302
+ (config.onError ?? defaultOnError3)(err);
3133
3303
  }
3134
3304
  };
3135
3305
  if (config.sync) {
@@ -3138,7 +3308,7 @@ async function record(input) {
3138
3308
  void send();
3139
3309
  }
3140
3310
  }
3141
- function defaultOnError2(err) {
3311
+ function defaultOnError3(err) {
3142
3312
  console.warn("[kgauto] brain record failed:", err);
3143
3313
  }
3144
3314
  function buildPayload(input, reg) {
@@ -3270,12 +3440,12 @@ async function recordOutcome(input) {
3270
3440
  if (!res.ok) {
3271
3441
  const text = await res.text().catch(() => "<no body>");
3272
3442
  const err = new Error(`brain ${res.status}: ${text}`);
3273
- (config.onError ?? defaultOnError2)(err);
3443
+ (config.onError ?? defaultOnError3)(err);
3274
3444
  return { ok: false, reason: "persistence_failed" };
3275
3445
  }
3276
3446
  return { ok: true };
3277
3447
  } catch (err) {
3278
- (config.onError ?? defaultOnError2)(err);
3448
+ (config.onError ?? defaultOnError3)(err);
3279
3449
  return { ok: false, reason: "persistence_failed" };
3280
3450
  }
3281
3451
  };
@@ -5264,6 +5434,66 @@ async function markAdvisoryResolved(opts) {
5264
5434
  }
5265
5435
  return { ok: true };
5266
5436
  }
5437
+ async function markExclusionFindingHandled(opts) {
5438
+ const {
5439
+ appId,
5440
+ archetype,
5441
+ excludedModel,
5442
+ resolution,
5443
+ resolutionNote,
5444
+ brainEndpoint,
5445
+ brainJwt,
5446
+ brainAnonKey,
5447
+ fetch: injectedFetch
5448
+ } = opts;
5449
+ if (!appId) return { ok: false, reason: "app_id_required" };
5450
+ if (!archetype) return { ok: false, reason: "archetype_required" };
5451
+ if (!excludedModel) {
5452
+ return { ok: false, reason: "excluded_model_required" };
5453
+ }
5454
+ if (resolution !== "consumer-marked" && resolution !== "declined" && resolution !== "probed-unblock" && resolution !== "probed-stay-excluded") {
5455
+ return { ok: false, reason: "resolution_invalid" };
5456
+ }
5457
+ const doFetch = resolveFetch(injectedFetch);
5458
+ const base = normalizeEndpoint(brainEndpoint);
5459
+ const url = `${base}/rest/v1/exclusion_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&excluded_model=eq.${encodeURIComponent(excludedModel)}&resolved_at=is.null`;
5460
+ const patchBody = {
5461
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
5462
+ resolution_source: resolution
5463
+ };
5464
+ if (resolutionNote !== void 0) {
5465
+ patchBody.resolution_note = resolutionNote;
5466
+ }
5467
+ let res;
5468
+ try {
5469
+ res = await doFetch(url, {
5470
+ method: "PATCH",
5471
+ headers: {
5472
+ Authorization: `Bearer ${brainJwt}`,
5473
+ apikey: brainAnonKey,
5474
+ "Content-Type": "application/json",
5475
+ Accept: "application/json",
5476
+ // PATCH may match zero rows when already resolved — return=minimal
5477
+ // keeps the response body empty so the success path is uniform.
5478
+ Prefer: "return=minimal"
5479
+ },
5480
+ body: JSON.stringify(patchBody)
5481
+ });
5482
+ } catch (err) {
5483
+ const msg = err instanceof Error ? err.message : String(err);
5484
+ return { ok: false, reason: `network_error:${msg}` };
5485
+ }
5486
+ if (res.status === 401 || res.status === 403) {
5487
+ return { ok: false, reason: "brain_auth_misconfig" };
5488
+ }
5489
+ if (res.status >= 500) {
5490
+ return { ok: false, reason: "brain_unavailable" };
5491
+ }
5492
+ if (!res.ok) {
5493
+ return { ok: false, reason: `patch_failed:${res.status}` };
5494
+ }
5495
+ return { ok: true };
5496
+ }
5267
5497
 
5268
5498
  // src/models-brain.ts
5269
5499
  function isModelRow(x) {
@@ -5397,6 +5627,7 @@ function compile2(ir, opts) {
5397
5627
  ALL_ARCHETYPES,
5398
5628
  ARCHETYPE_FLOOR_DEFAULT,
5399
5629
  CallError,
5630
+ DEFAULT_FINDINGS_ENDPOINT,
5400
5631
  DIALECT_VERSION,
5401
5632
  INTENT_ARCHETYPES,
5402
5633
  MEASURED_GROUNDING_MIN_N,
@@ -5428,11 +5659,13 @@ function compile2(ir, opts) {
5428
5659
  getReachabilityDiagnostic,
5429
5660
  getSequentialStarterChain,
5430
5661
  getSequentialStarterChainWithGrounding,
5662
+ getStaleExclusionFindings,
5431
5663
  getStarterChain,
5432
5664
  getStarterChainWithGrounding,
5433
5665
  hashShape,
5434
5666
  isArchetype,
5435
5667
  isBrainQueryActiveFor,
5668
+ isExclusionFindingsBrainActive,
5436
5669
  isModelReachable,
5437
5670
  isProviderReachable,
5438
5671
  learningKey,
@@ -5443,6 +5676,7 @@ function compile2(ir, opts) {
5443
5676
  loadModelsFromBrain,
5444
5677
  loadPricingFromBrain,
5445
5678
  markAdvisoryResolved,
5679
+ markExclusionFindingHandled,
5446
5680
  profileToRow,
5447
5681
  profilesByProvider,
5448
5682
  record,
package/dist/index.mjs CHANGED
@@ -825,6 +825,125 @@ function getArchetypePerfScore(modelId, archetype) {
825
825
  return { score, n, grounding };
826
826
  }
827
827
 
828
+ // src/exclusion-findings-brain.ts
829
+ function isValidVerdict(v) {
830
+ return v === "recommend-probe" || v === "unblock" || v === "stay-excluded" || v === "inconclusive";
831
+ }
832
+ function isValidConfidence(v) {
833
+ return v === "high" || v === "medium" || v === "low";
834
+ }
835
+ function isRawFindingRow(x) {
836
+ if (!x || typeof x !== "object") return false;
837
+ const r = x;
838
+ return typeof r.intent_archetype === "string" && typeof r.excluded_model === "string" && typeof r.excluded_provider === "string" && typeof r.verdict === "string" && typeof r.message === "string" && typeof r.suggestion === "string" && typeof r.confidence === "string";
839
+ }
840
+ function mapRowsToFindings(rows) {
841
+ const out = [];
842
+ for (const row of rows) {
843
+ if (!isRawFindingRow(row)) continue;
844
+ if (!isValidVerdict(row.verdict)) continue;
845
+ if (!isValidConfidence(row.confidence)) continue;
846
+ let savings = null;
847
+ if (typeof row.estimated_savings_usd_30d === "number") {
848
+ savings = Number.isFinite(row.estimated_savings_usd_30d) ? row.estimated_savings_usd_30d : null;
849
+ } else if (typeof row.estimated_savings_usd_30d === "string") {
850
+ const n = Number(row.estimated_savings_usd_30d);
851
+ savings = Number.isFinite(n) ? n : null;
852
+ }
853
+ out.push({
854
+ archetype: row.intent_archetype,
855
+ excludedModel: row.excluded_model,
856
+ excludedProvider: row.excluded_provider,
857
+ verdict: row.verdict,
858
+ estimatedSavingsUsd30d: savings,
859
+ message: row.message,
860
+ suggestion: row.suggestion,
861
+ confidence: row.confidence,
862
+ evidence: row.evidence && typeof row.evidence === "object" ? row.evidence : void 0
863
+ });
864
+ }
865
+ return out;
866
+ }
867
+ var snapshots = /* @__PURE__ */ new Map();
868
+ var runtime;
869
+ var warnedOnce = false;
870
+ var DEFAULT_FINDINGS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/findings/exclusions";
871
+ function configureExclusionFindingsBrain(rt) {
872
+ runtime = rt;
873
+ snapshots.clear();
874
+ warnedOnce = false;
875
+ }
876
+ function isExclusionFindingsBrainActive() {
877
+ return runtime !== void 0;
878
+ }
879
+ function getStaleExclusionFindings(opts) {
880
+ const rt = runtime;
881
+ if (!rt) return [];
882
+ const appId = opts.appId;
883
+ if (!appId) return [];
884
+ let snap = snapshots.get(appId);
885
+ if (!snap) {
886
+ snap = { data: [], expiresAt: 0, refreshing: false };
887
+ snapshots.set(appId, snap);
888
+ }
889
+ const now = Date.now();
890
+ const stale = snap.expiresAt <= now;
891
+ if (stale && !snap.refreshing) {
892
+ snap.refreshing = true;
893
+ void asyncRefresh(rt, appId);
894
+ }
895
+ if (opts.archetype) {
896
+ return snap.data.filter((f) => f.archetype === opts.archetype);
897
+ }
898
+ return snap.data;
899
+ }
900
+ var pendingRefreshes = /* @__PURE__ */ new Map();
901
+ async function asyncRefresh(rt, appId) {
902
+ const promise = doRefresh(rt, appId);
903
+ pendingRefreshes.set(appId, promise);
904
+ try {
905
+ await promise;
906
+ } finally {
907
+ if (pendingRefreshes.get(appId) === promise) {
908
+ pendingRefreshes.delete(appId);
909
+ }
910
+ }
911
+ }
912
+ async function doRefresh(rt, appId) {
913
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
914
+ let snap = snapshots.get(appId);
915
+ if (!snap) {
916
+ snap = { data: [], expiresAt: 0, refreshing: false };
917
+ snapshots.set(appId, snap);
918
+ }
919
+ try {
920
+ const res = await rt.fetchImpl(url, { method: "GET" });
921
+ if (!res.ok) {
922
+ throw new Error(`findings ${res.status}: ${res.statusText}`);
923
+ }
924
+ const body = await res.json();
925
+ if (runtime !== rt) return;
926
+ const rows = Array.isArray(body) ? mapRowsToFindings(body) : [];
927
+ snap.data = rows;
928
+ snap.expiresAt = Date.now() + rt.ttlMs;
929
+ snap.refreshing = false;
930
+ } catch (err) {
931
+ if (runtime !== rt) return;
932
+ snap.refreshing = false;
933
+ snap.expiresAt = Date.now() + rt.ttlMs;
934
+ if (!warnedOnce) {
935
+ warnedOnce = true;
936
+ (rt.onError ?? defaultOnError)(err);
937
+ }
938
+ }
939
+ }
940
+ function defaultOnError(err) {
941
+ console.warn(
942
+ "[kgauto] exclusion-findings fetch failed (using empty fallback):",
943
+ err
944
+ );
945
+ }
946
+
828
947
  // src/advisor.ts
829
948
  var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
830
949
  var TIER_DOWN_COST_RATIO = 0.5;
@@ -844,6 +963,9 @@ function runAdvisor(ir, result, profile, policy, phase2) {
844
963
  if (!translatorClearedToolCallCliff(phase2)) {
845
964
  out.push(...detectArchetypePerfFloorBreach(ir, profile));
846
965
  }
966
+ if (policy?.posture !== "locked") {
967
+ out.push(...detectStaleExclusionCandidate(ir));
968
+ }
847
969
  return out;
848
970
  }
849
971
  function translatorClearedToolCallCliff(phase2) {
@@ -1052,6 +1174,39 @@ function detectArchetypePerfFloorBreach(ir, profile) {
1052
1174
  }
1053
1175
  ];
1054
1176
  }
1177
+ function detectStaleExclusionCandidate(ir) {
1178
+ if (!isExclusionFindingsBrainActive()) return [];
1179
+ if (!ir.appId) return [];
1180
+ const findings = getStaleExclusionFindings({
1181
+ appId: ir.appId,
1182
+ archetype: ir.intent.archetype
1183
+ });
1184
+ if (findings.length === 0) return [];
1185
+ const ranked = [...findings].sort((a, b) => {
1186
+ const sa = a.estimatedSavingsUsd30d ?? -Infinity;
1187
+ const sb = b.estimatedSavingsUsd30d ?? -Infinity;
1188
+ if (sa !== sb) return sb - sa;
1189
+ return confidenceRank(b.confidence) - confidenceRank(a.confidence);
1190
+ });
1191
+ const top = ranked[0];
1192
+ const extraCount = findings.length - 1;
1193
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
1194
+ return [
1195
+ {
1196
+ level: "info",
1197
+ code: "stale-exclusion-candidate",
1198
+ message: `${top.message}${extraNote}`,
1199
+ suggestion: top.suggestion,
1200
+ recommendationType: "tier-down",
1201
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1202
+ }
1203
+ ];
1204
+ }
1205
+ function confidenceRank(c) {
1206
+ if (c === "high") return 3;
1207
+ if (c === "medium") return 2;
1208
+ return 1;
1209
+ }
1055
1210
 
1056
1211
  // src/translator.ts
1057
1212
  var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
@@ -1415,20 +1570,31 @@ function configureBrain(config) {
1415
1570
  }
1416
1571
  if (enabledTables.size === 0) {
1417
1572
  configureBrainQuery(void 0);
1418
- return;
1573
+ } else {
1574
+ configureBrainQuery({
1575
+ endpoint,
1576
+ configEndpoint: bq.configEndpoint,
1577
+ ttlMs: bq.cacheTtlMs ?? 3e5,
1578
+ fetchImpl: config.fetchImpl ?? fetch,
1579
+ enabledTables,
1580
+ onError: config.onError
1581
+ });
1582
+ }
1583
+ if (bq.findingsExclusions !== false) {
1584
+ configureExclusionFindingsBrain({
1585
+ endpoint: bq.findingsEndpoint ?? DEFAULT_FINDINGS_ENDPOINT,
1586
+ ttlMs: bq.cacheTtlMs ?? 3e5,
1587
+ fetchImpl: config.fetchImpl ?? fetch,
1588
+ onError: config.onError
1589
+ });
1590
+ } else {
1591
+ configureExclusionFindingsBrain(void 0);
1419
1592
  }
1420
- configureBrainQuery({
1421
- endpoint,
1422
- configEndpoint: bq.configEndpoint,
1423
- ttlMs: bq.cacheTtlMs ?? 3e5,
1424
- fetchImpl: config.fetchImpl ?? fetch,
1425
- enabledTables,
1426
- onError: config.onError
1427
- });
1428
1593
  }
1429
1594
  function clearBrain() {
1430
1595
  activeConfig = void 0;
1431
1596
  configureBrainQuery(void 0);
1597
+ configureExclusionFindingsBrain(void 0);
1432
1598
  }
1433
1599
  var compileRegistry = /* @__PURE__ */ new Map();
1434
1600
  var REGISTRY_MAX_ENTRIES = 1e4;
@@ -1532,7 +1698,7 @@ async function record(input) {
1532
1698
  }
1533
1699
  outcomeId = await tryExtractOutcomeId(res);
1534
1700
  } catch (err) {
1535
- (config.onError ?? defaultOnError)(err);
1701
+ (config.onError ?? defaultOnError2)(err);
1536
1702
  return;
1537
1703
  }
1538
1704
  const advisories = input.advisories ?? reg?.advisoriesFromCompile;
@@ -1554,7 +1720,7 @@ async function record(input) {
1554
1720
  throw new Error(`brain advisories ${res.status}: ${text}`);
1555
1721
  }
1556
1722
  } catch (err) {
1557
- (config.onError ?? defaultOnError)(err);
1723
+ (config.onError ?? defaultOnError2)(err);
1558
1724
  }
1559
1725
  };
1560
1726
  if (config.sync) {
@@ -1563,7 +1729,7 @@ async function record(input) {
1563
1729
  void send();
1564
1730
  }
1565
1731
  }
1566
- function defaultOnError(err) {
1732
+ function defaultOnError2(err) {
1567
1733
  console.warn("[kgauto] brain record failed:", err);
1568
1734
  }
1569
1735
  function buildPayload(input, reg) {
@@ -1695,12 +1861,12 @@ async function recordOutcome(input) {
1695
1861
  if (!res.ok) {
1696
1862
  const text = await res.text().catch(() => "<no body>");
1697
1863
  const err = new Error(`brain ${res.status}: ${text}`);
1698
- (config.onError ?? defaultOnError)(err);
1864
+ (config.onError ?? defaultOnError2)(err);
1699
1865
  return { ok: false, reason: "persistence_failed" };
1700
1866
  }
1701
1867
  return { ok: true };
1702
1868
  } catch (err) {
1703
- (config.onError ?? defaultOnError)(err);
1869
+ (config.onError ?? defaultOnError2)(err);
1704
1870
  return { ok: false, reason: "persistence_failed" };
1705
1871
  }
1706
1872
  };
@@ -2984,6 +3150,66 @@ async function markAdvisoryResolved(opts) {
2984
3150
  }
2985
3151
  return { ok: true };
2986
3152
  }
3153
+ async function markExclusionFindingHandled(opts) {
3154
+ const {
3155
+ appId,
3156
+ archetype,
3157
+ excludedModel,
3158
+ resolution,
3159
+ resolutionNote,
3160
+ brainEndpoint,
3161
+ brainJwt,
3162
+ brainAnonKey,
3163
+ fetch: injectedFetch
3164
+ } = opts;
3165
+ if (!appId) return { ok: false, reason: "app_id_required" };
3166
+ if (!archetype) return { ok: false, reason: "archetype_required" };
3167
+ if (!excludedModel) {
3168
+ return { ok: false, reason: "excluded_model_required" };
3169
+ }
3170
+ if (resolution !== "consumer-marked" && resolution !== "declined" && resolution !== "probed-unblock" && resolution !== "probed-stay-excluded") {
3171
+ return { ok: false, reason: "resolution_invalid" };
3172
+ }
3173
+ const doFetch = resolveFetch(injectedFetch);
3174
+ const base = normalizeEndpoint(brainEndpoint);
3175
+ const url = `${base}/rest/v1/exclusion_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&excluded_model=eq.${encodeURIComponent(excludedModel)}&resolved_at=is.null`;
3176
+ const patchBody = {
3177
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
3178
+ resolution_source: resolution
3179
+ };
3180
+ if (resolutionNote !== void 0) {
3181
+ patchBody.resolution_note = resolutionNote;
3182
+ }
3183
+ let res;
3184
+ try {
3185
+ res = await doFetch(url, {
3186
+ method: "PATCH",
3187
+ headers: {
3188
+ Authorization: `Bearer ${brainJwt}`,
3189
+ apikey: brainAnonKey,
3190
+ "Content-Type": "application/json",
3191
+ Accept: "application/json",
3192
+ // PATCH may match zero rows when already resolved — return=minimal
3193
+ // keeps the response body empty so the success path is uniform.
3194
+ Prefer: "return=minimal"
3195
+ },
3196
+ body: JSON.stringify(patchBody)
3197
+ });
3198
+ } catch (err) {
3199
+ const msg = err instanceof Error ? err.message : String(err);
3200
+ return { ok: false, reason: `network_error:${msg}` };
3201
+ }
3202
+ if (res.status === 401 || res.status === 403) {
3203
+ return { ok: false, reason: "brain_auth_misconfig" };
3204
+ }
3205
+ if (res.status >= 500) {
3206
+ return { ok: false, reason: "brain_unavailable" };
3207
+ }
3208
+ if (!res.ok) {
3209
+ return { ok: false, reason: `patch_failed:${res.status}` };
3210
+ }
3211
+ return { ok: true };
3212
+ }
2987
3213
 
2988
3214
  // src/models-brain.ts
2989
3215
  function isModelRow(x) {
@@ -3116,6 +3342,7 @@ export {
3116
3342
  ALL_ARCHETYPES,
3117
3343
  ARCHETYPE_FLOOR_DEFAULT,
3118
3344
  CallError,
3345
+ DEFAULT_FINDINGS_ENDPOINT,
3119
3346
  DIALECT_VERSION,
3120
3347
  INTENT_ARCHETYPES,
3121
3348
  MEASURED_GROUNDING_MIN_N,
@@ -3147,11 +3374,13 @@ export {
3147
3374
  getReachabilityDiagnostic,
3148
3375
  getSequentialStarterChain,
3149
3376
  getSequentialStarterChainWithGrounding,
3377
+ getStaleExclusionFindings,
3150
3378
  getStarterChain,
3151
3379
  getStarterChainWithGrounding,
3152
3380
  hashShape,
3153
3381
  isArchetype,
3154
3382
  isBrainQueryActiveFor,
3383
+ isExclusionFindingsBrainActive,
3155
3384
  isModelReachable,
3156
3385
  isProviderReachable,
3157
3386
  learningKey,
@@ -3162,6 +3391,7 @@ export {
3162
3391
  loadModelsFromBrain,
3163
3392
  loadPricingFromBrain,
3164
3393
  markAdvisoryResolved,
3394
+ markExclusionFindingHandled,
3165
3395
  profileToRow,
3166
3396
  profilesByProvider,
3167
3397
  record,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warmdrift/kgauto-compiler",
3
- "version": "2.0.0-alpha.37",
3
+ "version": "2.0.0-alpha.39",
4
4
  "description": "Prompt compiler + central learning brain for multi-model AI apps. Swap models without rewriting prompts.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",