tibet-continuityd 0.3.3__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 (29) hide show
  1. tibet_continuityd-0.3.3/PKG-INFO +128 -0
  2. tibet_continuityd-0.3.3/README.md +95 -0
  3. tibet_continuityd-0.3.3/pyproject.toml +62 -0
  4. tibet_continuityd-0.3.3/setup.cfg +4 -0
  5. tibet_continuityd-0.3.3/src/tibet_continuityd/__init__.py +105 -0
  6. tibet_continuityd-0.3.3/src/tibet_continuityd/__main__.py +7 -0
  7. tibet_continuityd-0.3.3/src/tibet_continuityd/backpressure.py +185 -0
  8. tibet_continuityd-0.3.3/src/tibet_continuityd/coalesce.py +143 -0
  9. tibet_continuityd-0.3.3/src/tibet_continuityd/daemon.py +698 -0
  10. tibet_continuityd-0.3.3/src/tibet_continuityd/police.py +281 -0
  11. tibet_continuityd-0.3.3/src/tibet_continuityd/seal.py +227 -0
  12. tibet_continuityd-0.3.3/src/tibet_continuityd/sniff.py +242 -0
  13. tibet_continuityd-0.3.3/src/tibet_continuityd/trust_kernel.py +364 -0
  14. tibet_continuityd-0.3.3/src/tibet_continuityd/verify_fork.py +306 -0
  15. tibet_continuityd-0.3.3/src/tibet_continuityd/watch.py +203 -0
  16. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/PKG-INFO +128 -0
  17. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/SOURCES.txt +27 -0
  18. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/dependency_links.txt +1 -0
  19. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/entry_points.txt +2 -0
  20. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/requires.txt +10 -0
  21. tibet_continuityd-0.3.3/src/tibet_continuityd.egg-info/top_level.txt +1 -0
  22. tibet_continuityd-0.3.3/tests/test_backpressure.py +213 -0
  23. tibet_continuityd-0.3.3/tests/test_coalesce.py +62 -0
  24. tibet_continuityd-0.3.3/tests/test_daemon.py +367 -0
  25. tibet_continuityd-0.3.3/tests/test_police.py +255 -0
  26. tibet_continuityd-0.3.3/tests/test_seal.py +347 -0
  27. tibet_continuityd-0.3.3/tests/test_sniff.py +150 -0
  28. tibet_continuityd-0.3.3/tests/test_trust_kernel.py +223 -0
  29. tibet_continuityd-0.3.3/tests/test_verify_fork.py +411 -0
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: tibet-continuityd
3
+ Version: 0.3.3
4
+ Summary: Continuous integrity system daemon for the Distributed Continuity OS — Watch, Sniff, Coalesce, Verify, Fork, Trust, Seal, Police, Backpressure across 3 modes (passive/active/strict).
5
+ Author-email: Jasper van de Meent <info@humotica.com>, Root AI <root_idd@humotica.nl>, Codex <codex@humotica.nl>
6
+ License: MIT
7
+ Project-URL: Homepage, https://humotica.com
8
+ Project-URL: Spec, https://github.com/Humotica/tibet-continuityd/blob/main/SPEC.md
9
+ Keywords: tibet,continuity,daemon,audit,trust,inotify,libmagic,icc,tbz,ssm,phantom,distributed-continuity-os,humotica
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Security :: Cryptography
21
+ Classifier: Topic :: System :: Monitoring
22
+ Classifier: Topic :: System :: Logging
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ Provides-Extra: verify
26
+ Requires-Dist: tibet-drop>=0.1.0; extra == "verify"
27
+ Requires-Dist: tibet-phantom>=0.2.0; extra == "verify"
28
+ Provides-Extra: full
29
+ Requires-Dist: tibet-drop>=0.1.0; extra == "full"
30
+ Requires-Dist: tibet-phantom>=0.2.0; extra == "full"
31
+ Requires-Dist: cryptography>=41.0; extra == "full"
32
+ Requires-Dist: cbor2>=5.4; extra == "full"
33
+
34
+ # tibet-continuityd
35
+
36
+ > **Continuous Integrity System Daemon for the Distributed Continuity OS.**
37
+
38
+ A residential trust-guardian that runs in the background of every
39
+ machine where TIBET cryptographic discipline must be continuously
40
+ enforced.
41
+
42
+ ```
43
+ Watch → Sniff → Verify → Fork → Triage → Reseal
44
+ inotify libmagic Ed25519 forward quarant periodic
45
+ +chain causal -ine
46
+ ```
47
+
48
+ ## Design axiom
49
+
50
+ > **Name is hint. Content is truth. Arrival is event.**
51
+ >
52
+ > — Codex, 9 May 2026
53
+
54
+ ## v0.1 scope (this release)
55
+
56
+ - ✅ `Watch` — inotify on a single inbox lane
57
+ - ✅ `Sniff` — magic-byte recognition (TBZ + executables + PDF + JSON)
58
+ - ✅ Disposition classification per Codex' intake policy table
59
+ - ✅ Audit JSONL log + journald-friendly stderr logging
60
+ - ✅ Mode 1 (Passive Guardian): observe + log + advise
61
+ - ✅ systemd unit ready for deployment
62
+ - ⏭ v0.2 will add: `Verify` (cryptographic) + `Fork` (via phantom.icc)
63
+ - ⏭ v0.3 will add: `Seal` (continuous reseal) + `Police` (unpacked detect)
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install tibet-continuityd
69
+ ```
70
+
71
+ ## Run (development)
72
+
73
+ ```bash
74
+ TIBET_CONTINUITYD_INBOX=/tmp/inbox \
75
+ TIBET_CONTINUITYD_AUDIT=/tmp/audit.jsonl \
76
+ python3 -m tibet_continuityd
77
+ ```
78
+
79
+ Drop a TBZ-prefixed file into `/tmp/inbox/`:
80
+
81
+ ```bash
82
+ printf 'TBZ\x01\x00\x00\x00' > /tmp/inbox/sample.claude.tza
83
+ ```
84
+
85
+ You will see:
86
+
87
+ ```
88
+ arrival: 'sample.claude.tza' → sealed-tbz (trusted-candidate, 7B)
89
+ ```
90
+
91
+ ## Run (production, systemd)
92
+
93
+ ```bash
94
+ sudo cp tibet-continuityd.service /etc/systemd/system/
95
+ sudo useradd -r -s /usr/sbin/nologin tibet
96
+ sudo mkdir -p /var/lib/tibet/{inbox,quarantine,triage,materialized}
97
+ sudo mkdir -p /var/log/tibet
98
+ sudo chown -R tibet:tibet /var/lib/tibet /var/log/tibet
99
+ sudo systemctl daemon-reload
100
+ sudo systemctl enable --now tibet-continuityd
101
+ journalctl -u tibet-continuityd -f
102
+ ```
103
+
104
+ ## Disposition table (v0.1)
105
+
106
+ | Intake class | Trigger | Disposition |
107
+ |-------------------------|------------------------------------------|----------------------|
108
+ | `sealed-tbz` | TBZ magic + recognized vendor extension | trusted-candidate |
109
+ | `sealed-tbz-no-ext` | TBZ magic, no/unknown extension | trusted-candidate |
110
+ | `disguised` | Vendor extension, no TBZ magic | triage-disguised |
111
+ | `executable` | ELF / PE binary | quarantine |
112
+ | `pdf` | PDF magic | reject |
113
+ | `json-text` | Plain JSON state in sealed-only zone | reseal-candidate |
114
+ | `unknown` | Anything else | quarantine |
115
+ | `empty` | Zero-byte file | reject |
116
+
117
+ v0.1 logs the disposition only. v0.2+ will act on it
118
+ (Verify + Fork or Quarantine + Triage).
119
+
120
+ ## Architecture & spec
121
+
122
+ - Spec: [`tibet-continuityd-spec.md`](https://humotica.com/specs/continuityd)
123
+ - Companion intake guide (Codex):
124
+ [`tibet-continuity-guardian.md`](https://humotica.com/specs/continuity-guardian)
125
+
126
+ ## License
127
+
128
+ MIT — Humotica + Root AI + Codex (2026)
@@ -0,0 +1,95 @@
1
+ # tibet-continuityd
2
+
3
+ > **Continuous Integrity System Daemon for the Distributed Continuity OS.**
4
+
5
+ A residential trust-guardian that runs in the background of every
6
+ machine where TIBET cryptographic discipline must be continuously
7
+ enforced.
8
+
9
+ ```
10
+ Watch → Sniff → Verify → Fork → Triage → Reseal
11
+ inotify libmagic Ed25519 forward quarant periodic
12
+ +chain causal -ine
13
+ ```
14
+
15
+ ## Design axiom
16
+
17
+ > **Name is hint. Content is truth. Arrival is event.**
18
+ >
19
+ > — Codex, 9 May 2026
20
+
21
+ ## v0.1 scope (this release)
22
+
23
+ - ✅ `Watch` — inotify on a single inbox lane
24
+ - ✅ `Sniff` — magic-byte recognition (TBZ + executables + PDF + JSON)
25
+ - ✅ Disposition classification per Codex' intake policy table
26
+ - ✅ Audit JSONL log + journald-friendly stderr logging
27
+ - ✅ Mode 1 (Passive Guardian): observe + log + advise
28
+ - ✅ systemd unit ready for deployment
29
+ - ⏭ v0.2 will add: `Verify` (cryptographic) + `Fork` (via phantom.icc)
30
+ - ⏭ v0.3 will add: `Seal` (continuous reseal) + `Police` (unpacked detect)
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install tibet-continuityd
36
+ ```
37
+
38
+ ## Run (development)
39
+
40
+ ```bash
41
+ TIBET_CONTINUITYD_INBOX=/tmp/inbox \
42
+ TIBET_CONTINUITYD_AUDIT=/tmp/audit.jsonl \
43
+ python3 -m tibet_continuityd
44
+ ```
45
+
46
+ Drop a TBZ-prefixed file into `/tmp/inbox/`:
47
+
48
+ ```bash
49
+ printf 'TBZ\x01\x00\x00\x00' > /tmp/inbox/sample.claude.tza
50
+ ```
51
+
52
+ You will see:
53
+
54
+ ```
55
+ arrival: 'sample.claude.tza' → sealed-tbz (trusted-candidate, 7B)
56
+ ```
57
+
58
+ ## Run (production, systemd)
59
+
60
+ ```bash
61
+ sudo cp tibet-continuityd.service /etc/systemd/system/
62
+ sudo useradd -r -s /usr/sbin/nologin tibet
63
+ sudo mkdir -p /var/lib/tibet/{inbox,quarantine,triage,materialized}
64
+ sudo mkdir -p /var/log/tibet
65
+ sudo chown -R tibet:tibet /var/lib/tibet /var/log/tibet
66
+ sudo systemctl daemon-reload
67
+ sudo systemctl enable --now tibet-continuityd
68
+ journalctl -u tibet-continuityd -f
69
+ ```
70
+
71
+ ## Disposition table (v0.1)
72
+
73
+ | Intake class | Trigger | Disposition |
74
+ |-------------------------|------------------------------------------|----------------------|
75
+ | `sealed-tbz` | TBZ magic + recognized vendor extension | trusted-candidate |
76
+ | `sealed-tbz-no-ext` | TBZ magic, no/unknown extension | trusted-candidate |
77
+ | `disguised` | Vendor extension, no TBZ magic | triage-disguised |
78
+ | `executable` | ELF / PE binary | quarantine |
79
+ | `pdf` | PDF magic | reject |
80
+ | `json-text` | Plain JSON state in sealed-only zone | reseal-candidate |
81
+ | `unknown` | Anything else | quarantine |
82
+ | `empty` | Zero-byte file | reject |
83
+
84
+ v0.1 logs the disposition only. v0.2+ will act on it
85
+ (Verify + Fork or Quarantine + Triage).
86
+
87
+ ## Architecture & spec
88
+
89
+ - Spec: [`tibet-continuityd-spec.md`](https://humotica.com/specs/continuityd)
90
+ - Companion intake guide (Codex):
91
+ [`tibet-continuity-guardian.md`](https://humotica.com/specs/continuity-guardian)
92
+
93
+ ## License
94
+
95
+ MIT — Humotica + Root AI + Codex (2026)
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tibet-continuityd"
7
+ version = "0.3.3"
8
+ description = "Continuous integrity system daemon for the Distributed Continuity OS — Watch, Sniff, Coalesce, Verify, Fork, Trust, Seal, Police, Backpressure across 3 modes (passive/active/strict)."
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "Jasper van de Meent", email = "info@humotica.com"},
14
+ {name = "Root AI", email = "root_idd@humotica.nl"},
15
+ {name = "Codex", email = "codex@humotica.nl"},
16
+ ]
17
+ keywords = [
18
+ "tibet", "continuity", "daemon", "audit", "trust",
19
+ "inotify", "libmagic", "icc", "tbz", "ssm", "phantom",
20
+ "distributed-continuity-os", "humotica",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: System Administrators",
25
+ "Intended Audience :: Information Technology",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: POSIX :: Linux",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Security :: Cryptography",
34
+ "Topic :: System :: Monitoring",
35
+ "Topic :: System :: Logging",
36
+ ]
37
+
38
+ # v0.3 has zero hard runtime dependencies; uses ctypes for inotify
39
+ # and an in-package magic-byte table. Optional dependencies enable
40
+ # cryptographic verify, pack, and reseal stages.
41
+ dependencies = []
42
+
43
+ [project.optional-dependencies]
44
+ # Verify + Fork + Seal stages with cryptographic discipline
45
+ verify = [
46
+ "tibet-drop>=0.1.0",
47
+ "tibet-phantom>=0.2.0",
48
+ ]
49
+ # Full stack including phantom-icc bridge
50
+ full = [
51
+ "tibet-drop>=0.1.0",
52
+ "tibet-phantom>=0.2.0",
53
+ "cryptography>=41.0",
54
+ "cbor2>=5.4",
55
+ ]
56
+
57
+ [project.scripts]
58
+ tibet-continuityd = "tibet_continuityd.daemon:main"
59
+
60
+ [project.urls]
61
+ Homepage = "https://humotica.com"
62
+ Spec = "https://github.com/Humotica/tibet-continuityd/blob/main/SPEC.md"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,105 @@
1
+ """
2
+ tibet-continuityd — Continuous Integrity System Daemon
3
+ =======================================================
4
+
5
+ A residential trust guardian that runs in the background of
6
+ every machine where TIBET cryptographic discipline must be
7
+ continuously enforced.
8
+
9
+ Watch → inotify
10
+ Sniff → libmagic / TBZ magic-byte recognition
11
+ Verify → (v0.2) cryptographic verification
12
+ Fork → (v0.2) forward-causal materialize via phantom.icc
13
+ Triage → (v0.2) quarantine on mismatch
14
+ Reseal → (v0.3) periodic reseal of materialized state
15
+
16
+ Three operating modes:
17
+ Mode 1 Passive Guardian observe + log + advise
18
+ Mode 2 Sealing Guardian auto-reseal + active intake
19
+ Mode 3 Strict Continuity zero-trust, ICC/TBZ only
20
+
21
+ Spec: /srv/jtel-stack/hersenspinsels/tibet-continuityd-spec.md
22
+ Plus: /srv/jtel-stack/hersenspinsels/tibet-continuity-guardian.md
23
+ (Codex' parallel intake-discipline guide)
24
+
25
+ "Name is hint. Content is truth. Arrival is event."
26
+ — Codex, 9 mei 2026
27
+ """
28
+
29
+ __version__ = "0.3.3"
30
+ __author__ = "Jasper van de Meent, Root AI, Codex"
31
+
32
+ # Core stages — pure stdlib, always available
33
+ from tibet_continuityd.backpressure import (
34
+ BackpressureMonitor,
35
+ BackpressureSnapshot,
36
+ BackpressureState,
37
+ )
38
+ from tibet_continuityd.police import (
39
+ FindingSeverity,
40
+ PoliceAction,
41
+ PoliceFinding,
42
+ PoliceScanner,
43
+ apply_action,
44
+ )
45
+ from tibet_continuityd.sniff import IntakeClass, sniff_payload
46
+ from tibet_continuityd.trust_kernel import (
47
+ TrustQuery,
48
+ TrustVerdict,
49
+ apply_verdict_to_disposition,
50
+ load_policies,
51
+ query_trust_kernel,
52
+ )
53
+ from tibet_continuityd.watch import LaneWatcher, WatchEvent
54
+
55
+ # Verify + Fork + Seal — require tibet-drop (install via [verify] extra)
56
+ # Defensive imports: core daemon works without these.
57
+ try:
58
+ from tibet_continuityd.verify_fork import (
59
+ CausalIDs,
60
+ VerifyForkResult,
61
+ verify_and_fork,
62
+ )
63
+ from tibet_continuityd.seal import SealEngine, SealResult
64
+ from tibet_continuityd.daemon import ContinuityDaemon
65
+ _HAS_VERIFY = True
66
+ except ImportError:
67
+ # tibet-drop not on sys.path — verify/fork/seal/daemon unavailable.
68
+ # Core sniff/police/trust_kernel/watch/backpressure still work.
69
+ CausalIDs = None # type: ignore
70
+ VerifyForkResult = None # type: ignore
71
+ verify_and_fork = None # type: ignore
72
+ SealEngine = None # type: ignore
73
+ SealResult = None # type: ignore
74
+ ContinuityDaemon = None # type: ignore
75
+ _HAS_VERIFY = False
76
+
77
+ __all__ = [
78
+ # Core (always available)
79
+ "LaneWatcher",
80
+ "WatchEvent",
81
+ "IntakeClass",
82
+ "sniff_payload",
83
+ "TrustQuery",
84
+ "TrustVerdict",
85
+ "query_trust_kernel",
86
+ "apply_verdict_to_disposition",
87
+ "load_policies",
88
+ "PoliceFinding",
89
+ "PoliceScanner",
90
+ "PoliceAction",
91
+ "FindingSeverity",
92
+ "apply_action",
93
+ "BackpressureMonitor",
94
+ "BackpressureSnapshot",
95
+ "BackpressureState",
96
+ # Verify-stage (None when tibet-drop unavailable)
97
+ "ContinuityDaemon",
98
+ "CausalIDs",
99
+ "VerifyForkResult",
100
+ "verify_and_fork",
101
+ "SealEngine",
102
+ "SealResult",
103
+ # Capability flag
104
+ "_HAS_VERIFY",
105
+ ]
@@ -0,0 +1,7 @@
1
+ """CLI entry: `python -m tibet_continuityd`."""
2
+ import sys
3
+
4
+ from tibet_continuityd.daemon import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
@@ -0,0 +1,185 @@
1
+ """
2
+ backpressure.py — Backpressure / circuit-breaker (v0.3.2, axe 3).
3
+
4
+ Per Jaspers prompt 9 mei 2026:
5
+
6
+ "MUX 500 files/sec, sniff/verify 100/sec. Inbox > 5000 onverwerkt
7
+ → daemon signaleert MUX/network 'Halt' of TCP-windowing-stijl
8
+ backpressure. Geen self-inflicted DoS."
9
+
10
+ This module implements that protection: monitor the inbox depth
11
+ and emit state-transition audit events when pressure rises or
12
+ falls. The daemon itself does NOT throttle its own work-rate
13
+ (that would worsen things) — it signals UPSTREAM that producer
14
+ should slow down.
15
+
16
+ State machine:
17
+
18
+ NORMAL depth < low_water
19
+
20
+ │ depth ≥ low_water
21
+
22
+ PRESSURE_RISING low_water ≤ depth < high_water
23
+
24
+ │ depth ≥ high_water
25
+
26
+ OVERLOADED depth ≥ high_water
27
+
28
+ │ depth < low_water (hysteresis)
29
+
30
+ RECOVERING transitional state
31
+
32
+ │ depth stable < low_water
33
+
34
+ NORMAL
35
+
36
+ Hysteresis: NORMAL → OVERLOADED requires high_water,
37
+ OVERLOADED → NORMAL requires going back below
38
+ low_water (not just back below high_water).
39
+ This prevents oscillation at the boundary.
40
+
41
+ Signal mechanism (v0.3.2 minimal):
42
+ emit "backpressure" audit-record at every state-transition
43
+ (= upstream consumers can read audit-stream and react)
44
+
45
+ Future v0.3.x extensions:
46
+ - write signal-file at /var/lib/tibet/backpressure-state
47
+ - HTTP endpoint POST to mux upstream
48
+ - TIBET token emission for cross-host signaling
49
+ """
50
+ from __future__ import annotations
51
+
52
+ import time
53
+ from dataclasses import dataclass, field
54
+ from enum import Enum
55
+ from pathlib import Path
56
+ from typing import Optional
57
+
58
+
59
+ class BackpressureState(Enum):
60
+ """Daemon's current capacity state."""
61
+ NORMAL = "normal" # depth < low_water
62
+ PRESSURE_RISING = "pressure-rising" # low ≤ depth < high
63
+ OVERLOADED = "overloaded" # depth ≥ high_water
64
+ RECOVERING = "recovering" # was overloaded, depth dropping
65
+
66
+
67
+ @dataclass
68
+ class BackpressureSnapshot:
69
+ """One observation of inbox depth + current state."""
70
+ state: BackpressureState
71
+ inbox_depth: int # number of unprocessed files
72
+ low_water: int # threshold for "rising"
73
+ high_water: int # threshold for "overloaded"
74
+ transitioned: bool # state changed since last check
75
+ prev_state: Optional[BackpressureState] = None
76
+ ts_unix: float = field(default_factory=time.time)
77
+
78
+ def to_dict(self) -> dict:
79
+ return {
80
+ "state": self.state.value,
81
+ "inbox_depth": self.inbox_depth,
82
+ "low_water": self.low_water,
83
+ "high_water": self.high_water,
84
+ "transitioned": self.transitioned,
85
+ "prev_state": self.prev_state.value
86
+ if self.prev_state else None,
87
+ "ts_unix": self.ts_unix,
88
+ }
89
+
90
+
91
+ @dataclass
92
+ class BackpressureMonitor:
93
+ """Track inbox depth and compute state transitions.
94
+
95
+ Usage:
96
+ monitor = BackpressureMonitor(
97
+ lane=Path("/var/lib/tibet/inbox"),
98
+ low_water=2000,
99
+ high_water=5000,
100
+ )
101
+ snapshot = monitor.check()
102
+ if snapshot.transitioned:
103
+ # emit audit, optionally signal upstream
104
+ ...
105
+ """
106
+ lane: Path
107
+ low_water: int = 2000
108
+ high_water: int = 5000
109
+ _state: BackpressureState = BackpressureState.NORMAL
110
+
111
+ def __post_init__(self):
112
+ if self.low_water >= self.high_water:
113
+ raise ValueError(
114
+ f"low_water ({self.low_water}) must be < "
115
+ f"high_water ({self.high_water})"
116
+ )
117
+
118
+ def _measure_depth(self) -> int:
119
+ """Count files in lane (excluding subdirectories)."""
120
+ if not self.lane.exists() or not self.lane.is_dir():
121
+ return 0
122
+ try:
123
+ return sum(1 for entry in self.lane.iterdir()
124
+ if entry.is_file())
125
+ except OSError:
126
+ return 0
127
+
128
+ def _classify_state(self, depth: int) -> BackpressureState:
129
+ """Compute target state from depth WITHOUT hysteresis logic.
130
+
131
+ Hysteresis is applied in check() — this is the raw mapping.
132
+ """
133
+ if depth >= self.high_water:
134
+ return BackpressureState.OVERLOADED
135
+ if depth >= self.low_water:
136
+ return BackpressureState.PRESSURE_RISING
137
+ return BackpressureState.NORMAL
138
+
139
+ def check(self) -> BackpressureSnapshot:
140
+ """Measure depth and update state with hysteresis."""
141
+ depth = self._measure_depth()
142
+ prev = self._state
143
+ target = self._classify_state(depth)
144
+
145
+ # Hysteresis: OVERLOADED can ONLY drop to RECOVERING (not
146
+ # straight to NORMAL via PRESSURE_RISING). This prevents
147
+ # oscillation when depth hovers near low_water.
148
+ new_state = target
149
+ if prev == BackpressureState.OVERLOADED and \
150
+ target != BackpressureState.OVERLOADED:
151
+ # transition out of overloaded → recovering first
152
+ if target == BackpressureState.NORMAL:
153
+ new_state = BackpressureState.RECOVERING
154
+ # else: target is PRESSURE_RISING, accept it
155
+ elif prev == BackpressureState.RECOVERING:
156
+ # In RECOVERING, stay until depth is fully back under
157
+ # low_water (= NORMAL target).
158
+ if target == BackpressureState.NORMAL:
159
+ new_state = BackpressureState.NORMAL
160
+ elif target == BackpressureState.OVERLOADED:
161
+ new_state = BackpressureState.OVERLOADED
162
+ else:
163
+ new_state = BackpressureState.RECOVERING
164
+
165
+ transitioned = (new_state != prev)
166
+ self._state = new_state
167
+
168
+ return BackpressureSnapshot(
169
+ state=new_state,
170
+ inbox_depth=depth,
171
+ low_water=self.low_water,
172
+ high_water=self.high_water,
173
+ transitioned=transitioned,
174
+ prev_state=prev if transitioned else None,
175
+ )
176
+
177
+
178
+ # ─── Public API ─────────────────────────────────────────────────
179
+
180
+
181
+ __all__ = [
182
+ "BackpressureMonitor",
183
+ "BackpressureSnapshot",
184
+ "BackpressureState",
185
+ ]