sum-engine 0.4.1__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {sum_engine-0.4.1 → sum_engine-0.5.0}/PKG-INFO +5 -2
  2. {sum_engine-0.4.1 → sum_engine-0.5.0}/README.md +1 -1
  3. {sum_engine-0.4.1 → sum_engine-0.5.0}/pyproject.toml +11 -1
  4. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/PKG-INFO +5 -2
  5. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/SOURCES.txt +2 -0
  6. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/requires.txt +4 -0
  7. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/mcp_server/server.py +150 -1
  8. sum_engine-0.5.0/sum_engine_internal/research/__init__.py +21 -0
  9. sum_engine-0.5.0/sum_engine_internal/research/sheaf_laplacian.py +231 -0
  10. {sum_engine-0.4.1 → sum_engine-0.5.0}/LICENSE +0 -0
  11. {sum_engine-0.4.1 → sum_engine-0.5.0}/setup.cfg +0 -0
  12. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_cli/__init__.py +0 -0
  13. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_cli/main.py +0 -0
  14. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/dependency_links.txt +0 -0
  15. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/entry_points.txt +0 -0
  16. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine.egg-info/top_level.txt +0 -0
  17. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/__init__.py +0 -0
  18. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/adapters/__init__.py +0 -0
  19. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/adapters/format_pivot.py +0 -0
  20. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/__init__.py +0 -0
  21. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/causal_discovery.py +0 -0
  22. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/chunked_corpus.py +0 -0
  23. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/minhash.py +0 -0
  24. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/predicate_canon.py +0 -0
  25. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/semantic_arithmetic.py +0 -0
  26. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/syntactic_sieve.py +0 -0
  27. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/algorithms/zk_semantics.py +0 -0
  28. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/__init__.py +0 -0
  29. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/automated_scientist.py +0 -0
  30. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/autonomous_agent.py +0 -0
  31. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/causal_triggers.py +0 -0
  32. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/confidence_calibrator.py +0 -0
  33. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/data/__init__.py +0 -0
  34. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/data/common_english_2000.txt +0 -0
  35. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/data/common_english_5000.txt +0 -0
  36. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/epistemic_arbiter.py +0 -0
  37. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/epistemic_loop.py +0 -0
  38. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/extraction_validator.py +0 -0
  39. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/gauge_orchestrator.py +0 -0
  40. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/live_llm_adapter.py +0 -0
  41. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/llm_dispatch.py +0 -0
  42. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/llm_entailment.py +0 -0
  43. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/mass_semantic_engine.py +0 -0
  44. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/ouroboros.py +0 -0
  45. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/s25_interventions.py +0 -0
  46. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/semantic_dedup.py +0 -0
  47. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/slider_renderer.py +0 -0
  48. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/tome_generator.py +0 -0
  49. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/tome_sliders.py +0 -0
  50. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/vector_bridge.py +0 -0
  51. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/ensemble/venn_abers.py +0 -0
  52. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/__init__.py +0 -0
  53. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/akashic_ledger.py +0 -0
  54. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/canonical_codec.py +0 -0
  55. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/jcs.py +0 -0
  56. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/jose_envelope.py +0 -0
  57. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/key_manager.py +0 -0
  58. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/p2p_mesh.py +0 -0
  59. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/prov_o.py +0 -0
  60. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/provenance.py +0 -0
  61. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/rate_limiter.py +0 -0
  62. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/resource_guards.py +0 -0
  63. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/scheme_registry.py +0 -0
  64. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/state_encoding.py +0 -0
  65. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/telemetry.py +0 -0
  66. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/tome_parser.py +0 -0
  67. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/verifiable_credential.py +0 -0
  68. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/infrastructure/zig_bridge.py +0 -0
  69. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/mcp_server/__init__.py +0 -0
  70. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/mcp_server/__main__.py +0 -0
  71. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/mcp_server/errors.py +0 -0
  72. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/merkle_sidecar/__init__.py +0 -0
  73. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/merkle_sidecar/tree.py +0 -0
  74. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/render_receipt/__init__.py +0 -0
  75. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/render_receipt/verifier.py +0 -0
  76. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/trust_root/__init__.py +0 -0
  77. {sum_engine-0.4.1 → sum_engine-0.5.0}/sum_engine_internal/trust_root/verifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sum-engine
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: SUM — bidirectional knowledge distillation with optional cryptographic attestation. Pipe prose, get a CanonicalBundle (HMAC / Ed25519 / W3C VC 2.0), verify anywhere.
5
5
  Author: ototao
