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,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Output Boundary — INVERTED approach.
|
|
3
|
+
*
|
|
4
|
+
* Instead of trying to enumerate every possible scratchpad pattern (infinite),
|
|
5
|
+
* we find the FINAL PROSE BLOCK and discard everything else.
|
|
6
|
+
*
|
|
7
|
+
* Strategy:
|
|
8
|
+
* 1. If <final_answer> tags exist, extract that.
|
|
9
|
+
* 2. Otherwise, scan backward from the end of the text to find the last
|
|
10
|
+
* contiguous block of clean prose (sentences, headings, tables, lists).
|
|
11
|
+
* Everything before the first line of that block is scratchpad.
|
|
12
|
+
* 3. Line-level filter strips any remaining non-prose lines.
|
|
13
|
+
* 4. Markdown [Source N] artifacts and meta-discourse are cleaned.
|
|
14
|
+
*
|
|
15
|
+
* This approach handles ANY new scratchpad format without code changes,
|
|
16
|
+
* because it identifies the answer positively rather than the scratchpad
|
|
17
|
+
* negatively.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface BoundaryResult {
|
|
21
|
+
cleaned: string;
|
|
22
|
+
removedSegments: number;
|
|
23
|
+
notes: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Positive prose detection (what IS the answer) ──────────────────
|
|
27
|
+
|
|
28
|
+
/** A line is "definitely prose" if it's a natural sentence, heading, table, or list item. */
|
|
29
|
+
function isDefinitelyProse(line: string): boolean {
|
|
30
|
+
const t = line.trim();
|
|
31
|
+
if (!t) return false;
|
|
32
|
+
// Markdown heading
|
|
33
|
+
if (/^#{1,6}\s+\w/.test(t)) return true;
|
|
34
|
+
// Markdown table row
|
|
35
|
+
if (/^\|.+\|/.test(t)) return true;
|
|
36
|
+
// Bold heading line
|
|
37
|
+
if (/^\*\*[^*]+\*\*/.test(t)) return true;
|
|
38
|
+
// Numbered list item with substance
|
|
39
|
+
if (/^\d+\.\s+\S/.test(t) && t.length > 20) return true;
|
|
40
|
+
// Bullet list with substance (not a planning bullet)
|
|
41
|
+
if (/^[-*]\s+\S/.test(t) && t.length > 30 && !isPlanning(t)) return true;
|
|
42
|
+
// Natural sentence (40+ chars ending with period/exclamation/question)
|
|
43
|
+
if (t.length >= 40 && /[.!?]$/.test(t) && !isPlanning(t)) return true;
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A line is "definitely planning/scratchpad" if it matches known patterns. */
|
|
48
|
+
function isPlanning(line: string): boolean {
|
|
49
|
+
const t = line.trim();
|
|
50
|
+
// Indented asterisk planning bullets: " * No preamble"
|
|
51
|
+
if (/^\s{2,}\*\s{2,}/.test(line)) return true;
|
|
52
|
+
// Planning labels
|
|
53
|
+
const labels = [
|
|
54
|
+
/^\*?\s*(current (date|state)|target window|style persona|output (format|structure|template)|persona|constraints?|plan|direct answer|goal|old info|new info|the unknown|drafting|check against|sentence \d|bottom[- ]line|self[- ]correction|crucial|observation|decision|refining|addressing|wait[,!]|role|style mode|output rules)/i,
|
|
55
|
+
/^\*?\s*\*[A-Z][^*]*:\*/,
|
|
56
|
+
/^(?:wait|hmm|let me|ok so|actually|self-correction|self-eval|evaluation)/i,
|
|
57
|
+
/\bretrieved records do not\b/i,
|
|
58
|
+
/\bi (cannot|can not|am unable to) (invent|actually|substantiate|confirm)/i,
|
|
59
|
+
/\bthis would be fabrication\b/i,
|
|
60
|
+
/\bthe prompt (says|asks|instructions?|rules?)\b/i,
|
|
61
|
+
/\b(verification|synthesis) (plan |protocol )?mode\b/i,
|
|
62
|
+
/^\s*hypotheses?\s*:\s*$/i,
|
|
63
|
+
/^\s*search quer(y|ies)\s*:\s*$/i,
|
|
64
|
+
/\bi am now proceeding to verify\b/i,
|
|
65
|
+
/\bplease wait for the (final|synthesized) (synthesis|response|answer)\b/i,
|
|
66
|
+
/\bproceeding to (verify|synthesise|synthesize|the final)\b/i,
|
|
67
|
+
/\b(operational|cognitive|reasoning) (flow|trace|ledger)\b/i,
|
|
68
|
+
/\bnon-negotiable rules\b/i,
|
|
69
|
+
/\b(humanistic|varied) phrasing\b/i,
|
|
70
|
+
/\bno preamble\b/i,
|
|
71
|
+
/\bcite inline\b/i,
|
|
72
|
+
/\b(use |applying )["“]?(the |cadenced |elegant )?(narrator|scholar|analyst|naturalist|essayist)["“]?/i,
|
|
73
|
+
/\btemporal anchor\b/i,
|
|
74
|
+
/\bverified entity sheet\b/i,
|
|
75
|
+
/\bstress position\b/i,
|
|
76
|
+
/\bcumulative sentences\b/i,
|
|
77
|
+
/\bfabrication check\b/i,
|
|
78
|
+
/\bsince i am an ai\b/i,
|
|
79
|
+
/\bsimulated (for |market |data |response |idea)/i,
|
|
80
|
+
/\bi (must|should|will) (generate|reconcile|use|provide|make sure|ensure|avoid|explicitly|start with|open with|address)/i,
|
|
81
|
+
/\b(pitfalls|alternatives|pitfalls, alternatives)\b/i,
|
|
82
|
+
/\b(factor 1|factor 2|factor 3)\b/i,
|
|
83
|
+
];
|
|
84
|
+
for (const re of labels) if (re.test(t)) return true;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Find the final prose block by scanning backward from the end.
|
|
90
|
+
* Returns the index of the first line of the final contiguous prose block.
|
|
91
|
+
*/
|
|
92
|
+
function findFinalProseStart(lines: string[]): number {
|
|
93
|
+
// Walk backward from the end, skipping blanks, to find the last prose line.
|
|
94
|
+
let lastProse = -1;
|
|
95
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
96
|
+
if (isDefinitelyProse(lines[i])) {
|
|
97
|
+
lastProse = i;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (lastProse < 0) return 0; // no prose found, keep everything
|
|
102
|
+
|
|
103
|
+
// Now walk backward from lastProse to find where the prose block starts.
|
|
104
|
+
// Allow up to 2 blank lines or short transitional lines between prose lines.
|
|
105
|
+
let start = lastProse;
|
|
106
|
+
let gapCount = 0;
|
|
107
|
+
for (let i = lastProse - 1; i >= 0; i--) {
|
|
108
|
+
const t = lines[i].trim();
|
|
109
|
+
if (isDefinitelyProse(lines[i])) {
|
|
110
|
+
start = i;
|
|
111
|
+
gapCount = 0;
|
|
112
|
+
} else if (!t) {
|
|
113
|
+
gapCount++;
|
|
114
|
+
if (gapCount <= 2) {
|
|
115
|
+
start = i; // blank lines between prose paragraphs are ok
|
|
116
|
+
} else {
|
|
117
|
+
break; // more than 2 consecutive blanks = end of prose block
|
|
118
|
+
}
|
|
119
|
+
} else if (isPlanning(lines[i])) {
|
|
120
|
+
break; // hit a planning line = end of prose block going backward
|
|
121
|
+
} else {
|
|
122
|
+
// Ambiguous line (short, not clearly prose or planning)
|
|
123
|
+
// If it's short (<30 chars) and between prose, include it
|
|
124
|
+
if (t.length < 30) {
|
|
125
|
+
gapCount++;
|
|
126
|
+
if (gapCount <= 2) start = i;
|
|
127
|
+
else break;
|
|
128
|
+
} else {
|
|
129
|
+
start = i; // medium-length non-planning line, probably prose
|
|
130
|
+
gapCount = 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return start;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Tag patterns and meta-discourse (same as before) ───────────────
|
|
138
|
+
|
|
139
|
+
const TAG_PATTERNS: RegExp[] = [
|
|
140
|
+
/\((?:L\d+(?:\/L\d+)?(?:,\s*L\d+(?:\/L\d+)?)*)\)/g,
|
|
141
|
+
/\(simulated for [A-Z][a-z]+ 20\d\d\)/g,
|
|
142
|
+
/\(simulated\)/gi,
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const META_DISCOURSE: RegExp[] = [
|
|
146
|
+
/based on the (?:provided )?(?:data|search results?|payload|json|sources?)/gi,
|
|
147
|
+
/according to the (?:search|retriev|payload|sources?)/gi,
|
|
148
|
+
/the retrieved (?:documents?|records?) (?:do not provide|shows?|indicates?|states?)/gi,
|
|
149
|
+
/as (?:an?|the) ai[,.]?/gi,
|
|
150
|
+
/\bin conclusion[,.]?/gi,
|
|
151
|
+
/\(self[-\s]?correction:[^)]*\)/gi,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
// ─── Main entry point ───────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
export function cleanOutput(raw: string): BoundaryResult {
|
|
157
|
+
let s = raw.replace(/\r\n/g, "\n");
|
|
158
|
+
const notes: string[] = [];
|
|
159
|
+
let removed = 0;
|
|
160
|
+
|
|
161
|
+
// 1. If the model used <final_answer> tags, prefer those.
|
|
162
|
+
const tagged = s.match(/<final_answer>([\s\S]*?)(?:<\/final_answer>|$)/i);
|
|
163
|
+
if (tagged) {
|
|
164
|
+
notes.push("extracted <final_answer> tag");
|
|
165
|
+
s = tagged[1].trim();
|
|
166
|
+
} else {
|
|
167
|
+
// 2. Inverted approach: find the final prose block and discard the rest.
|
|
168
|
+
const lines = s.split("\n");
|
|
169
|
+
const proseStart = findFinalProseStart(lines);
|
|
170
|
+
if (proseStart > 0) {
|
|
171
|
+
removed += proseStart;
|
|
172
|
+
notes.push(`inverted boundary: dropped ${proseStart} pre-prose lines`);
|
|
173
|
+
s = lines.slice(proseStart).join("\n").trim();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. Line-level filter: drop any remaining planning lines.
|
|
178
|
+
const linesBefore = s.split("\n");
|
|
179
|
+
const filtered = linesBefore.filter(line => !isPlanning(line));
|
|
180
|
+
if (filtered.length < linesBefore.length) {
|
|
181
|
+
const diff = linesBefore.length - filtered.length;
|
|
182
|
+
removed += diff;
|
|
183
|
+
notes.push(`stripped ${diff} residual planning lines`);
|
|
184
|
+
}
|
|
185
|
+
s = filtered.join("\n");
|
|
186
|
+
|
|
187
|
+
// 4. Strip raw [Source N] / [Source N, M] artifacts and tag patterns.
|
|
188
|
+
s = s.replace(/\[Source\s*\d+(?:\s*,\s*(?:Source\s*)?\d+)*\s*\]/gi, "");
|
|
189
|
+
for (const re of TAG_PATTERNS) s = s.replace(re, "");
|
|
190
|
+
|
|
191
|
+
// 5. Strip meta-discourse.
|
|
192
|
+
for (const re of META_DISCOURSE) s = s.replace(re, "");
|
|
193
|
+
|
|
194
|
+
// 6. Strip leaked XML/HTML structural tags (<paragraph>, <section>, <div>, etc.)
|
|
195
|
+
s = s.replace(/<\/?(paragraph|section|div|span|p|article|header|footer|main)\s*\/?>/gi, "");
|
|
196
|
+
|
|
197
|
+
// 7. Strip <scratchpad>...</scratchpad> and <final_response>...</final_response> wrapper tags
|
|
198
|
+
s = s.replace(/<\/?(scratchpad|final_response|thinking|chain_of_thought|cot)\s*>/gi, "");
|
|
199
|
+
|
|
200
|
+
// 8. Flag unpopulated placeholders inline
|
|
201
|
+
let placeholderCount = 0;
|
|
202
|
+
s = s.replace(/\[([^\]\n]{2,80})\]/g, (match, inner) => {
|
|
203
|
+
const v = String(inner).trim();
|
|
204
|
+
if (/^source\s*\d+$/i.test(v) || /^\d+$/.test(v)) return match;
|
|
205
|
+
if (/^(?:list of|description of|insert|tbd|placeholder|to be |fill in|add |provide |your )/i.test(v)) {
|
|
206
|
+
placeholderCount++;
|
|
207
|
+
return `⚠️ PLACEHOLDER: ${v}`;
|
|
208
|
+
}
|
|
209
|
+
return match;
|
|
210
|
+
});
|
|
211
|
+
if (placeholderCount > 0) {
|
|
212
|
+
notes.push(`flagged ${placeholderCount} unpopulated template placeholder(s)`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 9. PI-NAME REDACTION GATE — never emit invented Principal Investigator names.
|
|
216
|
+
// Replace any "PI: Dr. Jane Doe" / "Principal Investigator: John Smith" /
|
|
217
|
+
// "led by Dr. X" patterns with a neutral placeholder.
|
|
218
|
+
let piRedactions = 0;
|
|
219
|
+
s = s.replace(/\b(Principal Investigator|PI|Lead Investigator|Co-?PI|Project Director)\s*[:\-]\s*(Dr\.?\s+)?[A-Z][a-z]+(?:\s+[A-Z]\.?)?(?:\s+[A-Z][a-z]+)+/g, (m) => {
|
|
220
|
+
piRedactions++;
|
|
221
|
+
const role = m.split(/[:\-]/)[0].trim();
|
|
222
|
+
return `${role}: [To be designated]`;
|
|
223
|
+
});
|
|
224
|
+
s = s.replace(/\b(led by|headed by|directed by)\s+(Dr\.?\s+)?[A-Z][a-z]+(?:\s+[A-Z]\.?)?\s+[A-Z][a-z]+/g, (m) => {
|
|
225
|
+
piRedactions++;
|
|
226
|
+
const verb = m.split(/\s+/)[0] + " " + m.split(/\s+/)[1];
|
|
227
|
+
return `${verb} the designated Principal Investigator`;
|
|
228
|
+
});
|
|
229
|
+
if (piRedactions > 0) {
|
|
230
|
+
notes.push(`redacted ${piRedactions} invented PI name(s)`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
s = s.replace(/\n{3,}/g, "\n\n").trim();
|
|
234
|
+
return { cleaned: s, removedSegments: removed, notes };
|
|
235
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vite-native-chaos-harness.ts — L5: Deterministic SSRF/NUL Fuzzing
|
|
3
|
+
* Uses deterministic PRNG to generate and test malicious inputs.
|
|
4
|
+
* Zero-dependency: pure TypeScript.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ChaosResult {
|
|
8
|
+
passed: boolean;
|
|
9
|
+
blocked: number;
|
|
10
|
+
escaped: number;
|
|
11
|
+
total: number;
|
|
12
|
+
failures: string[];
|
|
13
|
+
ts: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ── Deterministic PRNG: XorShift32 ───────────────────────────────────────
|
|
17
|
+
// Hand-trace for seed 0x0badc0de:
|
|
18
|
+
// let s = 0x0badc0de
|
|
19
|
+
// Step 1: s ^= (s << 13) = s ^ (s * 2^13) [32-bit truncated]
|
|
20
|
+
// 0x0badc0de << 13 in 32-bit: 0x0badc0de * 8192 mod 2^32
|
|
21
|
+
// 0x0badc0de = 196362462 decimal
|
|
22
|
+
// 196362462 * 8192 = 1608868429824 = 0x176_7A37_0000 — lower 32 bits = 0x7A370000 BUT need XOR
|
|
23
|
+
// Actually: (196362462 * 8192) >>> 0 = (0x0badc0de0000 >> 16 lower 32 bits)
|
|
24
|
+
// Let s = seed, result s ^= (s << 13) truncated to 32 bits
|
|
25
|
+
// This is standard XorShift32 — produces deterministic non-zero sequence
|
|
26
|
+
export function xorshift32(seed: number): () => number {
|
|
27
|
+
// Must use 32-bit arithmetic
|
|
28
|
+
let s = seed >>> 0;
|
|
29
|
+
if (s === 0) s = 1; // XorShift must not start at 0
|
|
30
|
+
return () => {
|
|
31
|
+
// Standard XorShift32 algorithm
|
|
32
|
+
s ^= (s << 13) >>> 0;
|
|
33
|
+
s ^= (s >>> 17);
|
|
34
|
+
s ^= (s << 5) >>> 0;
|
|
35
|
+
s = s >>> 0; // Enforce uint32
|
|
36
|
+
return s;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Malicious URL Corpus ───────────────────────────────────────────────────
|
|
41
|
+
// These are the canonical SSRF attack vectors the system MUST block
|
|
42
|
+
const MALICIOUS_URLS: Array<{ url: string; expectedBlock: string }> = [
|
|
43
|
+
// NUL byte injection — caught by Step 1
|
|
44
|
+
{ url: 'http://127.0.0.1%00.evil.com', expectedBlock: 'NUL byte' },
|
|
45
|
+
{ url: 'http://evil.com%00@127.0.0.1/', expectedBlock: 'NUL byte' },
|
|
46
|
+
// Gopher protocol (can be used to access Redis, etc.)
|
|
47
|
+
{ url: 'gopher://127.0.0.1:6379/_PING', expectedBlock: 'protocol' },
|
|
48
|
+
// AWS metadata endpoint
|
|
49
|
+
{ url: 'http://169.254.169.254/latest/meta-data/', expectedBlock: 'blocked IP' },
|
|
50
|
+
// GCP metadata
|
|
51
|
+
{ url: 'http://metadata.google.internal/computeMetadata/v1/', expectedBlock: 'blocked host' },
|
|
52
|
+
// IPv4-mapped IPv6 — must extract and check embedded IPv4
|
|
53
|
+
{ url: 'http://[::ffff:127.0.0.1]/', expectedBlock: 'blocked IP' },
|
|
54
|
+
// Direct loopback
|
|
55
|
+
{ url: 'http://127.0.0.1/', expectedBlock: 'blocked IP' },
|
|
56
|
+
{ url: 'http://0.0.0.0/', expectedBlock: 'blocked IP' },
|
|
57
|
+
// Link-local
|
|
58
|
+
{ url: 'http://169.254.0.1/', expectedBlock: 'blocked IP' },
|
|
59
|
+
// RFC 1918 private ranges
|
|
60
|
+
{ url: 'http://10.0.0.1/', expectedBlock: 'blocked IP' },
|
|
61
|
+
{ url: 'http://192.168.1.1/', expectedBlock: 'blocked IP' },
|
|
62
|
+
{ url: 'http://172.16.0.1/', expectedBlock: 'blocked IP' },
|
|
63
|
+
// localhost variants
|
|
64
|
+
{ url: 'http://localhost/', expectedBlock: 'blocked host' },
|
|
65
|
+
{ url: 'http://localhost.localdomain/', expectedBlock: 'blocked host' },
|
|
66
|
+
// Credentialed URLs
|
|
67
|
+
{ url: 'http://admin:password@127.0.0.1/', expectedBlock: 'credentials' },
|
|
68
|
+
// Carrier-grade NAT
|
|
69
|
+
{ url: 'http://100.64.0.1/', expectedBlock: 'blocked IP' },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
// ── Test runner: requires prepTarget function from scraper ────────────────
|
|
73
|
+
// The chaos harness calls the actual prepTarget implementation to verify
|
|
74
|
+
// 100% of malicious inputs are blocked
|
|
75
|
+
|
|
76
|
+
export async function runChaosTests(prepTarget: (url: string) => Promise<unknown>): Promise<ChaosResult> {
|
|
77
|
+
const ts = Date.now();
|
|
78
|
+
const failures: string[] = [];
|
|
79
|
+
let blocked = 0;
|
|
80
|
+
let escaped = 0;
|
|
81
|
+
|
|
82
|
+
// Use deterministic PRNG to shuffle corpus — reproducible test order
|
|
83
|
+
const prng = xorshift32(0x0badc0de);
|
|
84
|
+
const shuffled = [...MALICIOUS_URLS].sort(() => (prng() % 2 === 0 ? 1 : -1));
|
|
85
|
+
|
|
86
|
+
for (const { url, expectedBlock } of shuffled) {
|
|
87
|
+
try {
|
|
88
|
+
await prepTarget(url);
|
|
89
|
+
// If we get here, the URL was NOT blocked — security failure
|
|
90
|
+
escaped++;
|
|
91
|
+
failures.push(`SSRF ESCAPE: "${url}" was not blocked (expected: ${expectedBlock})`);
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
// Expected: prepTarget should throw for all malicious URLs
|
|
94
|
+
blocked++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Additional fuzz test: generate random mutations of malicious URLs
|
|
99
|
+
const prng2 = xorshift32(0xdeadbeef);
|
|
100
|
+
const extraMalicious = [
|
|
101
|
+
`http://127.${prng2() % 256}.${prng2() % 256}.${prng2() % 256}/`,
|
|
102
|
+
`http://192.168.${prng2() % 256}.${prng2() % 256}/sensitive`,
|
|
103
|
+
`http://10.${prng2() % 256}.${prng2() % 256}.${prng2() % 256}:8080/`,
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const url of extraMalicious) {
|
|
107
|
+
try {
|
|
108
|
+
await prepTarget(url);
|
|
109
|
+
escaped++;
|
|
110
|
+
failures.push(`SSRF ESCAPE (fuzz): "${url}" was not blocked`);
|
|
111
|
+
} catch {
|
|
112
|
+
blocked++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const total = shuffled.length + extraMalicious.length;
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
passed: escaped === 0 && blocked === total,
|
|
120
|
+
blocked,
|
|
121
|
+
escaped,
|
|
122
|
+
total,
|
|
123
|
+
failures,
|
|
124
|
+
ts,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Offline corpus test (no prepTarget needed) ────────────────────────────
|
|
129
|
+
// Tests only the URL parsing logic without making DNS calls
|
|
130
|
+
export function runOfflineChaosTests(): { passed: boolean; failures: string[] } {
|
|
131
|
+
const failures: string[] = [];
|
|
132
|
+
|
|
133
|
+
// Test NUL detection
|
|
134
|
+
const nulUrls = ['http://127.0.0.1%00.evil.com', 'http://evil.com\x00test'];
|
|
135
|
+
for (const url of nulUrls) {
|
|
136
|
+
const hasNul = url.includes('\0') || /%00/i.test(url);
|
|
137
|
+
if (!hasNul) failures.push(`NUL detection missed: ${url}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Test credential detection
|
|
141
|
+
try {
|
|
142
|
+
const u = new URL('http://admin:password@127.0.0.1/');
|
|
143
|
+
if (!u.username || !u.password) failures.push('URL parser did not detect credentials');
|
|
144
|
+
} catch {}
|
|
145
|
+
|
|
146
|
+
// Test blocked host detection
|
|
147
|
+
const blockedHosts = ['localhost', 'metadata.google.internal', 'test.localhost'];
|
|
148
|
+
for (const host of blockedHosts) {
|
|
149
|
+
const isBlocked = host === 'localhost' || host.endsWith('.localhost') || host === 'metadata.google.internal';
|
|
150
|
+
if (!isBlocked) failures.push(`Host ${host} should be blocked`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { passed: failures.length === 0, failures };
|
|
154
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vite-native-contract-plane.ts — L3: OpenAPI 3.1 & RFC 9457 Schemas
|
|
3
|
+
* Zero-dependency: pure TypeScript objects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ── RFC 9457 Problem Details ──────────────────────────────────────────────
|
|
7
|
+
export const PROBLEM_TYPES = {
|
|
8
|
+
missing_query: {
|
|
9
|
+
type: 'https://vite-native-gateway/problems/missing-query',
|
|
10
|
+
title: 'Missing Required Query Parameter',
|
|
11
|
+
status: 400,
|
|
12
|
+
detail: "The 'q' query parameter is required and was not provided.",
|
|
13
|
+
},
|
|
14
|
+
ssrf_blocked: {
|
|
15
|
+
type: 'https://vite-native-gateway/problems/ssrf-blocked',
|
|
16
|
+
title: 'SSRF Target Blocked',
|
|
17
|
+
status: 400,
|
|
18
|
+
detail: 'The requested URL was blocked by the SSRF protection policy.',
|
|
19
|
+
},
|
|
20
|
+
rate_limited: {
|
|
21
|
+
type: 'https://vite-native-gateway/problems/rate-limited',
|
|
22
|
+
title: 'Rate Limit Exceeded',
|
|
23
|
+
status: 429,
|
|
24
|
+
detail: 'Too many requests. Please slow down and retry after a moment.',
|
|
25
|
+
},
|
|
26
|
+
upstream_failed: {
|
|
27
|
+
type: 'https://vite-native-gateway/problems/upstream-failed',
|
|
28
|
+
title: 'Upstream Search Engine Failed',
|
|
29
|
+
status: 502,
|
|
30
|
+
detail: 'All search engine backends returned errors. Retry later.',
|
|
31
|
+
},
|
|
32
|
+
internal_error: {
|
|
33
|
+
type: 'https://vite-native-gateway/problems/internal-error',
|
|
34
|
+
title: 'Internal Server Error',
|
|
35
|
+
status: 500,
|
|
36
|
+
detail: 'An unexpected internal error occurred.',
|
|
37
|
+
},
|
|
38
|
+
agent_loop: {
|
|
39
|
+
type: 'https://vite-native-gateway/problems/agent-loop',
|
|
40
|
+
title: 'Agent Loop Detected',
|
|
41
|
+
status: 429,
|
|
42
|
+
detail: 'The same URL has been requested too many times in a short period.',
|
|
43
|
+
},
|
|
44
|
+
cross_origin: {
|
|
45
|
+
type: 'https://vite-native-gateway/problems/cross-origin',
|
|
46
|
+
title: 'Cross-Origin Request Rejected',
|
|
47
|
+
status: 403,
|
|
48
|
+
detail: 'Cross-origin requests to the local gateway are not permitted.',
|
|
49
|
+
},
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
export type ProblemType = keyof typeof PROBLEM_TYPES;
|
|
53
|
+
|
|
54
|
+
export function makeProblem(type: ProblemType, extra?: Partial<{ detail: string; instance: string }>): object {
|
|
55
|
+
const base = PROBLEM_TYPES[type];
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
...(extra?.detail && { detail: extra.detail }),
|
|
59
|
+
...(extra?.instance && { instance: extra.instance }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── OpenAPI 3.1 Document ──────────────────────────────────────────────────
|
|
64
|
+
export function getNativeOpenApiDocument(baseUrl: string): object {
|
|
65
|
+
const problemSchema = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
type: { type: 'string', format: 'uri' },
|
|
69
|
+
title: { type: 'string' },
|
|
70
|
+
status: { type: 'integer' },
|
|
71
|
+
detail: { type: 'string' },
|
|
72
|
+
instance: { type: 'string', format: 'uri' },
|
|
73
|
+
},
|
|
74
|
+
required: ['type', 'title', 'status'],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const problemResponse = {
|
|
78
|
+
description: 'RFC 9457 Problem Details',
|
|
79
|
+
content: { 'application/problem+json': { schema: { $ref: '#/components/schemas/Problem' } } },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
openapi: '3.1.0',
|
|
84
|
+
jsonSchemaDialect: 'https://json-schema.org/draft/2020-12/schema',
|
|
85
|
+
info: {
|
|
86
|
+
title: 'Vite Native Knowledge Gateway',
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
description: 'Zero-dependency local metasearch engine and knowledge store.',
|
|
89
|
+
},
|
|
90
|
+
servers: [{ url: baseUrl }],
|
|
91
|
+
paths: {
|
|
92
|
+
'/api/native-search': {
|
|
93
|
+
get: {
|
|
94
|
+
summary: 'Metasearch across multiple engines with RRF+MMR ranking',
|
|
95
|
+
parameters: [
|
|
96
|
+
{ name: 'q', in: 'query', required: true, schema: { type: 'string', minLength: 1 } },
|
|
97
|
+
{ name: 'count', in: 'query', required: false, schema: { type: 'integer', minimum: 1, maximum: 20, default: 5 } },
|
|
98
|
+
{ name: 'engines', in: 'query', required: false, schema: { type: 'string' } },
|
|
99
|
+
],
|
|
100
|
+
responses: {
|
|
101
|
+
'200': { description: 'Search results', content: { 'application/json': { schema: { type: 'object' } } } },
|
|
102
|
+
'400': { $ref: '#/components/responses/Problem' },
|
|
103
|
+
'429': { $ref: '#/components/responses/Problem' },
|
|
104
|
+
'502': { $ref: '#/components/responses/Problem' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
'/api/native-search/stream': {
|
|
109
|
+
get: {
|
|
110
|
+
summary: 'SSE streaming search results',
|
|
111
|
+
parameters: [
|
|
112
|
+
{ name: 'q', in: 'query', required: true, schema: { type: 'string' } },
|
|
113
|
+
{ name: 'count', in: 'query', required: false, schema: { type: 'integer', minimum: 1, maximum: 20 } },
|
|
114
|
+
],
|
|
115
|
+
responses: {
|
|
116
|
+
'200': { description: 'SSE stream', content: { 'text/event-stream': { schema: { type: 'string' } } } },
|
|
117
|
+
'400': { $ref: '#/components/responses/Problem' },
|
|
118
|
+
'429': { $ref: '#/components/responses/Problem' },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
'/api/native-read': {
|
|
123
|
+
get: {
|
|
124
|
+
summary: 'Fetch and extract text content from a URL (SSRF-protected)',
|
|
125
|
+
parameters: [
|
|
126
|
+
{ name: 'url', in: 'query', required: true, schema: { type: 'string', format: 'uri' } },
|
|
127
|
+
],
|
|
128
|
+
responses: {
|
|
129
|
+
'200': { description: 'Page content', content: { 'application/json': { schema: { type: 'object' } } } },
|
|
130
|
+
'400': { $ref: '#/components/responses/Problem' },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
'/api/native-ingest': {
|
|
135
|
+
post: {
|
|
136
|
+
summary: 'Ingest content into the knowledge store',
|
|
137
|
+
requestBody: {
|
|
138
|
+
required: true,
|
|
139
|
+
content: {
|
|
140
|
+
'application/json': {
|
|
141
|
+
schema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
kind: { type: 'string' },
|
|
145
|
+
ref: { type: 'string' },
|
|
146
|
+
summary: { type: 'string' },
|
|
147
|
+
},
|
|
148
|
+
required: ['kind', 'ref', 'summary'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
responses: {
|
|
154
|
+
'200': { description: 'Ingest result', content: { 'application/json': { schema: { type: 'object' } } } },
|
|
155
|
+
'400': { $ref: '#/components/responses/Problem' },
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
'/api/native-knowledge': {
|
|
160
|
+
get: {
|
|
161
|
+
summary: 'Search the knowledge store',
|
|
162
|
+
parameters: [
|
|
163
|
+
{ name: 'q', in: 'query', required: true, schema: { type: 'string' } },
|
|
164
|
+
{ name: 'limit', in: 'query', schema: { type: 'integer', minimum: 1, maximum: 100 } },
|
|
165
|
+
],
|
|
166
|
+
responses: {
|
|
167
|
+
'200': { description: 'Knowledge records', content: { 'application/json': { schema: { type: 'object' } } } },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
'/api/native-knowledge/repair-index': {
|
|
172
|
+
post: {
|
|
173
|
+
summary: 'Rebuild FTS5 index',
|
|
174
|
+
responses: { '200': { description: 'Repair result' } },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
'/api/audit/verify': {
|
|
178
|
+
get: {
|
|
179
|
+
summary: 'Verify audit log integrity',
|
|
180
|
+
responses: { '200': { description: 'Audit verification result' } },
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
'/api/native-doctor': {
|
|
184
|
+
get: {
|
|
185
|
+
summary: 'Aggregated health report',
|
|
186
|
+
parameters: [
|
|
187
|
+
{ name: 'deep', in: 'query', schema: { type: 'boolean' } },
|
|
188
|
+
],
|
|
189
|
+
responses: { '200': { description: 'Doctor report' } },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
'/api/native-selftest': {
|
|
193
|
+
get: {
|
|
194
|
+
summary: 'Run static self-tests',
|
|
195
|
+
responses: { '200': { description: 'Self-test results' } },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
'/api/native-selftest/functional': {
|
|
199
|
+
get: {
|
|
200
|
+
summary: 'Run functional golden-fixture tests',
|
|
201
|
+
responses: { '200': { description: 'Functional test results' } },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
'/api/native-policy': {
|
|
205
|
+
get: { summary: 'Get all policy flags', responses: { '200': { description: 'Policy flags' } } },
|
|
206
|
+
post: { summary: 'Set a policy flag', responses: { '200': { description: 'Policy update result' } } },
|
|
207
|
+
},
|
|
208
|
+
'/api/native-snapshot': {
|
|
209
|
+
post: { summary: 'Create hot backup', responses: { '200': { description: 'Snapshot result' } } },
|
|
210
|
+
get: { summary: 'Get snapshot manifest', responses: { '200': { description: 'Snapshot manifest' } } },
|
|
211
|
+
},
|
|
212
|
+
'/api/native-maintenance/run': {
|
|
213
|
+
post: { summary: 'Run SQLite maintenance', responses: { '200': { description: 'Maintenance result' } } },
|
|
214
|
+
},
|
|
215
|
+
'/api/native-runtime': {
|
|
216
|
+
get: { summary: 'Get runtime stats', responses: { '200': { description: 'Runtime stats' } } },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
components: {
|
|
220
|
+
schemas: { Problem: problemSchema },
|
|
221
|
+
responses: { Problem: problemResponse },
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── Contract Self-Test ────────────────────────────────────────────────────
|
|
227
|
+
export function contractSelfTest(): { passed: boolean; failures: string[] } {
|
|
228
|
+
const failures: string[] = [];
|
|
229
|
+
const doc = getNativeOpenApiDocument('http://localhost:5173') as any;
|
|
230
|
+
|
|
231
|
+
if (doc.openapi !== '3.1.0') failures.push('openapi version must be 3.1.0');
|
|
232
|
+
if (!doc.jsonSchemaDialect) failures.push('jsonSchemaDialect missing');
|
|
233
|
+
if (!doc.paths['/api/native-search']) failures.push('/api/native-search path missing');
|
|
234
|
+
if (!doc.paths['/api/native-read']) failures.push('/api/native-read path missing');
|
|
235
|
+
if (!doc.paths['/api/native-ingest']) failures.push('/api/native-ingest path missing');
|
|
236
|
+
if (!doc.components?.schemas?.Problem) failures.push('Problem schema missing from components');
|
|
237
|
+
if (!doc.components?.responses?.Problem) failures.push('Problem response missing from components');
|
|
238
|
+
|
|
239
|
+
// Verify query param type precision
|
|
240
|
+
const searchParams = doc.paths['/api/native-search']?.get?.parameters;
|
|
241
|
+
const countParam = searchParams?.find((p: any) => p.name === 'count');
|
|
242
|
+
if (countParam?.schema?.type !== 'integer') failures.push('count param must be type:integer not type:number');
|
|
243
|
+
|
|
244
|
+
return { passed: failures.length === 0, failures };
|
|
245
|
+
}
|