stepcheck-dev 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,135 @@
1
+ """Isolated module namespace loading for callable invariants. Private.
2
+
3
+ `load_namespace()` exec-loads one module's staged source into a fresh dict.
4
+ Imports of *project* modules resolve to the kernel's canonical sources (never
5
+ to the files on disk, which only change once a commit has passed) through a temporary
6
+ meta-path finder; everything else imports normally. This is an isolated
7
+ namespace, not a security sandbox — invariants run the agent's own code.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import importlib.abc
12
+ import importlib.machinery
13
+ import importlib.util
14
+ import sys
15
+ import types
16
+
17
+ _PREFIX = "_stepcheck_ns_"
18
+
19
+
20
+ class _CanonicalFinder(importlib.abc.MetaPathFinder, importlib.abc.Loader):
21
+ """Serves project modules — flat or inside packages — from canonical sources."""
22
+
23
+ def __init__(self, sources: dict[str, str], packages: set[str]):
24
+ self.sources = sources
25
+ self.packages = packages
26
+
27
+ def find_spec(self, fullname, path=None, target=None):
28
+ if fullname in self.sources:
29
+ return importlib.machinery.ModuleSpec(
30
+ fullname, self, origin=f"<okf:{fullname}>", is_package=fullname in self.packages
31
+ )
32
+ return None
33
+
34
+ def create_module(self, spec):
35
+ return None
36
+
37
+ def exec_module(self, module):
38
+ origin = module.__spec__.origin
39
+ module.__file__ = origin
40
+ exec(compile(self.sources[module.__name__], origin, "exec"), module.__dict__)
41
+
42
+
43
+ def load_namespace(
44
+ node_id: str,
45
+ source: str,
46
+ project_sources: dict[str, str],
47
+ project_paths: dict[str, str] | None = None,
48
+ ) -> dict:
49
+ """Return the module namespace of `source` with project imports resolved.
50
+
51
+ `project_paths` (node_id -> file path) turns node ids into import names
52
+ through _okf.module_map, so `pkg/__init__.py` loads as package `pkg`,
53
+ `from . import sibling` inside it resolves to the sibling's canonical
54
+ source, and source-root aliases work too: `from kernel import X` inside
55
+ `backend/server.py` binds the canonical `backend/kernel/__init__.py`, not
56
+ whatever sys.path holds. Parents without their own `__init__.py` become
57
+ empty namespace packages. Without paths every node is a flat top-level
58
+ module named after its id. Real modules that share a name are shadowed
59
+ only while loading and restored afterwards."""
60
+ from . import _okf
61
+
62
+ paths = dict(project_paths or {})
63
+ by_node = dict(project_sources)
64
+ by_node[node_id] = source
65
+ if paths:
66
+ names = _okf.module_map({nid: p for nid, p in paths.items() if nid in by_node})
67
+ else:
68
+ names = {nid: nid for nid in by_node}
69
+ sources = {name: by_node[nid] for name, nid in names.items()}
70
+ target = _okf.module_name(paths[node_id]) if node_id in paths else node_id
71
+ sources.setdefault(target, source)
72
+ inits = {
73
+ nid for nid, p in paths.items() if p.replace("\\", "/").rsplit("/", 1)[-1] == "__init__.py"
74
+ }
75
+ packages = {name for name, nid in names.items() if nid in inits}
76
+ for mod in list(sources):
77
+ parts = mod.split(".")
78
+ for i in range(1, len(parts)):
79
+ prefix = ".".join(parts[:i])
80
+ packages.add(prefix)
81
+ sources.setdefault(prefix, "")
82
+
83
+ def owned(modname: str) -> bool:
84
+ return modname in sources or any(modname.startswith(p + ".") for p in packages)
85
+
86
+ finder = _CanonicalFinder(sources, packages)
87
+ shadowed = {n: sys.modules.pop(n) for n in list(sys.modules) if owned(n)}
88
+ sys.meta_path.insert(0, finder)
89
+ try:
90
+ module = importlib.import_module(target)
91
+ return dict(module.__dict__)
92
+ finally:
93
+ sys.meta_path.remove(finder)
94
+ for n in list(sys.modules):
95
+ if owned(n):
96
+ del sys.modules[n]
97
+ sys.modules.update(shadowed)
98
+
99
+
100
+ # -------------------------------------------------------- execution guard
101
+
102
+ STEP_BUDGET = 200_000 # Python line events
103
+ WALL_BUDGET_SECONDS = 0.5 # fallback for work that emits few line events
104
+
105
+
106
+ class ExecutionBudgetExceeded(RuntimeError):
107
+ """Raised inside the guarded code by the trace hook."""
108
+
109
+
110
+ def guarded(fn, *, steps: int = STEP_BUDGET, seconds: float = WALL_BUDGET_SECONDS):
111
+ """Run `fn()` under a per-thread trace hook that aborts at `steps` line
112
+ events or `seconds` of wall clock, whichever first. Deterministic on the
113
+ step axis; the clock is a backstop. Recursion depth is left to Python's
114
+ own RecursionError. Thread-local: sys.settrace affects this thread only."""
115
+ import time
116
+
117
+ deadline = time.monotonic() + seconds
118
+ count = 0
119
+
120
+ def tracer(frame, event, arg):
121
+ nonlocal count
122
+ if event == "line":
123
+ count += 1
124
+ if count > steps:
125
+ raise ExecutionBudgetExceeded(f"exceeded {steps:,} execution steps")
126
+ if time.monotonic() > deadline:
127
+ raise ExecutionBudgetExceeded(f"exceeded {seconds:.1f}s wall clock")
128
+ return tracer
129
+
130
+ previous = sys.gettrace()
131
+ sys.settrace(tracer)
132
+ try:
133
+ return fn()
134
+ finally:
135
+ sys.settrace(previous)
@@ -0,0 +1,52 @@
1
+ """Telemetry math — the one place rate constants live (GLOSSARY: Telemetry terms).
2
+
3
+ Private implementation of StateKernel; reached only through get_telemetry().
4
+ Raw model: an unfirewalled agent re-reads the full source of every file touched
5
+ so far on every turn. A re-read more than CACHE_CLIFF_SECONDS after the previous
6
+ one bills at full input rate; inside the window it bills at CACHE_HIT_FRACTION.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import math
11
+
12
+ USD_PER_MTOK_INPUT = 3.00 # Sonnet-class input rate
13
+ CACHE_HIT_FRACTION = 0.1
14
+ CACHE_CLIFF_SECONDS = 300.0
15
+
16
+
17
+ def compute(events: list[dict]) -> tuple[int, int, int]:
18
+ """-> (raw_bloated_tokens, compacted_okf_tokens, turns).
19
+
20
+ Events are the session's ordered checkout/commit records, each carrying
21
+ `node_id`, `tokens` (what the firewall actually sent), `full_tokens`
22
+ (the node's full-source size at that moment) and `at` (seconds; None means
23
+ "same instant as the previous event").
24
+ """
25
+ raw = 0
26
+ compacted = 0
27
+ turns = 0
28
+ touched: dict[str, int] = {} # node_id -> latest full-source tokens
29
+ last_read: dict[str, float] = {}
30
+ clock = 0.0
31
+
32
+ for ev in events:
33
+ if ev["kind"] not in ("checkout", "commit"):
34
+ continue
35
+ turns += 1
36
+ at = ev["at"] if ev.get("at") is not None else clock
37
+ clock = at
38
+ compacted += ev["tokens"]
39
+ touched[ev["node_id"]] = ev["full_tokens"]
40
+ for node_id, full in touched.items():
41
+ prev = last_read.get(node_id)
42
+ if prev is None or (at - prev) > CACHE_CLIFF_SECONDS:
43
+ raw += full
44
+ else:
45
+ raw += math.ceil(full * CACHE_HIT_FRACTION)
46
+ last_read[node_id] = at
47
+
48
+ return raw, compacted, turns
49
+
50
+
51
+ def dollars(tokens_saved: int) -> float:
52
+ return tokens_saved * USD_PER_MTOK_INPUT / 1_000_000
@@ -0,0 +1,7 @@
1
+ """Deterministic token accounting (GLOSSARY: Telemetry terms). Private module."""
2
+ import math
3
+
4
+
5
+ def count(text: str) -> int:
6
+ """ceil(len/4) — deterministic, provider-free."""
7
+ return math.ceil(len(text) / 4)