2sagaco48 1.0.0
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/diagnostics.mjs +141 -0
- package/dist/compute.worker-j5ahIpF-.js +1 -0
- package/dist/graph.worker-DG6iGCB9.js +1 -0
- package/dist/index.html +2314 -0
- package/flatten-guide.md +248 -0
- package/index.html +12 -0
- package/package.json +38 -0
- package/src/App.orig.tsx +204 -0
- package/src/App.orig1.tsx +27 -0
- package/src/App.orig2.tsx +25 -0
- package/src/App.tsx +3 -0
- package/src/BaseApp.tsx +18 -0
- package/src/INTEGRATION_LEDGER.md +22 -0
- package/src/PERSIST_CANARY.orig.txt +1 -0
- package/src/PERSIST_CANARY.txt +1 -0
- package/src/REGRESSION_LEDGER.md +651 -0
- package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
- package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
- package/src/chat/VeritasChat.tsx +69 -0
- package/src/chat/synthesis.ts +31 -0
- package/src/chat/tier.ts +45 -0
- package/src/components/AdaptersPage.orig.tsx +99 -0
- package/src/components/AdaptersPage.tsx +3 -0
- package/src/components/AdversarialPanel.tsx +136 -0
- package/src/components/AnchorBaselinePanel.tsx +75 -0
- package/src/components/AnswerPanel.tsx +60 -0
- package/src/components/ArtifactPanel.tsx +43 -0
- package/src/components/ChatApp.orig.tsx +1582 -0
- package/src/components/ChatApp.orig1.tsx +722 -0
- package/src/components/ChatApp.tsx +95 -0
- package/src/components/ChatAugmentPanels.tsx +73 -0
- package/src/components/CitationLedgerPanel.tsx +313 -0
- package/src/components/ComputeSandboxPanel.tsx +130 -0
- package/src/components/ConfigPanel.tsx +110 -0
- package/src/components/ControlPlanePage.tsx +195 -0
- package/src/components/CreativeTreeLifePage.tsx +234 -0
- package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
- package/src/components/DebugTracePanel.tsx +137 -0
- package/src/components/DeepReasoningTrace.tsx +48 -0
- package/src/components/EntitySheetPanel.tsx +51 -0
- package/src/components/FailureModesPanel.tsx +104 -0
- package/src/components/GBSDashboard.tsx +239 -0
- package/src/components/GraphView.tsx +116 -0
- package/src/components/HUD.tsx +125 -0
- package/src/components/HypothesisPanel.tsx +67 -0
- package/src/components/InnovationGenomeEngine.tsx +792 -0
- package/src/components/InnovationPersonaGuide.tsx +348 -0
- package/src/components/InnovationPersonaPanel.tsx +289 -0
- package/src/components/LiveResourceHUD.tsx +109 -0
- package/src/components/LongWriterPanel.tsx +112 -0
- package/src/components/MainPipelineV10Bridge.tsx +72 -0
- package/src/components/MarkdownLite.tsx +63 -0
- package/src/components/MemoryInspector.tsx +38 -0
- package/src/components/MemoryMonitor.tsx +52 -0
- package/src/components/MemoryStressPanel.tsx +176 -0
- package/src/components/ModulesPage.tsx +171 -0
- package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
- package/src/components/PipelineDebugConsole.tsx +3 -0
- package/src/components/PreFlightHUD.tsx +67 -0
- package/src/components/PrismaFetchTracePanel.tsx +87 -0
- package/src/components/ReportOSPanel.tsx +165 -0
- package/src/components/ResourceEstimatorPage.tsx +327 -0
- package/src/components/RichText.tsx +181 -0
- package/src/components/SagaOsPanel.tsx +605 -0
- package/src/components/SagaOsReference.tsx +421 -0
- package/src/components/SagaOsV2Panel.tsx +594 -0
- package/src/components/SagaStyleTab.tsx +382 -0
- package/src/components/SageBlueprintPanel.tsx +416 -0
- package/src/components/SharedChatInput.tsx +77 -0
- package/src/components/StatFinancePanel.tsx +190 -0
- package/src/components/StylePersonaPanel.tsx +128 -0
- package/src/components/TemplatesPage.tsx +300 -0
- package/src/components/TestPanel.tsx +63 -0
- package/src/components/TraceLog.tsx +89 -0
- package/src/components/V15CalibrationAugment.tsx +1134 -0
- package/src/components/V15CalibrationDialog.tsx +1871 -0
- package/src/components/V15Overlay.orig.tsx +120 -0
- package/src/components/V15Overlay.tsx +4 -0
- package/src/components/V15OverlayWrapper.tsx +362 -0
- package/src/components/V15Toggle.tsx +34 -0
- package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
- package/src/console/EngineeringConsole.tsx +103 -0
- package/src/console/FictionSystemPanels.tsx +882 -0
- package/src/console/InspectorPanels.tsx +254 -0
- package/src/console/ManuscriptGraderPanel.tsx +182 -0
- package/src/console/RecursiveReviserPanel.tsx +245 -0
- package/src/console/SeedControlPanel.tsx +135 -0
- package/src/console/StoryControllerPanel.tsx +443 -0
- package/src/console/engineBridge.ts +258 -0
- package/src/console/gemini.ts +106 -0
- package/src/console/grading.ts +250 -0
- package/src/console/util.ts +45 -0
- package/src/fiction/continuity.ts +372 -0
- package/src/fiction/crash-protection.ts +111 -0
- package/src/fiction/diagnostics.ts +208 -0
- package/src/fiction/diff.ts +74 -0
- package/src/fiction/entity-state-machine.ts +214 -0
- package/src/fiction/nive-engine.ts +404 -0
- package/src/fiction/pipeline-selftest.ts +132 -0
- package/src/fiction/seed-control.ts +193 -0
- package/src/fiction/story-engine.ts +326 -0
- package/src/fiction/types.ts +68 -0
- package/src/flatten-guide.md +379 -0
- package/src/index.css +14 -0
- package/src/index.orig.css +13 -0
- package/src/lib/academic-sources.base.ts +188 -0
- package/src/lib/academic-sources.orig.ts +28 -0
- package/src/lib/academic-sources.ts +10 -0
- package/src/lib/advanced-math.ts +119 -0
- package/src/lib/adversarial-engine.base.ts +409 -0
- package/src/lib/adversarial-engine.ts +82 -0
- package/src/lib/ai-revision.ts +623 -0
- package/src/lib/app-state.tsx +426 -0
- package/src/lib/artifact-registry.ts +97 -0
- package/src/lib/artifacts.ts +180 -0
- package/src/lib/atlas-dr.ts +167 -0
- package/src/lib/browser-mtls.ts +127 -0
- package/src/lib/browser-queue.ts +221 -0
- package/src/lib/browser-search-scraper.base.ts +382 -0
- package/src/lib/browser-search-scraper.orig.ts +90 -0
- package/src/lib/browser-search-scraper.ts +10 -0
- package/src/lib/calc-interceptor.ts +82 -0
- package/src/lib/citation-lane-tap.ts +78 -0
- package/src/lib/citation-ledger-store.ts +369 -0
- package/src/lib/citation-ledger.ts +203 -0
- package/src/lib/compute-sandbox.ts +215 -0
- package/src/lib/compute.worker.ts +153 -0
- package/src/lib/connectors/gemini.ts +120 -0
- package/src/lib/connectors/jina.ts +233 -0
- package/src/lib/connectors/marketdata.ts +51 -0
- package/src/lib/connectors/prismafetch.base.ts +175 -0
- package/src/lib/connectors/prismafetch.ts +1 -0
- package/src/lib/connectors/serpapi.ts +65 -0
- package/src/lib/connectors/wikidata.ts +44 -0
- package/src/lib/constraints.ts +331 -0
- package/src/lib/continuation-detector.base.ts +153 -0
- package/src/lib/continuation-detector.ts +48 -0
- package/src/lib/contradraft.ts +168 -0
- package/src/lib/cors-proxy.ts +120 -0
- package/src/lib/coverage.ts +81 -0
- package/src/lib/debug/architecture-prescription.ts +556 -0
- package/src/lib/debug/covea-repair.ts +543 -0
- package/src/lib/debug/helios-ground.ts +319 -0
- package/src/lib/debug/intent-decomposer.ts +281 -0
- package/src/lib/debug/intent-lattice.ts +349 -0
- package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
- package/src/lib/debug/pipeline-trace-bus.ts +424 -0
- package/src/lib/debug/prompt-forge.ts +678 -0
- package/src/lib/debug/repair-sites.ts +513 -0
- package/src/lib/debug/research-phase.ts +266 -0
- package/src/lib/debug/retrieval-context.ts +76 -0
- package/src/lib/debug/retrieval-hardener.ts +142 -0
- package/src/lib/debug/scraper-debug-runner.ts +576 -0
- package/src/lib/debug/scraper-forensics.ts +418 -0
- package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
- package/src/lib/debug/self-test.ts +415 -0
- package/src/lib/debug/step-attribution.ts +211 -0
- package/src/lib/debug/template-rubric.ts +301 -0
- package/src/lib/debug/unified-innovation.ts +126 -0
- package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
- package/src/lib/defense-registry.ts +104 -0
- package/src/lib/defenses.ts +162 -0
- package/src/lib/deterministic-citation-ledger.ts +174 -0
- package/src/lib/elo-registry.base.ts +117 -0
- package/src/lib/elo-registry.ts +1 -0
- package/src/lib/entity-resolver.ts +291 -0
- package/src/lib/failure-modes.ts +267 -0
- package/src/lib/feature-registry.ts +69 -0
- package/src/lib/flaw-registry.ts +143 -0
- package/src/lib/flaws/_template.ts +52 -0
- package/src/lib/flaws/builtins.ts +63 -0
- package/src/lib/flaws/finance.ts +169 -0
- package/src/lib/flaws/fixers.ts +10 -0
- package/src/lib/flaws/index.ts +32 -0
- package/src/lib/flaws/legal.ts +157 -0
- package/src/lib/flaws/medical.ts +188 -0
- package/src/lib/flaws/original-defenses-pack.ts +81 -0
- package/src/lib/flaws/sample-declarative-pack.json +38 -0
- package/src/lib/flaws/selftest.ts +201 -0
- package/src/lib/flaws/software-extended.ts +125 -0
- package/src/lib/flaws/software-rn-webgl.ts +182 -0
- package/src/lib/flaws/statistics-advanced.ts +46 -0
- package/src/lib/flaws/statistics.ts +61 -0
- package/src/lib/gbse/config.ts +20 -0
- package/src/lib/gbse/engine.ts +226 -0
- package/src/lib/gbse/graph.ts +118 -0
- package/src/lib/gbse/graph.worker.ts +21 -0
- package/src/lib/gbse/tests.ts +117 -0
- package/src/lib/gbse/tiers.ts +65 -0
- package/src/lib/gbse/types.ts +94 -0
- package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
- package/src/lib/innovation-genome-engine-v2.ts +3 -0
- package/src/lib/innovation-genome-engine.orig.ts +330 -0
- package/src/lib/innovation-genome-engine.ts +3 -0
- package/src/lib/innovation-genome-v10.orig.ts +314 -0
- package/src/lib/innovation-genome-v10.ts +2 -0
- package/src/lib/innovation-genome-v3.orig.ts +2015 -0
- package/src/lib/innovation-genome-v3.ts +2 -0
- package/src/lib/innovation-genome-v4.orig.ts +1959 -0
- package/src/lib/innovation-genome-v4.ts +2 -0
- package/src/lib/innovation-genome-v5.orig.ts +1210 -0
- package/src/lib/innovation-genome-v5.ts +2 -0
- package/src/lib/innovation-genome-v7.orig.ts +2549 -0
- package/src/lib/innovation-genome-v7.ts +2 -0
- package/src/lib/innovation-genome-v8.orig.ts +1485 -0
- package/src/lib/innovation-genome-v8.ts +2 -0
- package/src/lib/innovation-genome-v9.orig.ts +643 -0
- package/src/lib/innovation-genome-v9.ts +2 -0
- package/src/lib/jina.base.ts +293 -0
- package/src/lib/jina.ts +1 -0
- package/src/lib/live-telemetry.ts +61 -0
- package/src/lib/longwriter.ts +84 -0
- package/src/lib/manuscript-grade-v2.ts +298 -0
- package/src/lib/memory-governor.ts +173 -0
- package/src/lib/memory-stress-tests.ts +248 -0
- package/src/lib/model-intelligence.ts +48 -0
- package/src/lib/model-rotator.ts +75 -0
- package/src/lib/models.orig.ts +513 -0
- package/src/lib/models.ts +151 -0
- package/src/lib/n-deep.base.ts +592 -0
- package/src/lib/n-deep.ts +51 -0
- package/src/lib/nih-simulator.ts +56 -0
- package/src/lib/nih-vulnerability-fixes.ts +460 -0
- package/src/lib/omega-templates.base.ts +294 -0
- package/src/lib/omega-templates.ts +225 -0
- package/src/lib/omni-nexus.ts +64 -0
- package/src/lib/oracle-adapters.ts +67 -0
- package/src/lib/oracle-registry.ts +163 -0
- package/src/lib/orchestrator.ts +357 -0
- package/src/lib/output-boundary.ts +235 -0
- package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
- package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
- package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
- package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
- package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
- package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
- package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
- package/src/lib/overrides/vite-native-scraper.ts +848 -0
- package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
- package/src/lib/overrides/vite-native-selftest.ts +183 -0
- package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
- package/src/lib/philosophy-toolkit.ts +75 -0
- package/src/lib/pipeline.orig.ts +659 -0
- package/src/lib/pipeline.ts +42 -0
- package/src/lib/precache.ts +128 -0
- package/src/lib/py-sandbox.ts +205 -0
- package/src/lib/quality-score.ts +96 -0
- package/src/lib/quant-engine.ts +231 -0
- package/src/lib/quant-lib.ts +80 -0
- package/src/lib/reportos.ts +175 -0
- package/src/lib/research-os.ts +306 -0
- package/src/lib/resource-estimator.ts +114 -0
- package/src/lib/rpm-governor.ts +93 -0
- package/src/lib/runtime-estimator.ts +135 -0
- package/src/lib/sage/empty-space-v6.ts +560 -0
- package/src/lib/sage/empty-space-v7.ts +222 -0
- package/src/lib/sage/empty-space.ts +733 -0
- package/src/lib/sage/engine.ts +786 -0
- package/src/lib/sage/index.ts +17 -0
- package/src/lib/sage/judge.ts +246 -0
- package/src/lib/sage/prng.ts +53 -0
- package/src/lib/sage/reality-grounding.ts +380 -0
- package/src/lib/sage/repetition.ts +151 -0
- package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
- package/src/lib/sage/saga-os-v2.ts +274 -0
- package/src/lib/sage/saga-os-v3-core.ts +1491 -0
- package/src/lib/sage/saga-os-v3-release.ts +739 -0
- package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
- package/src/lib/sage/saga-os-v4.ts +212 -0
- package/src/lib/sage/saga-os-v5.ts +205 -0
- package/src/lib/sage/saga-os-v6.ts +346 -0
- package/src/lib/sage/saga-os-v7.ts +314 -0
- package/src/lib/sage/saga-os-v8.ts +505 -0
- package/src/lib/sage/saga-os.ts +329 -0
- package/src/lib/sage/taxonomy.ts +403 -0
- package/src/lib/sage/unified-roll.ts +524 -0
- package/src/lib/sage/worked-sketches.ts +44 -0
- package/src/lib/sage.ts +166 -0
- package/src/lib/scraper-debug-bus.ts +53 -0
- package/src/lib/scraper-enhanced.orig.ts +153 -0
- package/src/lib/scraper-enhanced.ts +10 -0
- package/src/lib/scraper-hardener.base.orig.ts +155 -0
- package/src/lib/scraper-hardener.base.ts +317 -0
- package/src/lib/scraper-hardener.ts +1 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
- package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
- package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
- package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
- package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
- package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
- package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
- package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
- package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
- package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
- package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
- package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
- package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
- package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
- package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
- package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
- package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
- package/src/lib/scraper-vnext/query-strategist.ts +423 -0
- package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
- package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
- package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
- package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
- package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
- package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
- package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
- package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
- package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
- package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
- package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
- package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
- package/src/lib/scraper-vnext/strata-engine.ts +13 -0
- package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
- package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
- package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
- package/src/lib/scraper-vnext/terminal-final.ts +277 -0
- package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
- package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
- package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
- package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
- package/src/lib/search-cache.ts +138 -0
- package/src/lib/sloop-runner.base.ts +223 -0
- package/src/lib/sloop-runner.ts +104 -0
- package/src/lib/sloop.ts +114 -0
- package/src/lib/small-model-adapters.ts +82 -0
- package/src/lib/sscp.ts +86 -0
- package/src/lib/system-map.ts +54 -0
- package/src/lib/uni-node.ts +86 -0
- package/src/lib/universal-rigor-guard.ts +433 -0
- package/src/lib/v15-gate-testbed.ts +274 -0
- package/src/lib/v15-gemini.base.ts +89 -0
- package/src/lib/v15-gemini.ts +1 -0
- package/src/lib/v15-grounding.orig.ts +241 -0
- package/src/lib/v15-grounding.ts +79 -0
- package/src/lib/v15-pipeline.base.ts +769 -0
- package/src/lib/v15-pipeline.orig.ts +1154 -0
- package/src/lib/v15-pipeline.ts +674 -0
- package/src/lib/v15-questions.ts +64 -0
- package/src/lib/v15-rate-limiter.orig.ts +91 -0
- package/src/lib/v15-rate-limiter.ts +48 -0
- package/src/lib/v15-state.base.ts +87 -0
- package/src/lib/v15-state.ts +28 -0
- package/src/lib/visual-table-generator.tsx +207 -0
- package/src/lib/wasm-runtime.ts +133 -0
- package/src/lib/williams-persona-guide.ts +265 -0
- package/src/lib/williams-style.base.ts +464 -0
- package/src/lib/williams-style.ts +59 -0
- package/src/lib/worker-pool.ts +96 -0
- package/src/lib/writing-tiers.ts +125 -0
- package/src/main.orig.tsx +10 -0
- package/src/main.tsx +10 -0
- package/src/utils/cn.orig.ts +6 -0
- package/src/utils/cn.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/unify.mjs +537 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* citation-ledger-store.ts — LIVE, workspace-durable citation provenance store
|
|
3
|
+
* ============================================================================
|
|
4
|
+
* WHY THIS EXISTS
|
|
5
|
+
* The package already ships two ledger implementations:
|
|
6
|
+
* - `citation-ledger.ts` (stochastic LLM-entailment audit)
|
|
7
|
+
* - `deterministic-citation-ledger.ts` (set-membership audit; honest)
|
|
8
|
+
* Neither is a *live, observable* store: they are built and discarded inside a
|
|
9
|
+
* single pipeline call, so no UI surface can subscribe to them.
|
|
10
|
+
*
|
|
11
|
+
* This module adds the missing seam ONLY — a tiny synchronous pub/sub registry
|
|
12
|
+
* that records every source the grounding fleet actually fetched, together with
|
|
13
|
+
* the pipeline stage at which it was FIRST admitted into the draft. It performs
|
|
14
|
+
* no network calls, no LLM calls, and invents nothing: every record originates
|
|
15
|
+
* from a real `groundQuestion` return value.
|
|
16
|
+
*
|
|
17
|
+
* Determinism: `[S#]` ids are assigned in first-seen order over the canonical
|
|
18
|
+
* URL, identical to `deterministic-citation-ledger.buildDeterministicLedger`,
|
|
19
|
+
* so tags emitted here resolve to the same sources the pipeline cited.
|
|
20
|
+
* ============================================================================ */
|
|
21
|
+
|
|
22
|
+
export type CitationStage =
|
|
23
|
+
| "initial"
|
|
24
|
+
| "grounding"
|
|
25
|
+
| "hdig"
|
|
26
|
+
| "cove"
|
|
27
|
+
| "n-deep"
|
|
28
|
+
| "adversarial"
|
|
29
|
+
| "synthesis";
|
|
30
|
+
|
|
31
|
+
export interface LiveCitationRecord {
|
|
32
|
+
/** Sequential 1-based id — renders as `[S{id}]`. */
|
|
33
|
+
id: number;
|
|
34
|
+
/** Page title as returned by the fetching lane. */
|
|
35
|
+
title: string;
|
|
36
|
+
/** Canonicalised absolute URL. */
|
|
37
|
+
url: string;
|
|
38
|
+
/** Hostname without `www.` — the "scraped site" column. */
|
|
39
|
+
site: string;
|
|
40
|
+
/** Verbatim leading text the model was shown (truncated, never rewritten). */
|
|
41
|
+
snippet: string;
|
|
42
|
+
/** Deterministic FNV-1a fingerprint of url + title + snippet head. */
|
|
43
|
+
fingerprint: string;
|
|
44
|
+
/** Originating scraper lane (provenance). */
|
|
45
|
+
lane: string;
|
|
46
|
+
/** Pipeline stage at which this source was FIRST integrated into the draft. */
|
|
47
|
+
stage: CitationStage;
|
|
48
|
+
/** Epoch ms of first admission. */
|
|
49
|
+
firstSeenAt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CitationLedgerSnapshotLive {
|
|
53
|
+
records: LiveCitationRecord[];
|
|
54
|
+
/** Distinct hostnames represented. */
|
|
55
|
+
siteCount: number;
|
|
56
|
+
/** Question that produced the most recent ingest, if supplied. */
|
|
57
|
+
lastQuestion: string;
|
|
58
|
+
/** Epoch ms of the most recent ingest. */
|
|
59
|
+
updatedAt: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Listener = (snapshot: CitationLedgerSnapshotLive) => void;
|
|
63
|
+
|
|
64
|
+
function fnv1a(input: string): string {
|
|
65
|
+
let h = 0x811c9dc5;
|
|
66
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
67
|
+
h ^= input.charCodeAt(i);
|
|
68
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
|
|
69
|
+
}
|
|
70
|
+
return h.toString(16).padStart(8, "0");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function canonicalUrl(url: string): string {
|
|
74
|
+
try {
|
|
75
|
+
const u = new URL(url);
|
|
76
|
+
u.hash = "";
|
|
77
|
+
u.hostname = u.hostname.toLowerCase().replace(/^www\./, "");
|
|
78
|
+
return u.toString();
|
|
79
|
+
} catch {
|
|
80
|
+
return (url || "").trim();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hostnameOf(url: string): string {
|
|
85
|
+
try {
|
|
86
|
+
return new URL(url).hostname.replace(/^www\./, "");
|
|
87
|
+
} catch {
|
|
88
|
+
return "(local)";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const byUrl = new Map<string, LiveCitationRecord>();
|
|
93
|
+
const listeners = new Set<Listener>();
|
|
94
|
+
let lastQuestion = "";
|
|
95
|
+
let updatedAt = 0;
|
|
96
|
+
|
|
97
|
+
function snapshot(): CitationLedgerSnapshotLive {
|
|
98
|
+
const records = Array.from(byUrl.values()).sort((a, b) => a.id - b.id);
|
|
99
|
+
return {
|
|
100
|
+
records,
|
|
101
|
+
siteCount: new Set(records.map((r) => r.site)).size,
|
|
102
|
+
lastQuestion,
|
|
103
|
+
updatedAt,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function emit(): void {
|
|
108
|
+
const snap = snapshot();
|
|
109
|
+
for (const listener of listeners) {
|
|
110
|
+
try {
|
|
111
|
+
listener(snap);
|
|
112
|
+
} catch {
|
|
113
|
+
/* a failing subscriber must never break ingestion */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Subscribe to ledger changes. Fires immediately with the current snapshot. */
|
|
119
|
+
export function subscribeCitationLedger(listener: Listener): () => void {
|
|
120
|
+
listeners.add(listener);
|
|
121
|
+
try {
|
|
122
|
+
listener(snapshot());
|
|
123
|
+
} catch {
|
|
124
|
+
/* ignore */
|
|
125
|
+
}
|
|
126
|
+
return () => {
|
|
127
|
+
listeners.delete(listener);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Current snapshot without subscribing. */
|
|
132
|
+
export function getCitationLedgerSnapshot(): CitationLedgerSnapshotLive {
|
|
133
|
+
return snapshot();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Record sources that were ACTUALLY fetched. Idempotent per canonical URL:
|
|
138
|
+
* re-ingesting a known URL never renumbers it and never overwrites the stage
|
|
139
|
+
* at which it first entered the draft (that is the auditable fact).
|
|
140
|
+
*/
|
|
141
|
+
export function recordCitationSources(
|
|
142
|
+
sources: Array<{ title?: string; url?: string; content?: string; lane?: string }>,
|
|
143
|
+
options: { stage?: CitationStage; lane?: string; question?: string } = {},
|
|
144
|
+
): LiveCitationRecord[] {
|
|
145
|
+
const stage = options.stage ?? "grounding";
|
|
146
|
+
const added: LiveCitationRecord[] = [];
|
|
147
|
+
|
|
148
|
+
for (const source of sources) {
|
|
149
|
+
const url = canonicalUrl(source.url || "");
|
|
150
|
+
if (!url) continue;
|
|
151
|
+
|
|
152
|
+
const existing = byUrl.get(url);
|
|
153
|
+
if (existing) {
|
|
154
|
+
// STAGE REFINEMENT (single, narrow rule).
|
|
155
|
+
// `grounding` means "fetched by a lane, not yet known to be admitted to
|
|
156
|
+
// the draft". Any non-`grounding` stage comes from the pipeline's own
|
|
157
|
+
// CitationLedger, which stamps the stage at FIRST draft admission — a
|
|
158
|
+
// strictly more precise fact. Upgrade once, never downgrade, never
|
|
159
|
+
// renumber. Everything else about the record is left untouched.
|
|
160
|
+
if (existing.stage === "grounding" && stage !== "grounding") {
|
|
161
|
+
existing.stage = stage;
|
|
162
|
+
updatedAt = Date.now();
|
|
163
|
+
emit();
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const title = (source.title || "Untitled").trim().slice(0, 250);
|
|
169
|
+
const snippet = (source.content || "").replace(/\s+/g, " ").trim().slice(0, 600);
|
|
170
|
+
const record: LiveCitationRecord = {
|
|
171
|
+
id: byUrl.size + 1,
|
|
172
|
+
title,
|
|
173
|
+
url,
|
|
174
|
+
site: hostnameOf(url),
|
|
175
|
+
snippet,
|
|
176
|
+
fingerprint: fnv1a(`${url}\u0000${title}\u0000${snippet.slice(0, 400)}`),
|
|
177
|
+
lane: source.lane || options.lane || "grounding",
|
|
178
|
+
stage,
|
|
179
|
+
firstSeenAt: Date.now(),
|
|
180
|
+
};
|
|
181
|
+
byUrl.set(url, record);
|
|
182
|
+
added.push(record);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof options.question === "string" && options.question.trim()) {
|
|
186
|
+
lastQuestion = options.question.trim().slice(0, 400);
|
|
187
|
+
}
|
|
188
|
+
if (added.length > 0 || options.question) {
|
|
189
|
+
updatedAt = Date.now();
|
|
190
|
+
emit();
|
|
191
|
+
}
|
|
192
|
+
return added;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Promote a source to a later stage only if that stage is genuinely later. */
|
|
196
|
+
const STAGE_ORDER: CitationStage[] = [
|
|
197
|
+
"initial",
|
|
198
|
+
"grounding",
|
|
199
|
+
"hdig",
|
|
200
|
+
"cove",
|
|
201
|
+
"n-deep",
|
|
202
|
+
"adversarial",
|
|
203
|
+
"synthesis",
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
export function markCitationStage(id: number, stage: CitationStage): boolean {
|
|
207
|
+
for (const record of byUrl.values()) {
|
|
208
|
+
if (record.id !== id) continue;
|
|
209
|
+
if (STAGE_ORDER.indexOf(stage) <= STAGE_ORDER.indexOf(record.stage)) return false;
|
|
210
|
+
record.stage = stage;
|
|
211
|
+
updatedAt = Date.now();
|
|
212
|
+
emit();
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Clear the ledger (new session / explicit user reset). */
|
|
219
|
+
export function clearCitationLedger(): void {
|
|
220
|
+
byUrl.clear();
|
|
221
|
+
lastQuestion = "";
|
|
222
|
+
updatedAt = Date.now();
|
|
223
|
+
emit();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function getCitationStyle(): string {
|
|
227
|
+
try {
|
|
228
|
+
return localStorage.getItem("veritas.v15.citationStyle") || "APA";
|
|
229
|
+
} catch {
|
|
230
|
+
return "APA";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function formatCitationInline(record: LiveCitationRecord, style: string = "APA"): string {
|
|
235
|
+
const s = style.toUpperCase();
|
|
236
|
+
// We don't have author/year parsed, so we use site + title as proxy
|
|
237
|
+
// But we format the tag wrapper according to style
|
|
238
|
+
switch (s) {
|
|
239
|
+
case "APA":
|
|
240
|
+
return `[S${record.id}]`; // final formatted references will be APA; inline we keep S# for audit but instruct model to add (Site, Year) if available
|
|
241
|
+
case "MLA":
|
|
242
|
+
return `[S${record.id}]`;
|
|
243
|
+
case "CHICAGO":
|
|
244
|
+
return `[S${record.id}]`;
|
|
245
|
+
case "IEEE":
|
|
246
|
+
return `[${record.id}]`; // IEEE uses numeric brackets
|
|
247
|
+
case "AMA":
|
|
248
|
+
return `[S${record.id}]`;
|
|
249
|
+
default:
|
|
250
|
+
return `[S${record.id}]`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function formatReferenceEntry(record: LiveCitationRecord, style: string = "APA"): string {
|
|
255
|
+
const s = style.toUpperCase();
|
|
256
|
+
const title = record.title || "Untitled";
|
|
257
|
+
const site = record.site || "Unknown site";
|
|
258
|
+
const url = record.url;
|
|
259
|
+
const year = new Date(record.firstSeenAt).getFullYear();
|
|
260
|
+
const accessDate = new Date(record.firstSeenAt).toISOString().split("T")[0];
|
|
261
|
+
|
|
262
|
+
// Extract potential author from title or snippet if possible (very basic)
|
|
263
|
+
// For now use site as corporate author fallback
|
|
264
|
+
|
|
265
|
+
switch (s) {
|
|
266
|
+
case "APA":
|
|
267
|
+
// APA 7: Site. (Year). Title. URL
|
|
268
|
+
return `[S${record.id}] ${site}. (${year}). ${title}. Retrieved ${accessDate}, from ${url}`;
|
|
269
|
+
case "MLA":
|
|
270
|
+
// MLA 9: "Title." Site, Day Month Year, URL. Accessed Day Month Year.
|
|
271
|
+
return `[S${record.id}] "${title}." ${site}, ${year}, ${url}. Accessed ${accessDate}.`;
|
|
272
|
+
case "CHICAGO":
|
|
273
|
+
// Chicago 17 author-date: Site. Year. "Title." Accessed Month Day, Year. URL.
|
|
274
|
+
return `[S${record.id}] ${site}. ${year}. "${title}." Accessed ${accessDate}. ${url}.`;
|
|
275
|
+
case "IEEE":
|
|
276
|
+
// IEEE: [id] "Title," Site, Year. [Online]. Available: URL
|
|
277
|
+
return `[${record.id}] "${title}," ${site}, ${year}. [Online]. Available: ${url}`;
|
|
278
|
+
case "AMA":
|
|
279
|
+
// AMA: Title. Site. Published Year. Accessed Month Day, Year. URL
|
|
280
|
+
return `[S${record.id}] ${title}. ${site}. Published ${year}. Accessed ${accessDate}. ${url}`;
|
|
281
|
+
default:
|
|
282
|
+
return `[S${record.id}] ${title} — ${site} (${url})`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The mandatory retrieve-before-generate contract injected into every prompt
|
|
288
|
+
* that has a populated ledger. Forces verbatim quotation and strict paraphrase
|
|
289
|
+
* bound to real fetched text, which is precisely what makes the deterministic
|
|
290
|
+
* citation audit and CoVe pass by construction rather than by luck.
|
|
291
|
+
*
|
|
292
|
+
* Now enhanced with:
|
|
293
|
+
* - Citation style awareness (APA default, respects Cite dropdown)
|
|
294
|
+
* - Verbatim quote enforcement with source-first ordering
|
|
295
|
+
* - True inline citation requirements
|
|
296
|
+
*/
|
|
297
|
+
export function buildQuotationContract(records: LiveCitationRecord[]): string {
|
|
298
|
+
if (records.length === 0) return "";
|
|
299
|
+
const style = getCitationStyle();
|
|
300
|
+
const inventory = records
|
|
301
|
+
.map((r) => formatReferenceEntry(r, style))
|
|
302
|
+
.join("\n");
|
|
303
|
+
|
|
304
|
+
const inlineExample = style.toUpperCase() === "IEEE"
|
|
305
|
+
? `Example: "The market grew 23% in 2024" [1] or for strict paraphrase: Market growth was 23% in 2024 [1].`
|
|
306
|
+
: `Example: "The market grew 23% in 2024" [S1] or for strict paraphrase: Market growth was 23% in 2024 [S1].`;
|
|
307
|
+
|
|
308
|
+
return [
|
|
309
|
+
`## SOURCE-FIRST QUOTATION CONTRACT (mandatory, non-negotiable) — ${style.toUpperCase()} STYLE`,
|
|
310
|
+
"",
|
|
311
|
+
"You have been given the full text of every source below. These are the ONLY",
|
|
312
|
+
"admissible external facts. Read them BEFORE composing any sentence.",
|
|
313
|
+
"",
|
|
314
|
+
`Active citation style: ${style.toUpperCase()} (from Cite dropdown, default APA).`,
|
|
315
|
+
`Format ALL citations and References section according to ${style.toUpperCase()} rules.`,
|
|
316
|
+
"",
|
|
317
|
+
"Order of operations — do not reorder:",
|
|
318
|
+
" 1. Read EVERY source passage supplied in the evidence block.",
|
|
319
|
+
" 2. For each fact you plan to use, locate the EXACT sentence in source text.",
|
|
320
|
+
" 3. Extract verbatim quotes (character-identical, in double quotes) for load-bearing facts.",
|
|
321
|
+
" 4. Only then compose the answer from those extracted sentences.",
|
|
322
|
+
"",
|
|
323
|
+
"VERBATIM QUOTE REQUIREMENTS (enforced by audit):",
|
|
324
|
+
" - Every section MUST contain at least 2 verbatim quotes (\"exact text from source\" with [S#])",
|
|
325
|
+
" - Quotes must be <= 20 words, character-identical to source, in double quotes",
|
|
326
|
+
" - Quote the most important facts, numbers, dates, names directly from sources",
|
|
327
|
+
" - Example of valid verbatim: The report states \"cannabis market reached $13.2B in 2023\" [S3]",
|
|
328
|
+
" - Invalid (paraphrased as quote): \"market is big\" — this is NOT verbatim if source says \"market reached $13.2B\"",
|
|
329
|
+
"",
|
|
330
|
+
"TRUE INLINE CITATION REQUIREMENTS:",
|
|
331
|
+
" - Every externally-sourced factual sentence MUST carry an inline [S#] tag",
|
|
332
|
+
" drawn from the inventory below. Tags outside this inventory are invalid.",
|
|
333
|
+
" - Place citation IMMEDIATELY after the fact, not at end of paragraph:",
|
|
334
|
+
" GOOD: \"Revenue grew 23% to $4.2M\" [S2].",
|
|
335
|
+
" BAD: Revenue grew 23% to $4.2M. Market is large. Future looks good [S2]. (which fact does S2 support?)",
|
|
336
|
+
" - For each claim, the [S#] must point to a passage that EXPLICITLY contains that claim",
|
|
337
|
+
" - Multiple facts from same source in one sentence: single [S#] at end is OK",
|
|
338
|
+
" - Facts from different sources: cite each: Claim A [S1] and claim B [S2].",
|
|
339
|
+
" - " + inlineExample,
|
|
340
|
+
"",
|
|
341
|
+
"BINDING RULES:",
|
|
342
|
+
" - For each load-bearing fact, either quote verbatim in double quotes",
|
|
343
|
+
" (<= 20 words, character-identical) OR paraphrase strictly: same entities,",
|
|
344
|
+
" same numbers, same units, same time reference, same polarity,",
|
|
345
|
+
" no widened scope, no added causation.",
|
|
346
|
+
" - Never assert a number, date, name, or statistic that does not appear",
|
|
347
|
+
" literally in a supplied passage. If absent, write [UNVERIFIED].",
|
|
348
|
+
" - Never cite a source for a claim it does not state. A [S#] tag asserts",
|
|
349
|
+
" that the cited passage contains the claim verbatim or as strict paraphrase.",
|
|
350
|
+
" - If sources do not answer part of question, say so explicitly rather than",
|
|
351
|
+
" filling gap from prior knowledge.",
|
|
352
|
+
" - References section at end MUST follow " + style.toUpperCase() + " formatting:",
|
|
353
|
+
...(style.toUpperCase() === "APA" ? [
|
|
354
|
+
" APA Format: Author. (Year). Title. Site. URL",
|
|
355
|
+
" Example: Leafly. (2024). Cannabis Market Report 2023. Leafly. https://leafly.com/report",
|
|
356
|
+
] : style.toUpperCase() === "MLA" ? [
|
|
357
|
+
" MLA Format: \"Title.\" Site, Year, URL. Accessed Date.",
|
|
358
|
+
] : style.toUpperCase() === "IEEE" ? [
|
|
359
|
+
" IEEE Format: [id] \"Title,\" Site, Year. [Online]. Available: URL",
|
|
360
|
+
] : [
|
|
361
|
+
` ${style.toUpperCase()} Format: See inventory below for example format`,
|
|
362
|
+
]),
|
|
363
|
+
"",
|
|
364
|
+
"SOURCE INVENTORY (valid citation tags — use ONLY these):",
|
|
365
|
+
inventory,
|
|
366
|
+
"",
|
|
367
|
+
`AUDIT WILL FAIL IF: fewer than 2 verbatim quotes per section, or citations not matching ${style.toUpperCase()} References, or [S#] tags not in inventory.`,
|
|
368
|
+
].join("\n");
|
|
369
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Citation Ledger — auditable, per-source evidence provenance.
|
|
3
|
+
*
|
|
4
|
+
* Every retrieved source gets a stable entry with:
|
|
5
|
+
* - sequential [S#] tag
|
|
6
|
+
* - title, URL, verbatim snippet (the exact text the LLM saw)
|
|
7
|
+
* - content hash (SHA-like fingerprint for tamper detection)
|
|
8
|
+
* - retrieval stage (initial grounding, HDIG, CoVe, N-Deep re-ground)
|
|
9
|
+
* - timestamp
|
|
10
|
+
*
|
|
11
|
+
* The ledger is threaded through the entire pipeline so every LLM call
|
|
12
|
+
* that uses evidence cites from the SAME stable ID space, and the final
|
|
13
|
+
* `auditCitations()` function verifies whether each [S#] tag in the
|
|
14
|
+
* output has a matching ledger entry and whether the cited passage
|
|
15
|
+
* supports the claim (via substring overlap — no LLM call needed for
|
|
16
|
+
* the basic trust check).
|
|
17
|
+
*
|
|
18
|
+
* The UI receives the full ledger for one-click audit: clicking any
|
|
19
|
+
* [S#] tag reveals the exact snippet, URL, retrieval stage, and hash.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface CitationEntry {
|
|
23
|
+
id: number; // sequential, 1-based — matches [S{id}]
|
|
24
|
+
title: string;
|
|
25
|
+
url: string;
|
|
26
|
+
snippet: string; // verbatim text the LLM was shown
|
|
27
|
+
hash: string; // content fingerprint
|
|
28
|
+
stage: "initial" | "hdig" | "cove" | "n-deep" | "adversarial";
|
|
29
|
+
timestamp: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CitationAuditResult {
|
|
33
|
+
tag: string; // e.g. "[S3]"
|
|
34
|
+
id: number;
|
|
35
|
+
found: boolean; // true if ledger has an entry for this ID
|
|
36
|
+
entry?: CitationEntry; // the ledger entry (if found)
|
|
37
|
+
claimContext: string; // the ~200 char context around the [S#] in the output
|
|
38
|
+
snippetOverlap: number; // 0-1 — fraction of claim context words found in source snippet
|
|
39
|
+
trusted: boolean; // true if found AND snippetOverlap >= 0.15 (at least some words match)
|
|
40
|
+
entailmentScore?: number; // 0.0-1.0 score from LLM entailment check (if run)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CitationLedgerSnapshot {
|
|
44
|
+
entries: CitationEntry[];
|
|
45
|
+
auditResults: CitationAuditResult[];
|
|
46
|
+
totalCitations: number;
|
|
47
|
+
trustedCount: number;
|
|
48
|
+
untrustedCount: number;
|
|
49
|
+
missingCount: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function quickHash(text: string): string {
|
|
53
|
+
let h = 0x811c9dc5;
|
|
54
|
+
for (let i = 0; i < text.length; i++) {
|
|
55
|
+
h ^= text.charCodeAt(i);
|
|
56
|
+
h = Math.imul(h, 0x01000193);
|
|
57
|
+
}
|
|
58
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class CitationLedger {
|
|
62
|
+
private entries: CitationEntry[] = [];
|
|
63
|
+
|
|
64
|
+
/** Add a source. Returns the assigned [S#] ID. */
|
|
65
|
+
addSource(source: { title: string; url: string; content: string }, stage: CitationEntry["stage"]): number {
|
|
66
|
+
const id = this.entries.length + 1;
|
|
67
|
+
this.entries.push({
|
|
68
|
+
id,
|
|
69
|
+
title: source.title.slice(0, 200),
|
|
70
|
+
url: source.url,
|
|
71
|
+
snippet: source.content.slice(0, 1200),
|
|
72
|
+
hash: quickHash(source.content),
|
|
73
|
+
stage,
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
return id;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Add multiple sources and return the starting ID. */
|
|
80
|
+
addSources(sources: Array<{ title: string; url: string; content: string }>, stage: CitationEntry["stage"]): number {
|
|
81
|
+
const startId = this.entries.length + 1;
|
|
82
|
+
for (const s of sources) this.addSource(s, stage);
|
|
83
|
+
return startId;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Get the current count. */
|
|
87
|
+
get count(): number { return this.entries.length; }
|
|
88
|
+
|
|
89
|
+
/** Get a specific entry by ID. */
|
|
90
|
+
get(id: number): CitationEntry | undefined { return this.entries.find(e => e.id === id); }
|
|
91
|
+
|
|
92
|
+
/** Build the evidence block string for LLM injection. */
|
|
93
|
+
buildEvidenceBlock(provider: string): string {
|
|
94
|
+
if (!this.entries.length) return "";
|
|
95
|
+
return `LIVE RETRIEVED EVIDENCE (${provider}, ${this.entries.length} sources; cite only [S1]-[S${this.entries.length}]):\n` +
|
|
96
|
+
this.entries.map(e => `[S${e.id}] ${e.title}\nURL: ${e.url}\n${e.snippet}`).join("\n---\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Audit all [S#] citations in the output text against this ledger. */
|
|
100
|
+
auditCitations(outputText: string): CitationLedgerSnapshot {
|
|
101
|
+
const tags = [...new Set((outputText.match(/\[S(\d+)\]/g) ?? []))];
|
|
102
|
+
const auditResults: CitationAuditResult[] = [];
|
|
103
|
+
|
|
104
|
+
for (const tag of tags) {
|
|
105
|
+
const idMatch = tag.match(/\d+/);
|
|
106
|
+
if (!idMatch) continue;
|
|
107
|
+
const id = parseInt(idMatch[0], 10);
|
|
108
|
+
const entry = this.get(id);
|
|
109
|
+
|
|
110
|
+
// Extract ~200 chars of context around the citation
|
|
111
|
+
const tagPos = outputText.indexOf(tag);
|
|
112
|
+
const contextStart = Math.max(0, tagPos - 100);
|
|
113
|
+
const contextEnd = Math.min(outputText.length, tagPos + tag.length + 100);
|
|
114
|
+
const claimContext = outputText.slice(contextStart, contextEnd);
|
|
115
|
+
|
|
116
|
+
let snippetOverlap = 0;
|
|
117
|
+
if (entry) {
|
|
118
|
+
// Compute word overlap between the claim context and the source snippet
|
|
119
|
+
const claimWords = new Set(claimContext.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter(w => w.length > 3));
|
|
120
|
+
const snippetWords = new Set(entry.snippet.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter(w => w.length > 3));
|
|
121
|
+
if (claimWords.size > 0) {
|
|
122
|
+
let overlap = 0;
|
|
123
|
+
for (const w of claimWords) { if (snippetWords.has(w)) overlap++; }
|
|
124
|
+
snippetOverlap = overlap / claimWords.size;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
auditResults.push({
|
|
129
|
+
tag,
|
|
130
|
+
id,
|
|
131
|
+
found: !!entry,
|
|
132
|
+
entry,
|
|
133
|
+
claimContext,
|
|
134
|
+
snippetOverlap: Math.round(snippetOverlap * 100) / 100,
|
|
135
|
+
trusted: !!entry && snippetOverlap >= 0.15,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const trustedCount = auditResults.filter(r => r.trusted).length;
|
|
140
|
+
const missingCount = auditResults.filter(r => !r.found).length;
|
|
141
|
+
const untrustedCount = auditResults.filter(r => r.found && !r.trusted).length;
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
entries: [...this.entries],
|
|
145
|
+
auditResults,
|
|
146
|
+
totalCitations: tags.length,
|
|
147
|
+
trustedCount,
|
|
148
|
+
untrustedCount,
|
|
149
|
+
missingCount,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Run a structured LLM entailment check on untrusted citations. */
|
|
154
|
+
async verifyEntailment(auditSnapshot: CitationLedgerSnapshot, apiKey: string, model: string, onProgress?: (msg: string) => void): Promise<CitationLedgerSnapshot> {
|
|
155
|
+
const untrusted = auditSnapshot.auditResults.filter(r => r.found && !r.trusted);
|
|
156
|
+
if (!untrusted.length) return auditSnapshot;
|
|
157
|
+
|
|
158
|
+
onProgress?.(`Citation Ledger: running LLM entailment check on ${untrusted.length} low-overlap citation(s)`);
|
|
159
|
+
|
|
160
|
+
for (const r of untrusted) {
|
|
161
|
+
if (!r.entry) continue;
|
|
162
|
+
const prompt = `You are a strict citation entailment judge. Verify if the CLAIM is supported by the SOURCE SNIPPET.
|
|
163
|
+
Return ONLY JSON: {"score": <0.0 to 1.0>, "reason": "<short>"}
|
|
164
|
+
Score 1.0 if fully supported, 0.5 if partially supported/inferred, 0.0 if contradicted or not mentioned.
|
|
165
|
+
|
|
166
|
+
CLAIM: "${r.claimContext}"
|
|
167
|
+
SOURCE SNIPPET: "${r.entry.snippet}"`;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Dynamic import to avoid circular dependency at top level
|
|
171
|
+
const res = await (await import("./model-rotator")).generateWithRotation({
|
|
172
|
+
apiKey, prompt, preferredModel: model, maxOutputTokens: 100
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
const m = res.text.match(/\{[\s\S]*\}/);
|
|
177
|
+
const j = JSON.parse(m ? m[0] : res.text);
|
|
178
|
+
r.entailmentScore = Number(j.score) || 0;
|
|
179
|
+
r.trusted = r.entailmentScore >= 0.8;
|
|
180
|
+
onProgress?.(`Citation Ledger: [${r.tag}] entailment score ${r.entailmentScore.toFixed(2)} → ${r.trusted ? "TRUSTED" : "UNTRUSTED"}`);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
onProgress?.(`Citation Ledger: [${r.tag}] entailment check failed`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const trustedCount = auditSnapshot.auditResults.filter(r => r.trusted).length;
|
|
188
|
+
const missingCount = auditSnapshot.auditResults.filter(r => !r.found).length;
|
|
189
|
+
const untrustedCount = auditSnapshot.auditResults.filter(r => r.found && !r.trusted).length;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
entries: [...auditSnapshot.entries],
|
|
193
|
+
auditResults: auditSnapshot.auditResults,
|
|
194
|
+
totalCitations: auditSnapshot.totalCitations,
|
|
195
|
+
trustedCount,
|
|
196
|
+
untrustedCount,
|
|
197
|
+
missingCount,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Get all entries as a serializable snapshot. */
|
|
202
|
+
snapshot(): CitationEntry[] { return [...this.entries]; }
|
|
203
|
+
}
|