Skip to main content

Multi-language narratives (W4.1)

POST /api/v1/decisions/:id/narrative now accepts a language field in the request body. Honored only when the tenant has tenantSettings.aiAnalyzerSettings.multiLanguageEnabled = true.

Supported languages

12 ISO 639-1 codes: en, es, fr, de, pt, it, nl, ja, zh, ko, hi, ar. BCP-47 tags are normalized — en-USen, es-MXes, etc. Any unrecognized locale falls back to English.

Response additions

  • language: BCP-47 language actually used after normalization.
  • quality: deterministic 0-100 score + A-F grade based on structural rules per mode (regulator narratives are checked for a 4–8 sentence count and required mentions of “offer”, “model”, and “factor”; agent narratives must parse as JSON; customer narratives must stay short and avoid numeric scores / algorithmic jargon), plus universal hype-word and fabricated-feature/offer guards.
  • leakWarnings: lines flagged as likely English-leak in non-Latin script outputs. Best-effort — never blocks delivery.

Cache key

The narrative cache key now includes the language, so the same decision trace can be cached per (tenant × trace × mode × model × language). No re-translation cost when the same narrative is requested twice.

GitOps drift detection cron (W4.4)

GET /api/v1/cron/gitops-drift-check (Bearer CRON_SECRET) sweeps every tenant nightly, comparing live production resources (ranking profiles, contact policies, decisioning gates, offers) against the last-applied YAML snapshot. Drift events are recorded in the audit log with action: "gitops_drift_detected". Note: Until the GitOps last-applied snapshot store lands, the last-applied snapshot is treated as empty — the sweep still detects added_in_prod resources but cannot detect field-level drift. When the storage migration arrives, the cron route will compare full specs. In helm/values.yaml add to the cron tier:

Admin SBOM endpoint (W4.5)

GET /api/v1/admin/sbom returns the CycloneDX 1.5 SBOM for the running deployment, computed on-demand from package-lock.json. Admin role required.
Response shape:
The X-SBOM-Digest response header carries the same digest so caching proxies can serve the right artifact. For release-time SBOM artifacts (signed alongside the Docker image), see the W3 release pipeline at .github/workflows/release.yml.

Counterfactual + LIME explanation modes (W4.2 + W4.3)

POST /api/v1/decisions/:id/narrative now accepts an explanationType field with three values: Counterfactual + LIME are gated by a separate tenant flag because they recompute against the live model on every call:

Required body for non-SHAP modes

  • modelId — must be a tenant-owned gradient-boosted algorithm model with at least one trained tree. Any other model type returns a 400.
  • attributes — same shape used at scoring time (the trace doesn’t persist raw attributes by default, so the caller passes them).
  • featureRanges — required only for counterfactual; defines the empirical p05/p95 bounds the binary search may explore.

Response shape additions

LIME response:

Honest limits

  • We do not synthesize a scorer from persisted SHAP values. If the caller doesn’t pass a real modelId + attributes, the request is rejected — we’d rather 400 than mislead.
  • Counterfactual search is single-feature, numeric-only. Multi-feature joint counterfactuals are out of scope for V1.
  • LIME samples default to 500. Bump via options.limeSamples if you need a tighter weighted R² fit.

Cache key

The narrative cache key now includes the explanationType, so each explanation primitive caches independently per (tenant × trace × mode × model × language × explanationType).

UI: NarrativeDialog tabs

<NarrativeDialog /> renders a 3-button strip inside each mode tab: SHAP / Counterfactual / LIME. Switching tabs hits the cache before re-fetching, so explanations switch instantly after the first load. When advancedDefaults are not supplied to the dialog, the Counterfactual + LIME tabs render a developer panel where the operator can paste a modelId + attributes JSON + (for counterfactual) a featureRanges JSON, then click Run. This is the same shape the SHAP endpoint already accepts.