From a028e21f9780200aca97295438d886b0905dd0ff Mon Sep 17 00:00:00 2001 From: BoggersTheFish Date: Tue, 21 Jul 2026 21:42:37 +0100 Subject: [PATCH] feat: import typed tension kernel from TS-Core --- docs/migration/import-ledger.md | 2 +- docs/migration/sources/TS-Core.md | 57 ++++++++ examples/typed_tension_kernel_demo.py | 86 ++++++++++++ .../core/typed_tension/__init__.py | 38 ++++++ .../core/typed_tension/channels.py | 31 +++++ .../core/typed_tension/evolve.py | 20 +++ .../core/typed_tension/graph.py | 96 ++++++++++++++ .../core/typed_tension/receipts.py | 17 +++ .../core/typed_tension/runtime.py | 52 ++++++++ .../core/typed_tension/schema.py | 38 ++++++ .../core/typed_tension/types.py | 125 ++++++++++++++++++ tests/test_ts_core_typed_tension.py | 70 ++++++++++ 12 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 docs/migration/sources/TS-Core.md create mode 100644 examples/typed_tension_kernel_demo.py create mode 100644 src/thinking_system/core/typed_tension/__init__.py create mode 100644 src/thinking_system/core/typed_tension/channels.py create mode 100644 src/thinking_system/core/typed_tension/evolve.py create mode 100644 src/thinking_system/core/typed_tension/graph.py create mode 100644 src/thinking_system/core/typed_tension/receipts.py create mode 100644 src/thinking_system/core/typed_tension/runtime.py create mode 100644 src/thinking_system/core/typed_tension/schema.py create mode 100644 src/thinking_system/core/typed_tension/types.py create mode 100644 tests/test_ts_core_typed_tension.py diff --git a/docs/migration/import-ledger.md b/docs/migration/import-ledger.md index 595a94b..ff4e866 100644 --- a/docs/migration/import-ledger.md +++ b/docs/migration/import-ledger.md @@ -51,7 +51,7 @@ Empty placeholder directories (no tracked source): `packages/ts-*`, top-level `e | Repository | Verified source SHA in this clone? | Destination claimed historically | Status | Notes | |------------|-------------------------------------|----------------------------------|--------|-------| -| TS-Core | Not verified here | `packages/ts-core`, `packages/ts-kernel` | **INSPECTED** / **PLANNED** | Empty `packages/`; kernel is native monorepo `core/kernel` | +| TS-Core | `3ef48ad00efef8659ad0981d71de509a9827f584` | `src/thinking_system/core/typed_tension/` | **PARTIALLY_IMPORTED** | Domain-neutral typed tension kernel imported with its focused test and example; legacy Python application layers, Rust acceleration, CLI, Z3, Grok, UI, and Kernel Wave 12 surfaces remain unimported | | cig-ts-engine | Not verified here | `packages/ts-graph` | **INSPECTED** / **PLANNED** | Graph lives in `core/graph` | | TS-Benchmarks | Not verified here | `benchmarks/` | **PLANNED** | `benchmarks/suites` empty | | ts-chat-language | Not verified here | `packages/ts-language` | **INSPECTED** / **PLANNED** | TSLC-like code in `core/language/tslc.py` is in-tree monorepo code; full satellite port not evidenced | diff --git a/docs/migration/sources/TS-Core.md b/docs/migration/sources/TS-Core.md new file mode 100644 index 0000000..7334c8c --- /dev/null +++ b/docs/migration/sources/TS-Core.md @@ -0,0 +1,57 @@ +# TS-Core consolidation record + +## Source + +- Repository: `BoggersTheFish/TS-Core` +- Source branch: `master` +- Source commit: `3ef48ad00efef8659ad0981d71de509a9827f584` +- Licence: MIT + +## Imported surface + +The domain-neutral typed tension kernel from the satellite's `ts_core/` +package was imported into: + +`src/thinking_system/core/typed_tension/` + +The corresponding focused test and generic demonstration were imported into: + +- `tests/test_ts_core_typed_tension.py` +- `examples/typed_tension_kernel_demo.py` + +## Adaptation + +Internal package source was copied without architectural rewriting. + +Test and demonstration imports were changed from: + +`ts_core` + +to: + +`thinking_system.core.typed_tension` + +## Deliberately not imported + +The following TS-Core surfaces remain outside the monorepo pending separate +classification: + +- legacy `src/python/` application and narrative layers +- TUI and Streamlit interfaces +- Grok/xAI integration +- Z3 toy alignment tooling +- training-data utilities +- Docker and Ollama configuration +- Rust/PyO3 acceleration +- Kernel Wave 12 and Daily Spin application modes +- legacy `tscore` and `tscore-daily` commands + +These are not represented as consolidated by this import. + +## Current status + +**PARTIALLY_IMPORTED** + +TS-Core must not be archived solely because of this import. Remaining unique +surfaces must be classified as importable, obsolete, historical, or independent +before an archival decision. diff --git a/examples/typed_tension_kernel_demo.py b/examples/typed_tension_kernel_demo.py new file mode 100644 index 0000000..cb98637 --- /dev/null +++ b/examples/typed_tension_kernel_demo.py @@ -0,0 +1,86 @@ +"""Generic typed tension kernel demo. + +This example intentionally avoids reasoning-specific relations. It shows a +channel that detects an unbalanced resource edge and resolves it by adding a +traceable balancing edge. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from thinking_system.core.typed_tension import ( + ChannelResult, + Edge, + GraphState, + Node, + ResolverEvent, + TensionChannel, + TypedTensionRuntime, +) + + +class BalanceChannel(TensionChannel): + name = "generic_balance" + version = "0.1.0" + + def activate(self, graph: GraphState, context: dict) -> bool: + return any(edge.relation == "requires" for edge in graph.edges) + + def measure(self, graph: GraphState, context: dict) -> ChannelResult: + missing = [ + edge + for edge in graph.edges + if edge.relation == "requires" + and not graph.has_edge(edge.target, edge.source, "stabilizes") + ] + tension = 1.0 if missing else 0.0 + return ChannelResult( + channel=self.name, + activated=bool(missing), + initial_tension=tension, + final_tension=tension, + evidence=[f"{edge.source}->{edge.target}" for edge in missing], + details={"missing_stabilizers": len(missing)}, + ) + + def resolve(self, graph: GraphState, context: dict) -> ResolverEvent: + for edge in list(graph.edges): + if edge.relation == "requires" and not graph.has_edge( + edge.target, edge.source, "stabilizes" + ): + graph.add_edge(Edge(edge.target, edge.source, "stabilizes", weight=0.5)) + return ResolverEvent( + channel=self.name, + action="added_stabilizing_edge", + status="resolved", + target=f"{edge.target}->{edge.source}", + tension_delta=-1.0, + evidence=[f"{edge.source}->{edge.target}"], + ) + return ResolverEvent(channel=self.name, action="no_op", status="settled") + + +def main() -> None: + graph = GraphState() + graph.add_node(Node("process", activation=0.8, stability=0.6)) + graph.add_node(Node("resource", activation=0.5, stability=0.9)) + graph.add_edge(Edge("process", "resource", "requires")) + trace = TypedTensionRuntime([BalanceChannel()]).run(graph) + print( + json.dumps( + {"graph": graph.to_dict(), "trace": trace.to_dict()}, + indent=2, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/src/thinking_system/core/typed_tension/__init__.py b/src/thinking_system/core/typed_tension/__init__.py new file mode 100644 index 0000000..3032ba5 --- /dev/null +++ b/src/thinking_system/core/typed_tension/__init__.py @@ -0,0 +1,38 @@ +"""Typed tension kernel surface for TS-Core.""" + +from .channels import TensionChannel +from .evolve import EvolveHook, apply_evolve_hooks +from .graph import GraphState +from .receipts import write_receipt +from .runtime import TypedTensionRuntime +from .schema import validate_runtime_trace +from .types import ( + ActivationState, + ChannelResult, + Edge, + Node, + Receipt, + ResolverEvent, + RuntimeTrace, + to_jsonable, + utc_now, +) + +__all__ = [ + "ActivationState", + "ChannelResult", + "Edge", + "EvolveHook", + "GraphState", + "Node", + "Receipt", + "ResolverEvent", + "RuntimeTrace", + "TensionChannel", + "TypedTensionRuntime", + "apply_evolve_hooks", + "to_jsonable", + "utc_now", + "validate_runtime_trace", + "write_receipt", +] diff --git a/src/thinking_system/core/typed_tension/channels.py b/src/thinking_system/core/typed_tension/channels.py new file mode 100644 index 0000000..b3f43c3 --- /dev/null +++ b/src/thinking_system/core/typed_tension/channels.py @@ -0,0 +1,31 @@ +"""Typed tension channel interface.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any + +from .graph import GraphState +from .types import ChannelResult, ResolverEvent + + +class TensionChannel(ABC): + """A typed operational route for measuring and resolving one tension kind.""" + + name = "unnamed" + version = "0.1.0" + + @abstractmethod + def activate(self, graph: GraphState, context: dict[str, Any]) -> bool: + """Return true when this channel has work to measure or resolve.""" + + @abstractmethod + def measure(self, graph: GraphState, context: dict[str, Any]) -> ChannelResult: + """Measure this channel's unresolved pressure.""" + + @abstractmethod + def resolve(self, graph: GraphState, context: dict[str, Any]) -> ResolverEvent: + """Apply this channel's resolver operation to the graph or context.""" + + def serialize(self) -> dict[str, Any]: + return {"name": self.name, "version": self.version} diff --git a/src/thinking_system/core/typed_tension/evolve.py b/src/thinking_system/core/typed_tension/evolve.py new file mode 100644 index 0000000..10a98be --- /dev/null +++ b/src/thinking_system/core/typed_tension/evolve.py @@ -0,0 +1,20 @@ +"""Break/Evolve hook protocol for typed tension runtimes.""" + +from __future__ import annotations + +from typing import Protocol + +from .graph import GraphState +from .types import ResolverEvent + + +class EvolveHook(Protocol): + def __call__(self, graph: GraphState, event: ResolverEvent) -> None: ... + + +def apply_evolve_hooks( + graph: GraphState, events: list[ResolverEvent], hooks: list[EvolveHook] +) -> None: + for event in events: + for hook in hooks: + hook(graph, event) diff --git a/src/thinking_system/core/typed_tension/graph.py b/src/thinking_system/core/typed_tension/graph.py new file mode 100644 index 0000000..fb98566 --- /dev/null +++ b/src/thinking_system/core/typed_tension/graph.py @@ -0,0 +1,96 @@ +"""Domain-neutral graph state for typed tension runtimes.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Iterable + +from .types import Edge, Node, to_jsonable + + +@dataclass +class GraphState: + nodes: dict[str, Node] = field(default_factory=dict) + edges: list[Edge] = field(default_factory=list) + metadata: dict[str, Any] = field(default_factory=dict) + + def add_node(self, node: Node) -> None: + self.nodes[node.node_id] = node + + def add_edge(self, edge: Edge) -> None: + if edge.source not in self.nodes: + raise ValueError(f"unknown edge source: {edge.source}") + if edge.target not in self.nodes: + raise ValueError(f"unknown edge target: {edge.target}") + self.edges.append(edge) + + def outgoing(self, node_id: str, relation: str | None = None) -> list[Edge]: + return [ + edge + for edge in self.edges + if edge.source == node_id + and (relation is None or edge.relation == relation) + ] + + def incoming(self, node_id: str, relation: str | None = None) -> list[Edge]: + return [ + edge + for edge in self.edges + if edge.target == node_id + and (relation is None or edge.relation == relation) + ] + + def has_edge(self, source: str, target: str, relation: str | None = None) -> bool: + return any( + edge.source == source + and edge.target == target + and (relation is None or edge.relation == relation) + for edge in self.edges + ) + + def edge_keys(self) -> set[tuple[str, str, str]]: + return {(edge.source, edge.target, edge.relation) for edge in self.edges} + + def extend(self, nodes: Iterable[Node] = (), edges: Iterable[Edge] = ()) -> None: + for node in nodes: + self.add_node(node) + for edge in edges: + self.add_edge(edge) + + def to_dict(self) -> dict[str, Any]: + return { + "nodes": { + node_id: node.to_dict() for node_id, node in sorted(self.nodes.items()) + }, + "edges": [edge.to_dict() for edge in self.edges], + "metadata": to_jsonable(self.metadata), + } + + @classmethod + def from_dict(cls, payload: dict[str, Any]) -> "GraphState": + graph = cls(metadata=dict(payload.get("metadata") or {})) + raw_nodes = payload.get("nodes") or {} + if isinstance(raw_nodes, dict): + raw_nodes = raw_nodes.values() + for item in raw_nodes: + graph.add_node( + Node( + node_id=str(item.get("node_id") or item.get("id")), + kind=str(item.get("kind", "state")), + activation=float(item.get("activation", 0.0)), + stability=float(item.get("stability", 1.0)), + data=dict(item.get("data") or {}), + ) + ) + for item in payload.get("edges") or []: + graph.add_edge( + Edge( + source=str(item.get("source") or item.get("from")), + target=str(item.get("target") or item.get("to")), + relation=str(item.get("relation", "constraint")), + weight=float(item.get("weight", 1.0)), + directed=bool(item.get("directed", True)), + data=dict(item.get("data") or {}), + ) + ) + return graph diff --git a/src/thinking_system/core/typed_tension/receipts.py b/src/thinking_system/core/typed_tension/receipts.py new file mode 100644 index 0000000..e9f6642 --- /dev/null +++ b/src/thinking_system/core/typed_tension/receipts.py @@ -0,0 +1,17 @@ +"""Receipt helpers for TS artifacts.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from .types import Receipt + + +def write_receipt(receipt: Receipt, path: str | Path) -> Path: + target = Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text( + json.dumps(receipt.to_dict(), indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) + return target diff --git a/src/thinking_system/core/typed_tension/runtime.py b/src/thinking_system/core/typed_tension/runtime.py new file mode 100644 index 0000000..8436489 --- /dev/null +++ b/src/thinking_system/core/typed_tension/runtime.py @@ -0,0 +1,52 @@ +"""Runtime loop for typed tension channels.""" + +from __future__ import annotations + +from typing import Any, Iterable + +from .channels import TensionChannel +from .graph import GraphState +from .types import ChannelResult, RuntimeTrace + + +class TypedTensionRuntime: + def __init__(self, channels: Iterable[TensionChannel]) -> None: + self.channels = list(channels) + + def run( + self, graph: GraphState, context: dict[str, Any] | None = None + ) -> RuntimeTrace: + ctx = context if context is not None else {} + results: list[ChannelResult] = [] + events = [] + for channel in self.channels: + activated = channel.activate(graph, ctx) + if not activated: + result = ChannelResult(channel=channel.name, activated=False) + results.append(result) + continue + before = channel.measure(graph, ctx) + event = channel.resolve(graph, ctx) + after = channel.measure(graph, ctx) + result = ChannelResult( + channel=channel.name, + activated=True, + initial_tension=before.initial_tension, + final_tension=after.final_tension, + resolution=event.action, + evidence=after.evidence or before.evidence, + details={**before.details, **after.details, "event": event.to_dict()}, + ) + results.append(result) + events.append(event) + + global_tension = round( + sum(result.final_tension for result in results) / max(1, len(results)), 4 + ) + return RuntimeTrace( + channel_results=results, + resolver_events=events, + global_tension=global_tension, + settled=global_tension == 0.0, + metadata={"channel_count": len(self.channels)}, + ) diff --git a/src/thinking_system/core/typed_tension/schema.py b/src/thinking_system/core/typed_tension/schema.py new file mode 100644 index 0000000..dd01690 --- /dev/null +++ b/src/thinking_system/core/typed_tension/schema.py @@ -0,0 +1,38 @@ +"""Small schema checks for typed tension traces.""" + +from __future__ import annotations + +from typing import Any + +REQUIRED_TRACE_KEYS = { + "runtime", + "started_at", + "channel_results", + "resolver_events", + "global_tension", + "settled", +} + + +def validate_runtime_trace(payload: dict[str, Any]) -> list[str]: + errors: list[str] = [] + missing = REQUIRED_TRACE_KEYS - set(payload) + for key in sorted(missing): + errors.append(f"missing trace key: {key}") + if "channel_results" in payload and not isinstance( + payload["channel_results"], list + ): + errors.append("channel_results must be a list") + if "resolver_events" in payload and not isinstance( + payload["resolver_events"], list + ): + errors.append("resolver_events must be a list") + if "global_tension" in payload: + try: + value = float(payload["global_tension"]) + except (TypeError, ValueError): + errors.append("global_tension must be numeric") + else: + if value < 0.0: + errors.append("global_tension must be non-negative") + return errors diff --git a/src/thinking_system/core/typed_tension/types.py b/src/thinking_system/core/typed_tension/types.py new file mode 100644 index 0000000..8a61bf6 --- /dev/null +++ b/src/thinking_system/core/typed_tension/types.py @@ -0,0 +1,125 @@ +"""Typed tension kernel dataclasses. + +These types are deliberately domain-neutral. TS-Reasoner, CIG, and project +control layers should attach their own meaning through node/edge data and +channel implementations instead of baking that meaning into the kernel. +""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass, field, is_dataclass +from datetime import datetime, timezone +from typing import Any + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def to_jsonable(value: Any) -> Any: + if is_dataclass(value): + return {key: to_jsonable(item) for key, item in asdict(value).items()} + if isinstance(value, list): + return [to_jsonable(item) for item in value] + if isinstance(value, dict): + return {str(key): to_jsonable(item) for key, item in value.items()} + return value + + +@dataclass(frozen=True) +class Node: + node_id: str + kind: str = "state" + activation: float = 0.0 + stability: float = 1.0 + data: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class Edge: + source: str + target: str + relation: str = "constraint" + weight: float = 1.0 + directed: bool = True + data: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class ActivationState: + active_nodes: dict[str, float] = field(default_factory=dict) + context: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class ChannelResult: + channel: str + activated: bool + initial_tension: float = 0.0 + final_tension: float = 0.0 + resolution: str = "not_activated" + evidence: list[str] = field(default_factory=list) + details: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class ResolverEvent: + channel: str + action: str + status: str + target: str | None = None + tension_delta: float = 0.0 + evidence: list[str] = field(default_factory=list) + details: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class RuntimeTrace: + runtime: str = "ts_core.typed_tension" + started_at: str = field(default_factory=utc_now) + channel_results: list[ChannelResult] = field(default_factory=list) + resolver_events: list[ResolverEvent] = field(default_factory=list) + global_tension: float = 0.0 + settled: bool = True + metadata: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) + + +@dataclass(frozen=True) +class Receipt: + project: str + version: str + commit: str + date: str + claim: str + scope: str + inputs: list[Any] = field(default_factory=list) + commands_run: list[str] = field(default_factory=list) + tests: dict[str, Any] = field(default_factory=dict) + benchmarks: dict[str, Any] = field(default_factory=dict) + artifacts: list[dict[str, Any]] = field(default_factory=list) + known_limitations: list[str] = field(default_factory=list) + tensions_detected: list[str] = field(default_factory=list) + tensions_resolved: list[str] = field(default_factory=list) + unresolved_tensions: list[str] = field(default_factory=list) + public_claim_level: str = "experimental" + + def to_dict(self) -> dict[str, Any]: + return to_jsonable(self) diff --git a/tests/test_ts_core_typed_tension.py b/tests/test_ts_core_typed_tension.py new file mode 100644 index 0000000..c0ed28d --- /dev/null +++ b/tests/test_ts_core_typed_tension.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import json +import unittest + +from thinking_system.core.typed_tension import ( + ChannelResult, + Edge, + GraphState, + Node, + ResolverEvent, + TensionChannel, + TypedTensionRuntime, + validate_runtime_trace, +) + + +class DemoChannel(TensionChannel): + name = "demo" + + def activate(self, graph, context): + return graph.has_edge("a", "b", "needs") and not graph.has_edge( + "b", "a", "supports" + ) + + def measure(self, graph, context): + tension = 1.0 if self.activate(graph, context) else 0.0 + return ChannelResult( + channel=self.name, + activated=tension > 0, + initial_tension=tension, + final_tension=tension, + evidence=["a->b"] if tension else [], + ) + + def resolve(self, graph, context): + graph.add_edge(Edge("b", "a", "supports")) + return ResolverEvent( + channel=self.name, + action="added_support", + status="resolved", + tension_delta=-1.0, + ) + + +class TypedTensionKernelTests(unittest.TestCase): + def test_graph_nodes_edges_serialize_cleanly(self): + graph = GraphState() + graph.add_node(Node("a", activation=0.4)) + graph.add_node(Node("b", activation=0.7)) + graph.add_edge(Edge("a", "b", "needs")) + payload = graph.to_dict() + self.assertEqual(payload["nodes"]["a"]["node_id"], "a") + self.assertEqual(payload["edges"][0]["relation"], "needs") + json.dumps(payload) + + def test_runtime_channel_event_and_schema(self): + graph = GraphState() + graph.add_node(Node("a")) + graph.add_node(Node("b")) + graph.add_edge(Edge("a", "b", "needs")) + trace = TypedTensionRuntime([DemoChannel()]).run(graph) + payload = trace.to_dict() + self.assertEqual(payload["resolver_events"][0]["action"], "added_support") + self.assertEqual(payload["global_tension"], 0.0) + self.assertEqual(validate_runtime_trace(payload), []) + + +if __name__ == "__main__": + unittest.main()