6
6
  License: Apache-2.0
@@ -35,6 +35,9 @@ Provides-Extra: receipt-verify
35
35
  Requires-Dist: joserfc>=1.0.0; extra == "receipt-verify"
36
36
  Provides-Extra: mcp
37
37
  Requires-Dist: mcp>=1.0.0; extra == "mcp"
38
+ Provides-Extra: research
39
+ Requires-Dist: numpy>=1.24.0; extra == "research"
40
+ Requires-Dist: scipy>=1.10.0; extra == "research"
38
41
  Provides-Extra: omni-format
39
42
  Requires-Dist: markitdown==0.1.5; extra == "omni-format"
40
43
  Provides-Extra: dev
@@ -106,7 +109,7 @@ A minimal Node verifier using `jose` + `canonicalize` is in [`docs/RENDER_RECEIP
106
109
 
107
110
  | Surface | Status | Verifies |
108
111
  |---|---|---|
109
- | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on `main` (PyPI 0.3.0 stale; 0.4.0 cut on the operator queue) | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
112
+ | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on PyPI 0.4.1 | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
110
113
  | Cloudflare Worker at `sum-demo.ototao.workers.dev` | shipped | `/api/render` → tome + `render_receipt`; `/.well-known/jwks.json` → JWKS; `/api/qid` → Wikidata resolver |
111
114
  | Single-file browser demo (`single_file_demo/index.html`) | shipped | paste prose → in-browser attest → CanonicalBundle JSON; same bytes verify under `node standalone_verifier/verify.js` (Chrome / Firefox / Safari with WebCrypto Ed25519 support) |
112
115
  | Cross-runtime trust triangle | locked by CI (`make xruntime`) | K1 / K1-mw / K2 / K3 / K4 — Python ↔ Node ↔ Browser agree byte-for-byte on valid bundles. `make xruntime-adversarial` adds A1–A6 rejection-class equivalence. |
@@ -48,7 +48,7 @@ A minimal Node verifier using `jose` + `canonicalize` is in [`docs/RENDER_RECEIP
48
48
 
49
49
  | Surface | Status | Verifies |
50
50
  |---|---|---|
51
- | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on `main` (PyPI 0.3.0 stale; 0.4.0 cut on the operator queue) | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
51
+ | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on PyPI 0.4.1 | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
52
52
  | Cloudflare Worker at `sum-demo.ototao.workers.dev` | shipped | `/api/render` → tome + `render_receipt`; `/.well-known/jwks.json` → JWKS; `/api/qid` → Wikidata resolver |
53
53
  | Single-file browser demo (`single_file_demo/index.html`) | shipped | paste prose → in-browser attest → CanonicalBundle JSON; same bytes verify under `node standalone_verifier/verify.js` (Chrome / Firefox / Safari with WebCrypto Ed25519 support) |
54
54
  | Cross-runtime trust triangle | locked by CI (`make xruntime`) | K1 / K1-mw / K2 / K3 / K4 — Python ↔ Node ↔ Browser agree byte-for-byte on valid bundles. `make xruntime-adversarial` adds A1–A6 rejection-class equivalence. |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sum-engine"
7
- version = "0.4.1"
7
+ version = "0.5.0"
8
8
  description = "SUM — bidirectional knowledge distillation with optional cryptographic attestation. Pipe prose, get a CanonicalBundle (HMAC / Ed25519 / W3C VC 2.0), verify anywhere."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -60,6 +60,16 @@ receipt-verify = ["joserfc>=1.0.0"]
60
60
  # `mcp` package is the official Python SDK; FastMCP is its
61
61
  # decorator-style high-level API. See docs/MCP_INTEGRATION.md.
62
62
  mcp = ["mcp>=1.0.0"]
63
+ # Research-grade modules under sum_engine_internal/research/. NOT on
64
+ # the production install path; APIs may change between minor releases
65
+ # without backwards-compatibility guarantees. Currently provides the
66
+ # v1 sheaf-Laplacian hallucination detector grounded in Gebhart,
67
+ # Hansen & Schrater (2023, AISTATS, arXiv:2110.03789) and the
68
+ # sheaf-Laplacian theory of Hansen & Ghrist (2019). See
69
+ # docs/SHEAF_HALLUCINATION_DETECTOR.md for the spec, including
70
+ # verified blindspots (predicate-flip, off-graph fact-fabrication,
71
+ # empty-render false negative).
72
+ research = ["numpy>=1.24.0", "scipy>=1.10.0"]
63
73
  # Omni-format adapter. Markdown is the canonical pivot for the
64
74
  # attest pipeline: any input format -> markdown -> existing
65
75
  # extract/state/bundle path. Source URI anchors to the original
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sum-engine
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: SUM — bidirectional knowledge distillation with optional cryptographic attestation. Pipe prose, get a CanonicalBundle (HMAC / Ed25519 / W3C VC 2.0), verify anywhere.
5
5
  Author: ototao
6
6
  License: Apache-2.0
@@ -35,6 +35,9 @@ Provides-Extra: receipt-verify
35
35
  Requires-Dist: joserfc>=1.0.0; extra == "receipt-verify"
36
36
  Provides-Extra: mcp
37
37
  Requires-Dist: mcp>=1.0.0; extra == "mcp"
38
+ Provides-Extra: research
39
+ Requires-Dist: numpy>=1.24.0; extra == "research"
40
+ Requires-Dist: scipy>=1.10.0; extra == "research"
38
41
  Provides-Extra: omni-format
39
42
  Requires-Dist: markitdown==0.1.5; extra == "omni-format"
40
43
  Provides-Extra: dev
@@ -106,7 +109,7 @@ A minimal Node verifier using `jose` + `canonicalize` is in [`docs/RENDER_RECEIP
106
109
 
107
110
  | Surface | Status | Verifies |
108
111
  |---|---|---|
109
- | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on `main` (PyPI 0.3.0 stale; 0.4.0 cut on the operator queue) | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
112
+ | `pip install 'sum-engine[sieve]'` — `sum attest` / `sum verify` / `sum render` / `sum resolve` / `sum ledger` / `sum inspect` / `sum schema` | shipped on PyPI 0.4.1 | structural reconstruction; HMAC-SHA256 + Ed25519 signatures (W3C VC 2.0 `eddsa-jcs-2022`); bidirectional `sum attest` ↔ `sum render` symmetry from the shell |
110
113
  | Cloudflare Worker at `sum-demo.ototao.workers.dev` | shipped | `/api/render` → tome + `render_receipt`; `/.well-known/jwks.json` → JWKS; `/api/qid` → Wikidata resolver |
111
114
  | Single-file browser demo (`single_file_demo/index.html`) | shipped | paste prose → in-browser attest → CanonicalBundle JSON; same bytes verify under `node standalone_verifier/verify.js` (Chrome / Firefox / Safari with WebCrypto Ed25519 support) |
112
115
  | Cross-runtime trust triangle | locked by CI (`make xruntime`) | K1 / K1-mw / K2 / K3 / K4 — Python ↔ Node ↔ Browser agree byte-for-byte on valid bundles. `make xruntime-adversarial` adds A1–A6 rejection-class equivalence. |
@@ -69,5 +69,7 @@ sum_engine_internal/merkle_sidecar/__init__.py
69
69
  sum_engine_internal/merkle_sidecar/tree.py
70
70
  sum_engine_internal/render_receipt/__init__.py
71
71
  sum_engine_internal/render_receipt/verifier.py
72
+ sum_engine_internal/research/__init__.py
73
+ sum_engine_internal/research/sheaf_laplacian.py
72
74
  sum_engine_internal/trust_root/__init__.py
73
75
  sum_engine_internal/trust_root/verifier.py
@@ -37,5 +37,9 @@ markitdown==0.1.5
37
37
  [receipt-verify]
38
38
  joserfc>=1.0.0
39
39
 
40
+ [research]
41
+ numpy>=1.24.0
42
+ scipy>=1.10.0
43
+
40
44
  [sieve]
41
45
  spacy>=3.7.0
@@ -92,7 +92,7 @@ def build_server() -> FastMCP:
92
92
  name="sum",
93
93
  instructions=(
94
94
  "SUM verifiable knowledge distillation engine (v2 hardened). "
95
- "Tools: extract / attest / verify / inspect / schema. "
95
+ "Tools: extract / attest / verify / inspect / render / schema. "
96
96
  "Default extractor is offline-only (sieve). The LLM extractor "
97
97
  "is disabled unless SUM_MCP_ALLOW_NETWORK=1 was set when the "
98
98
  "server started. Every tool returns either a tool-specific "
@@ -472,6 +472,155 @@ def build_server() -> FastMCP:
472
472
  "inspect", t0, ErrorClass.INTERNAL, type(exc).__name__
473
473
  )
474
474
 
475
+ # ------------------------------------------------------------------
476
+ # render
477
+ # ------------------------------------------------------------------
478
+
479
+ @mcp.tool()
480
+ async def render(
481
+ bundle: dict,
482
+ density: float = 1.0,
483
+ length: float = 0.5,
484
+ formality: float = 0.5,
485
+ audience: float = 0.5,
486
+ perspective: float = 0.5,
487
+ title: str = "Rendered Tome",
488
+ ) -> dict:
489
+ """Render a CanonicalBundle's axioms back into prose under
490
+ explicit slider control. The MCP analogue of ``sum render``.
491
+
492
+ Local-only path: actions the density slider deterministically
493
+ (lex-prefix subsetting); non-neutral length / formality /
494
+ audience / perspective return ``error_class="schema"`` because
495
+ the LLM-conditioned axes require a Worker, which the MCP
496
+ server does not currently broker (see
497
+ ``docs/MCP_INTEGRATION.md`` for the rationale — keeping the
498
+ MCP server fully offline by default preserves the
499
+ ``SUM_MCP_ALLOW_NETWORK`` opt-in property).
500
+
501
+ Args:
502
+ bundle: CanonicalBundle dict (same shape ``verify`` accepts).
503
+ density: Axiom-coverage slider in [0, 1]. 1.0 keeps all
504
+ axioms (default); 0.0 keeps none.
505
+ length / formality / audience / perspective: 0.5 = neutral
506
+ (default). Non-0.5 rejected with a SCHEMA error
507
+ pointing at the local-only constraint.
508
+ title: Tome title. Default: "Rendered Tome".
509
+
510
+ Returns:
511
+ Success: ``{tome, sliders, mode, axiom_count_input, title}``.
512
+ Failure: ``{error_class, errors}``.
513
+ """
514
+ t0 = time.perf_counter()
515
+ try:
516
+ if not isinstance(bundle, dict):
517
+ return error_result(
518
+ "render", t0, ErrorClass.SCHEMA,
519
+ f"bundle must be a dict, got {type(bundle).__name__}",
520
+ )
521
+
522
+ for field in ("canonical_tome", "canonical_format_version"):
523
+ if field not in bundle:
524
+ return error_result(
525
+ "render", t0, ErrorClass.SCHEMA,
526
+ f"bundle missing required field: {field}",
527
+ )
528
+
529
+ ver = str(bundle["canonical_format_version"])
530
+ if not ver.startswith("1."):
531
+ return error_result(
532
+ "render", t0, ErrorClass.SCHEMA,
533
+ f"unsupported canonical_format_version {ver!r} "
534
+ f"(this server speaks {_SUPPORTED_CANONICAL_FORMAT})",
535
+ )
536
+
537
+ tome_str = bundle["canonical_tome"]
538
+ if not isinstance(tome_str, str) or len(tome_str) > MAX_TOME_CHARS:
539
+ return error_result(
540
+ "render", t0, ErrorClass.INPUT_TOO_LARGE,
541
+ f"canonical_tome exceeds {MAX_TOME_CHARS} chars",
542
+ )
543
+
544
+ from sum_engine_internal.ensemble.tome_sliders import TomeSliders
545
+
546
+ try:
547
+ sliders = TomeSliders(
548
+ density=density, length=length,
549
+ formality=formality, audience=audience,
550
+ perspective=perspective,
551
+ )
552
+ except (ValueError, TypeError) as e:
553
+ return error_result(
554
+ "render", t0, ErrorClass.SCHEMA,
555
+ f"invalid slider value: {e}",
556
+ )
557
+
558
+ if sliders.requires_extrapolator():
559
+ non_neutral = [
560
+ f"{name}={getattr(sliders, name)}"
561
+ for name in ("length", "formality", "audience", "perspective")
562
+ if abs(getattr(sliders, name) - 0.5) > 1e-9
563
+ ]
564
+ return error_result(
565
+ "render", t0, ErrorClass.SCHEMA,
566
+ "non-neutral LLM-conditioned axes ("
567
+ + ", ".join(non_neutral)
568
+ + ") require an LLM extrapolator. The MCP server's "
569
+ "render tool is local-only (deterministic density "
570
+ "slider) — drop the affected sliders to 0.5, or "
571
+ "use the Worker's POST /api/render endpoint for "
572
+ "LLM-conditioned rendering.",
573
+ )
574
+
575
+ triples: list[tuple[str, str, str]] = []
576
+ for line in tome_str.splitlines():
577
+ m = _TOME_LINE_PATTERN.match(line.strip())
578
+ if m:
579
+ if len(triples) >= MAX_AXIOM_COUNT:
580
+ return error_result(
581
+ "render", t0, ErrorClass.INPUT_TOO_LARGE,
582
+ f"axiom count exceeds {MAX_AXIOM_COUNT}",
583
+ )
584
+ triples.append((m.group(1), m.group(2), m.group(3)))
585
+
586
+ if not triples:
587
+ return error_result(
588
+ "render", t0, ErrorClass.STRUCTURAL,
589
+ "bundle's canonical_tome contains zero parseable "
590
+ "axiom lines — nothing to render.",
591
+ )
592
+
593
+ from sum_engine_internal.algorithms.semantic_arithmetic import GodelStateAlgebra
594
+ from sum_engine_internal.ensemble.tome_generator import AutoregressiveTomeGenerator
595
+
596
+ algebra = GodelStateAlgebra()
597
+ state = 1
598
+ for s, p, o in triples:
599
+ state = math.lcm(state, algebra.get_or_mint_prime(s, p, o))
600
+
601
+ tome_gen = AutoregressiveTomeGenerator(algebra)
602
+ tome_text = tome_gen.generate_controlled(state, sliders=sliders, title=title)
603
+
604
+ return success_result(
605
+ "render",
606
+ t0,
607
+ tome=tome_text,
608
+ sliders={
609
+ "density": sliders.density,
610
+ "length": sliders.length,
611
+ "formality": sliders.formality,
612
+ "audience": sliders.audience,
613
+ "perspective": sliders.perspective,
614
+ },
615
+ mode="local-deterministic",
616
+ axiom_count_input=len(triples),
617
+ title=title,
618
+ )
619
+ except Exception as exc:
620
+ return error_result(
621
+ "render", t0, ErrorClass.INTERNAL, type(exc).__name__
622
+ )
623
+
475
624
  # ------------------------------------------------------------------
476
625
  # schema
477
626
  # ------------------------------------------------------------------
@@ -0,0 +1,21 @@
1
+ """sum_engine_internal.research — research-grade modules.
2
+
3
+ Modules in this package are NOT part of the production install path.
4
+ They live behind the ``[research]`` extras flag in pyproject.toml so
5
+ that ``pip install sum-engine`` does not pull research dependencies
6
+ (numpy, scipy) by default. Use ``pip install 'sum-engine[research]'``.
7
+
8
+ Modules:
9
+
10
+ sheaf_laplacian
11
+ v1 sheaf-Laplacian hallucination detector. Implements the
12
+ primitives specified in docs/SHEAF_HALLUCINATION_DETECTOR.md
13
+ §3.2 (1-dim presence stalks). Math is grounded in Gebhart,
14
+ Hansen & Schrater (2023, AISTATS, arXiv:2110.03789) Eq. 1
15
+ and the sheaf-Laplacian theory of Hansen & Ghrist (2019).
16
+
17
+ Stability: research-grade. APIs may change between minor releases
18
+ without backwards-compatibility guarantees. Production-stable
19
+ counterparts will live elsewhere if/when the research artifacts
20
+ benchmark sufficiently.
21
+ """
@@ -0,0 +1,231 @@
1
+ """v1 sheaf-Laplacian hallucination detector.
2
+
3
+ Faithful to docs/SHEAF_HALLUCINATION_DETECTOR.md §3.2 (v1, 1-dim
4
+ presence stalks). Math grounded in Gebhart, Hansen & Schrater
5
+ (2023, AISTATS, arXiv:2110.03789) Equation 1 and the sheaf-Laplacian
6
+ theory of Hansen & Ghrist (2019).
7
+
8
+ Every function below references the spec equation it implements.
9
+ The v1 detector lives behind the ``[research]`` extras flag in
10
+ ``pyproject.toml`` — it is not on the production install path.
11
+
12
+ v1 known blindspots (verified empirically by the synthetic
13
+ micro-benchmark in scripts/research/sheaf_microbench.py and pinned
14
+ by Tests/research/test_sheaf_laplacian.py):
15
+
16
+ - Predicate-flip perturbations (A2): invisible. Presence stalks
17
+ do not carry predicate information. v2 (learned-embedding stalks)
18
+ is required for predicate-sensitive detection.
19
+ - Off-graph fact-fabrication (A3): invisible. Entities not in the
20
+ source vertex set are silently ignored by ``cochain_from_extracted``.
21
+ v2 is required to flag fabricated entities.
22
+ - Empty-render false negative: a render that extracts zero triples
23
+ yields x = 0, hence x^T L x = 0 — the same score as a perfectly
24
+ consistent render. Callers should treat n_extracted == 0 as a
25
+ separate signal, not rely on the Laplacian alone.
26
+
27
+ v1 verified-positive detection classes (6/6 detect rate, 100%
28
+ top-1 localization accuracy on the synthetic micro-benchmark):
29
+
30
+ - Entity-swap (A1): one source entity replaced.
31
+ - Triple-drop (A4): one triple omitted; isolated endpoints vanish.
32
+ - Consistent-entity-swap (A5): the SAME swap applied across the
33
+ full render manifold. Caught by the *mean* Laplacian even though
34
+ per-render variance is zero. (The spec originally
35
+ mischaracterized this as a v1 blindspot; corrected in §6 to
36
+ distinguish A5-via-swap (caught) from A5-via-predicate-flip
37
+ (missed).)
38
+ """
39
+ from __future__ import annotations
40
+
41
+ from dataclasses import dataclass
42
+ import numpy as np
43
+
44
+
45
+ Triple = tuple[str, str, str]
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class KnowledgeSheaf:
50
+ """A cellular sheaf on a knowledge graph (Gebhart et al. 2023, Def. 4).
51
+
52
+ For v1 (1-dim presence stalks):
53
+ F(v) = R for every vertex v
54
+ F(e) = R for every edge e
55
+ F_h⊵_h r = F_t⊵_t r = 1 (identity on R)
56
+
57
+ The vertex order is fixed at construction so cochains are
58
+ indexable as plain numpy arrays.
59
+
60
+ Note on ``stalk_dim``: v1 only supports ``stalk_dim=1``.
61
+ Construction with any other value is rejected at
62
+ ``__post_init__`` time so callers cannot build a sheaf they
63
+ can't use. v2 (text-embedding stalks) will lift this constraint;
64
+ until then, attempting to use higher-dimensional stalks should
65
+ fail loudly at the boundary — not silently surface as a
66
+ NotImplementedError mid-pipeline.
67
+ """
68
+ vertices: tuple[str, ...]
69
+ edges: tuple[Triple, ...] # (subject, predicate, object)
70
+ vertex_index: dict[str, int] # name → index into cochain vector
71
+ stalk_dim: int # = 1 for v1
72
+
73
+ def __post_init__(self) -> None:
74
+ if self.stalk_dim != 1:
75
+ raise ValueError(
76
+ f"v1 KnowledgeSheaf supports stalk_dim=1 only "
77
+ f"(got stalk_dim={self.stalk_dim}). v2 with text-"
78
+ f"embedding stalks (1536-dim) is the planned "
79
+ f"successor; see docs/SHEAF_HALLUCINATION_DETECTOR.md "
80
+ f"§5.3 for the v1 → v2 plan."
81
+ )
82
+
83
+ @classmethod
84
+ def from_triples(cls, triples: list[Triple], stalk_dim: int = 1) -> "KnowledgeSheaf":
85
+ seen: dict[str, None] = {}
86
+ for s, _, o in triples:
87
+ seen.setdefault(s, None)
88
+ seen.setdefault(o, None)
89
+ vertices = tuple(seen)
90
+ return cls(
91
+ vertices=vertices,
92
+ edges=tuple(triples),
93
+ vertex_index={v: i for i, v in enumerate(vertices)},
94
+ stalk_dim=stalk_dim,
95
+ )
96
+
97
+
98
+ def coboundary_matrix(sheaf: KnowledgeSheaf) -> np.ndarray:
99
+ """δ : C^0 → C^1 as a matrix.
100
+
101
+ Spec §2.1 / Gebhart Def. 4: for an edge e: u → v with identity
102
+ restriction maps, (δx)_e = x_v - x_u. Each row of δ has
103
+ exactly two non-zeros: -1 at u's column, +1 at v's column.
104
+
105
+ Shape: (|E|, |V|) for stalk_dim=1; (|E|*d, |V|*d) when d>1
106
+ using a Kronecker product. v1 uses d=1, enforced at sheaf
107
+ construction time (see ``KnowledgeSheaf.__post_init__``).
108
+ """
109
+ # Defensive — should be unreachable since KnowledgeSheaf rejects
110
+ # non-1 stalk_dim at construction. Kept as an invariant so a
111
+ # future v2 KnowledgeSheaf subclass that lifts the restriction
112
+ # but forgets to override this function fails loudly here.
113
+ assert sheaf.stalk_dim == 1, (
114
+ f"coboundary_matrix v1 requires stalk_dim=1, got {sheaf.stalk_dim}"
115
+ )
116
+ n_v = len(sheaf.vertices)
117
+ n_e = len(sheaf.edges)
118
+ delta = np.zeros((n_e, n_v), dtype=np.float64)
119
+ for i, (s, _, o) in enumerate(sheaf.edges):
120
+ delta[i, sheaf.vertex_index[s]] = -1.0 # F_u⊵e applied to head
121
+ delta[i, sheaf.vertex_index[o]] = +1.0 # F_v⊵e applied to tail
122
+ return delta
123
+
124
+
125
+ def sheaf_laplacian(sheaf: KnowledgeSheaf) -> np.ndarray:
126
+ """L_F = δ^T δ (Gebhart Eq. 1; Hansen-Ghrist 2019)."""
127
+ delta = coboundary_matrix(sheaf)
128
+ return delta.T @ delta
129
+
130
+
131
+ def laplacian_quadratic_form(sheaf: KnowledgeSheaf, x: np.ndarray) -> float:
132
+ """x^T L_F x. Zero iff x is a global section.
133
+
134
+ For v1 1-dim stalks, x ∈ R^|V|; this measures sum over edges of
135
+ (x_v - x_u)^2.
136
+ """
137
+ L = sheaf_laplacian(sheaf)
138
+ return float(x @ L @ x)
139
+
140
+
141
+ def per_edge_discrepancy(sheaf: KnowledgeSheaf, x: np.ndarray) -> list[tuple[Triple, float]]:
142
+ """Per-edge contribution to the Laplacian quadratic form.
143
+
144
+ Used for the localization claim (P2 in the spec). Returns
145
+ [(edge, |F_v⊵e x_v - F_u⊵e x_u|^2), ...] sorted descending.
146
+ """
147
+ delta = coboundary_matrix(sheaf)
148
+ edge_residual = delta @ x # shape (|E|,)
149
+ contribs = [
150
+ (sheaf.edges[i], float(edge_residual[i] ** 2))
151
+ for i in range(len(sheaf.edges))
152
+ ]
153
+ contribs.sort(key=lambda kv: kv[1], reverse=True)
154
+ return contribs
155
+
156
+
157
+ def cochain_from_extracted(
158
+ sheaf: KnowledgeSheaf,
159
+ extracted_triples: list[Triple],
160
+ ) -> np.ndarray:
161
+ """Build the 0-cochain x ∈ C^0(G; F_1d) from a render's
162
+ re-extracted triples (spec §3.2 step 3b).
163
+
164
+ x_v = 1 if v appears as subject or object in extracted_triples,
165
+ else 0. This is the 1-dim presence indicator.
166
+ """
167
+ mentioned: set[str] = set()
168
+ for s, _, o in extracted_triples:
169
+ mentioned.add(s)
170
+ mentioned.add(o)
171
+ x = np.zeros(len(sheaf.vertices), dtype=np.float64)
172
+ for v in mentioned:
173
+ if v in sheaf.vertex_index:
174
+ x[sheaf.vertex_index[v]] = 1.0
175
+ return x
176
+
177
+
178
+ def consistency_profile(
179
+ source_triples: list[Triple],
180
+ rendered_extractions: list[list[Triple]],
181
+ ) -> dict:
182
+ """Mean & std of the Laplacian quadratic form across a render
183
+ manifold. Spec §3.2 step 5.
184
+
185
+ rendered_extractions: a list, one entry per rendering, each a
186
+ list of (s, p, o) triples extracted from that rendering.
187
+
188
+ Returns the consistency profile envelope shape from spec §3.5
189
+ (without the receipt-binding parts, which are v3). When the
190
+ render manifold is empty, returns a profile with explicit
191
+ null fields rather than a misleading zero score.
192
+ """
193
+ sheaf = KnowledgeSheaf.from_triples(source_triples, stalk_dim=1)
194
+
195
+ # Empty manifold has no signal to report; return explicit nulls
196
+ # so callers can branch on render_count == 0 without having to
197
+ # interpret a fabricated all-zero profile.
198
+ if not rendered_extractions:
199
+ return {
200
+ "render_count": 0,
201
+ "stalk_dim": sheaf.stalk_dim,
202
+ "version": "v1-presence-stalks",
203
+ "mean_laplacian": None,
204
+ "std_laplacian": None,
205
+ "max_per_render": None,
206
+ "argmax_render_idx": None,
207
+ "per_render_v": [],
208
+ "per_edge_top3_argmax_render": [],
209
+ }
210
+
211
+ per_render_v: list[float] = []
212
+ per_render_localization: list[list[tuple[Triple, float]]] = []
213
+ for triples_n in rendered_extractions:
214
+ x_n = cochain_from_extracted(sheaf, triples_n)
215
+ v_n = laplacian_quadratic_form(sheaf, x_n)
216
+ per_render_v.append(v_n)
217
+ per_render_localization.append(per_edge_discrepancy(sheaf, x_n))
218
+
219
+ arr = np.array(per_render_v)
220
+ argmax_idx = int(arr.argmax())
221
+ return {
222
+ "render_count": len(rendered_extractions),
223
+ "stalk_dim": sheaf.stalk_dim,
224
+ "version": "v1-presence-stalks",
225
+ "mean_laplacian": float(arr.mean()),
226
+ "std_laplacian": float(arr.std()),
227
+ "max_per_render": float(arr.max()),
228
+ "argmax_render_idx": argmax_idx,
229
+ "per_render_v": per_render_v,
230
+ "per_edge_top3_argmax_render": per_render_localization[argmax_idx][:3],
231
+ }
File without changes
File without changes
File without changes