chronolith-pro 3.2.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.
- chronolith_pro-3.2.0/LICENSE +22 -0
- chronolith_pro-3.2.0/PKG-INFO +68 -0
- chronolith_pro-3.2.0/README.md +37 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/__init__.py +1 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/anchor.py +241 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/archive_manager.py +77 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/automation_common.py +732 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/bootstrap_context.py +40 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/bootstrap_project.py +178 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/context_loader.py +160 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/continuity_status.py +86 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/continuity_suggest.py +67 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/decision_engine.py +137 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/discover_project.py +109 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/doc_parity_check.py +128 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/encoding_sanitizer.py +174 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/ene_optimizer.py +76 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/heal_parity.py +78 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/hook_utils.py +83 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/memory_graph_lite.py +64 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/notify_webhook.py +143 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/run_chronolith_cycle.py +996 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/secret_detector.py +69 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/sovereign_identity.py +135 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/sovereign_vault.py +281 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/summarize_memory.py +58 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/sync_external_dev_context.py +236 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/sync_translations.py +179 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/system_membership_check.py +69 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/tokenator.py +322 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/update_memory.py +81 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/vector_store_lite.py +69 -0
- chronolith_pro-3.2.0/chronolith_pro/chronolith/zip_bridge.py +88 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/PKG-INFO +68 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/SOURCES.txt +45 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/dependency_links.txt +1 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/entry_points.txt +2 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/requires.txt +18 -0
- chronolith_pro-3.2.0/chronolith_pro.egg-info/top_level.txt +1 -0
- chronolith_pro-3.2.0/pyproject.toml +41 -0
- chronolith_pro-3.2.0/setup.cfg +4 -0
- chronolith_pro-3.2.0/tests/test_anchor.py +169 -0
- chronolith_pro-3.2.0/tests/test_integrity.py +240 -0
- chronolith_pro-3.2.0/tests/test_logic.py +105 -0
- chronolith_pro-3.2.0/tests/test_parity_logic.py +44 -0
- chronolith_pro-3.2.0/tests/test_vault.py +196 -0
- chronolith_pro-3.2.0/tests/test_verify.py +150 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ethernium (X: @Steveblackbeard)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chronolith-pro
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: Chronolith (Pro): Industrial-grade AI chronolith framework with security audits and global synchronization.
|
|
5
|
+
Author: SteveBlackbeard
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SteveBlackbeard/CHRONOLITH-by-Ethernium
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/SteveBlackbeard/CHRONOLITH-by-Ethernium/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: typer>=0.9.0
|
|
16
|
+
Requires-Dist: rich>=13.0.0
|
|
17
|
+
Requires-Dist: tiktoken>=0.5.0
|
|
18
|
+
Requires-Dist: cryptography>=42.0.0
|
|
19
|
+
Provides-Extra: vector
|
|
20
|
+
Requires-Dist: chromadb>=0.4.0; extra == "vector"
|
|
21
|
+
Requires-Dist: sentence-transformers; extra == "vector"
|
|
22
|
+
Provides-Extra: anchor
|
|
23
|
+
Requires-Dist: opentimestamps>=0.4.0; extra == "anchor"
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: chromadb>=0.4.0; extra == "all"
|
|
26
|
+
Requires-Dist: sentence-transformers; extra == "all"
|
|
27
|
+
Requires-Dist: networkx; extra == "all"
|
|
28
|
+
Requires-Dist: rdflib; extra == "all"
|
|
29
|
+
Requires-Dist: opentimestamps>=0.4.0; extra == "all"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Chronolith Pro
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
Chronolith Pro is part of the Ethernium Chronolith Python runtime.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install chronolith-pro
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
chronolith-pro --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Role
|
|
51
|
+
|
|
52
|
+
Industrial chronolith with stronger checks, hooks, parity diagnostics, and token/context tooling.
|
|
53
|
+
|
|
54
|
+
## Governance Boundary
|
|
55
|
+
|
|
56
|
+
Chronolith is the Python runtime and governance kernel. Chronolith Conekta is an external visual control surface, and AgentOps is an extractable incubated tool.
|
|
57
|
+
|
|
58
|
+
## Quality Gate
|
|
59
|
+
|
|
60
|
+
Before release, run the root repository checks:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python scripts/health_guard.py --strict
|
|
64
|
+
python scripts/golden_baseline.py verify
|
|
65
|
+
pytest -q
|
|
66
|
+
python -m build
|
|
67
|
+
python -m twine check dist\*
|
|
68
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Chronolith Pro
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Chronolith Pro is part of the Ethernium Chronolith Python runtime.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install chronolith-pro
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
chronolith-pro --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Role
|
|
20
|
+
|
|
21
|
+
Industrial chronolith with stronger checks, hooks, parity diagnostics, and token/context tooling.
|
|
22
|
+
|
|
23
|
+
## Governance Boundary
|
|
24
|
+
|
|
25
|
+
Chronolith is the Python runtime and governance kernel. Chronolith Conekta is an external visual control surface, and AgentOps is an extractable incubated tool.
|
|
26
|
+
|
|
27
|
+
## Quality Gate
|
|
28
|
+
|
|
29
|
+
Before release, run the root repository checks:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python scripts/health_guard.py --strict
|
|
33
|
+
python scripts/golden_baseline.py verify
|
|
34
|
+
pytest -q
|
|
35
|
+
python -m build
|
|
36
|
+
python -m twine check dist\*
|
|
37
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core helpers for Chronolith."""
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Bitcoin anchoring for the DNA transparency chain (external witness).
|
|
2
|
+
|
|
3
|
+
The signed chain proves lineage *to anyone who trusts the sovereign key*. That is
|
|
4
|
+
the honest limit: a party who controls both the repo and the key can rebuild a
|
|
5
|
+
consistent chain. OpenTimestamps closes that gap — it stamps the chain head into
|
|
6
|
+
the Bitcoin blockchain, so a **skeptical third party** can verify that this exact
|
|
7
|
+
DNA state existed at a given time without trusting the operator at all. And
|
|
8
|
+
because a past Bitcoin timestamp cannot be forged even with a stolen key, the
|
|
9
|
+
anchor also gives forward-security against history rewrites after a key
|
|
10
|
+
compromise.
|
|
11
|
+
|
|
12
|
+
Design (matching the Ethernium Frugal anchor): anchoring is optional
|
|
13
|
+
infrastructure, never a blocker. If the `ots` client is installed it produces a
|
|
14
|
+
real `.ots` proof; if not, a local sovereign record is still written and install
|
|
15
|
+
instructions are printed. No hard dependency, no network call from this module
|
|
16
|
+
itself — the `ots` client owns the calendar/network interaction.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import subprocess
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
ANCHOR_DIRNAME = "anchors"
|
|
27
|
+
_OTS_CANDIDATES = ("ots", "ots.exe")
|
|
28
|
+
# Public OpenTimestamps aggregator calendars (same set the ots client uses).
|
|
29
|
+
_CALENDARS = (
|
|
30
|
+
"https://alice.btc.calendar.opentimestamps.org",
|
|
31
|
+
"https://bob.btc.calendar.opentimestamps.org",
|
|
32
|
+
"https://finney.calendar.eternitywall.com",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def library_available() -> bool:
|
|
37
|
+
try:
|
|
38
|
+
import opentimestamps # noqa: F401
|
|
39
|
+
return True
|
|
40
|
+
except ImportError:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def stamp_with_library(anchor_path: str | Path, *, timeout: int = 10, calendars=_CALENDARS) -> tuple[bool, str]:
|
|
45
|
+
"""Stamp a file via the opentimestamps Python library — the friction-free
|
|
46
|
+
path when the `ots` CLI is broken (its python-bitcoinlib/OpenSSL DLL issue on
|
|
47
|
+
Windows). This is a thin wrapper: the library owns the calendar protocol and
|
|
48
|
+
.ots serialization; we only submit the file's SHA-256 to the aggregator
|
|
49
|
+
calendars and write the returned proof. Requires network to the calendars.
|
|
50
|
+
Never raises."""
|
|
51
|
+
import hashlib
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
from opentimestamps.calendar import RemoteCalendar
|
|
55
|
+
from opentimestamps.core.op import OpSHA256
|
|
56
|
+
from opentimestamps.core.timestamp import DetachedTimestampFile, Timestamp
|
|
57
|
+
from opentimestamps.core.serialize import BytesSerializationContext
|
|
58
|
+
except ImportError:
|
|
59
|
+
return False, "opentimestamps library not installed (pip install chronolith-pro[anchor])"
|
|
60
|
+
|
|
61
|
+
path = Path(anchor_path)
|
|
62
|
+
digest = hashlib.sha256(path.read_bytes()).digest()
|
|
63
|
+
timestamp = Timestamp(digest)
|
|
64
|
+
reached = []
|
|
65
|
+
for url in calendars:
|
|
66
|
+
try:
|
|
67
|
+
timestamp.merge(RemoteCalendar(url).submit(digest, timeout=timeout))
|
|
68
|
+
reached.append(url)
|
|
69
|
+
except Exception:
|
|
70
|
+
continue # try the next calendar; one is enough
|
|
71
|
+
if not reached:
|
|
72
|
+
return False, "no OpenTimestamps calendar was reachable"
|
|
73
|
+
ctx = BytesSerializationContext()
|
|
74
|
+
DetachedTimestampFile(OpSHA256(), timestamp).serialize(ctx)
|
|
75
|
+
ots_path = path.with_suffix(path.suffix + ".ots")
|
|
76
|
+
ots_path.write_bytes(ctx.getbytes())
|
|
77
|
+
return True, f"{ots_path} (via {len(reached)} calendar(s))"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def anchor_dir(repo_root: str | Path) -> Path:
|
|
81
|
+
return Path(repo_root) / ".chronolith" / ANCHOR_DIRNAME
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_anchor_record(
|
|
85
|
+
chain_head: dict | None,
|
|
86
|
+
merkle_root: str,
|
|
87
|
+
sovereign_pub_hex: str | None,
|
|
88
|
+
) -> dict:
|
|
89
|
+
"""The record that gets timestamped. Anchoring `chain_head['entry_hash']`
|
|
90
|
+
(which hash-links the entire history) timestamps the whole lineage, not just
|
|
91
|
+
the current root."""
|
|
92
|
+
return {
|
|
93
|
+
"project": "Chronolith (Pro)",
|
|
94
|
+
"anchored_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
|
95
|
+
"method": "opentimestamps",
|
|
96
|
+
"merkle_root": merkle_root,
|
|
97
|
+
"chain_seq": chain_head.get("seq") if chain_head else None,
|
|
98
|
+
"chain_head_hash": chain_head.get("entry_hash") if chain_head else None,
|
|
99
|
+
"sovereign_public_key": sovereign_pub_hex,
|
|
100
|
+
"note": (
|
|
101
|
+
"The chain head hash links the full DNA lineage; a Bitcoin timestamp "
|
|
102
|
+
"over this file proves that exact state existed at the confirmed time, "
|
|
103
|
+
"verifiable by anyone with the .ots proof — no trust in the operator."
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def write_anchor(repo_root: str | Path, record: dict) -> Path:
|
|
109
|
+
directory = anchor_dir(repo_root)
|
|
110
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
tag = (record.get("chain_head_hash") or record.get("merkle_root") or "state")[:12]
|
|
112
|
+
path = directory / f"ANCHOR_{tag}.json"
|
|
113
|
+
path.write_text(json.dumps(record, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
114
|
+
return path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _find_ots() -> str | None:
|
|
118
|
+
for binary in _OTS_CANDIDATES:
|
|
119
|
+
try:
|
|
120
|
+
probe = subprocess.run([binary, "--version"], capture_output=True, text=True)
|
|
121
|
+
except (OSError, FileNotFoundError):
|
|
122
|
+
continue
|
|
123
|
+
if probe.returncode == 0 or probe.stdout or probe.stderr:
|
|
124
|
+
return binary
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def try_ots_stamp(anchor_path: str | Path) -> tuple[bool, str]:
|
|
129
|
+
"""Create a `.ots` proof via the OpenTimestamps client, if installed.
|
|
130
|
+
Returns (stamped, message). Never raises — anchoring must not block a run."""
|
|
131
|
+
binary = _find_ots()
|
|
132
|
+
if not binary:
|
|
133
|
+
return False, "ots client not installed"
|
|
134
|
+
try:
|
|
135
|
+
result = subprocess.run([binary, "stamp", str(anchor_path)], capture_output=True, text=True)
|
|
136
|
+
except OSError as exc:
|
|
137
|
+
return False, f"ots stamp failed: {exc}"
|
|
138
|
+
if result.returncode == 0:
|
|
139
|
+
return True, f"{anchor_path}.ots"
|
|
140
|
+
return False, (result.stderr or result.stdout or "ots stamp returned non-zero").strip()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def inspect_ots_proof(ots_path: str | Path) -> tuple[bool, str]:
|
|
144
|
+
"""Local structural check of a .ots proof (no network): confirmed if it
|
|
145
|
+
already carries a Bitcoin attestation, otherwise pending on the calendars.
|
|
146
|
+
Full blockchain confirmation still needs `ots verify` or a Bitcoin node."""
|
|
147
|
+
try:
|
|
148
|
+
from opentimestamps.core.serialize import BytesDeserializationContext
|
|
149
|
+
from opentimestamps.core.timestamp import DetachedTimestampFile
|
|
150
|
+
from opentimestamps.core.notary import BitcoinBlockHeaderAttestation
|
|
151
|
+
except ImportError:
|
|
152
|
+
return False, "opentimestamps library not installed"
|
|
153
|
+
path = Path(ots_path)
|
|
154
|
+
if not path.exists():
|
|
155
|
+
return False, f"proof not found: {path}"
|
|
156
|
+
try:
|
|
157
|
+
ctx = BytesDeserializationContext(path.read_bytes())
|
|
158
|
+
dtf = DetachedTimestampFile.deserialize(ctx)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
return False, f"malformed .ots proof: {exc}"
|
|
161
|
+
for _msg, attestation in dtf.timestamp.all_attestations():
|
|
162
|
+
if isinstance(attestation, BitcoinBlockHeaderAttestation):
|
|
163
|
+
return True, f"confirmed in Bitcoin block {attestation.height}"
|
|
164
|
+
return False, "pending: calendar attestation present, not yet confirmed on Bitcoin (upgrade later)"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _walk_timestamps(ts):
|
|
168
|
+
"""Yield a timestamp and all of its sub-timestamps (the proof tree)."""
|
|
169
|
+
yield ts
|
|
170
|
+
for _op, sub in ts.ops.items():
|
|
171
|
+
yield from _walk_timestamps(sub)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def upgrade_proof(ots_path: str | Path, *, timeout: int = 10) -> tuple[bool, str]:
|
|
175
|
+
"""Fetch Bitcoin attestations from the calendars for a pending proof and
|
|
176
|
+
rewrite the .ots. Returns (upgraded, message). Best-effort and DLL-safe: the
|
|
177
|
+
calendar fetch and attestation reads do NOT import python-bitcoinlib's
|
|
178
|
+
OpenSSL-dependent key module, so this works where the `ots` CLI crashes on
|
|
179
|
+
Windows. Right after stamping the calendar has nothing yet (pending); it
|
|
180
|
+
succeeds a few hours later once Bitcoin confirms."""
|
|
181
|
+
try:
|
|
182
|
+
from opentimestamps.calendar import RemoteCalendar
|
|
183
|
+
from opentimestamps.core.timestamp import DetachedTimestampFile
|
|
184
|
+
from opentimestamps.core.notary import PendingAttestation
|
|
185
|
+
from opentimestamps.core.serialize import (
|
|
186
|
+
BytesDeserializationContext, BytesSerializationContext,
|
|
187
|
+
)
|
|
188
|
+
except ImportError:
|
|
189
|
+
return False, "opentimestamps library not installed"
|
|
190
|
+
path = Path(ots_path)
|
|
191
|
+
try:
|
|
192
|
+
dtf = DetachedTimestampFile.deserialize(BytesDeserializationContext(path.read_bytes()))
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
return False, f"malformed .ots proof: {exc}"
|
|
195
|
+
upgraded = False
|
|
196
|
+
for sub in _walk_timestamps(dtf.timestamp):
|
|
197
|
+
for att in list(sub.attestations):
|
|
198
|
+
if isinstance(att, PendingAttestation):
|
|
199
|
+
uri = att.uri.decode() if isinstance(att.uri, bytes) else att.uri
|
|
200
|
+
try:
|
|
201
|
+
sub.merge(RemoteCalendar(uri).get_timestamp(sub.msg, timeout=timeout))
|
|
202
|
+
upgraded = True
|
|
203
|
+
except Exception:
|
|
204
|
+
continue # not confirmed yet, or calendar unreachable
|
|
205
|
+
if upgraded:
|
|
206
|
+
ctx = BytesSerializationContext()
|
|
207
|
+
dtf.serialize(ctx)
|
|
208
|
+
path.write_bytes(ctx.getbytes())
|
|
209
|
+
return upgraded, "upgraded from calendar" if upgraded else "no upgrade available yet (still pending)"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def verify_via_library(ots_path: str | Path) -> tuple[bool, str]:
|
|
213
|
+
"""Full-as-possible verification via the library (no broken CLI): upgrade the
|
|
214
|
+
proof from the calendars, then report whether it now carries a Bitcoin
|
|
215
|
+
attestation. Confirmed = the timestamp is anchored in a Bitcoin block."""
|
|
216
|
+
upgrade_proof(ots_path)
|
|
217
|
+
return inspect_ots_proof(ots_path)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def try_ots_verify(ots_path: str | Path) -> tuple[bool, str]:
|
|
221
|
+
"""Verify a `.ots` proof against the Bitcoin blockchain via the client.
|
|
222
|
+
Returns (verified, message)."""
|
|
223
|
+
binary = _find_ots()
|
|
224
|
+
if not binary:
|
|
225
|
+
return False, "ots client not installed (cannot verify the blockchain proof)"
|
|
226
|
+
path = Path(ots_path)
|
|
227
|
+
if not path.exists():
|
|
228
|
+
return False, f"proof not found: {path}"
|
|
229
|
+
try:
|
|
230
|
+
result = subprocess.run([binary, "verify", str(path)], capture_output=True, text=True)
|
|
231
|
+
except OSError as exc:
|
|
232
|
+
return False, f"ots verify failed: {exc}"
|
|
233
|
+
output = (result.stdout + result.stderr).strip()
|
|
234
|
+
# `ots verify` prints "Success! Bitcoin block ..." on a confirmed proof and a
|
|
235
|
+
# "Pending" notice while the calendar attestation awaits confirmation. Check
|
|
236
|
+
# pending FIRST — the pending message also mentions "Bitcoin".
|
|
237
|
+
if "pending" in output.lower():
|
|
238
|
+
return False, "pending: calendar attestation not yet confirmed on Bitcoin (retry in a few hours)"
|
|
239
|
+
if result.returncode == 0 and ("Success" in output or "Bitcoin block" in output):
|
|
240
|
+
return True, output
|
|
241
|
+
return False, output or "verification failed"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
archive_manager.py — CHRONOLITH Pro
|
|
4
|
+
==========================================
|
|
5
|
+
Systemic Log Rotation & Context Archiving.
|
|
6
|
+
|
|
7
|
+
Moves historical outputs and oversized log files from .chronolith/
|
|
8
|
+
to .chronolith/archive/ to prevent context window saturation while
|
|
9
|
+
preserving the project's 'Genetic Code'.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import json
|
|
14
|
+
import shutil
|
|
15
|
+
import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
# Config
|
|
19
|
+
MAX_LOG_SIZE_BYTES = 50 * 1024 # 50KB threshold for rotation
|
|
20
|
+
ARCHIVE_DIR_NAME = "archive"
|
|
21
|
+
|
|
22
|
+
def rotate_logs(repo_root: Path):
|
|
23
|
+
print(f"[*] Archive Manager: Evaluating log health in {repo_root}...")
|
|
24
|
+
|
|
25
|
+
chronolith_dir = repo_root / ".chronolith"
|
|
26
|
+
archive_dir = chronolith_dir / ARCHIVE_DIR_NAME
|
|
27
|
+
|
|
28
|
+
if not chronolith_dir.exists():
|
|
29
|
+
print("[!] No .chronolith directory found. Skipping archiving.")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Files to track for rotation
|
|
35
|
+
targets = [
|
|
36
|
+
"DECISIONS_LOG.md",
|
|
37
|
+
"TIMELINE.md",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
for target in targets:
|
|
41
|
+
file_path = chronolith_dir / target
|
|
42
|
+
if file_path.exists() and file_path.stat().st_size > MAX_LOG_SIZE_BYTES:
|
|
43
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
44
|
+
archive_name = f"{target.replace('.md', '')}_{timestamp}.md"
|
|
45
|
+
dest_path = archive_dir / archive_name
|
|
46
|
+
|
|
47
|
+
print(f" [>] Rotating {target} (Size: {file_path.stat().st_size} bytes)")
|
|
48
|
+
shutil.copy2(file_path, dest_path)
|
|
49
|
+
|
|
50
|
+
# Reset the active file with a link to the archive
|
|
51
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
52
|
+
f.write(f"# {target.replace('.md', '').replace('_', ' ')}\n")
|
|
53
|
+
f.write(f"> [!] LOG ROTATED on {timestamp}. See archive/{archive_name} for history.\n\n")
|
|
54
|
+
|
|
55
|
+
# Cleanup old cycle reports in outputs
|
|
56
|
+
outputs_dir = repo_root / "outputs" / "chronolith"
|
|
57
|
+
if outputs_dir.exists():
|
|
58
|
+
# Keep only the last 10 reports, archive the rest? Or just prune.
|
|
59
|
+
# For '' we archive them.
|
|
60
|
+
report_archive = archive_dir / "reports"
|
|
61
|
+
report_archive.mkdir(exist_ok=True)
|
|
62
|
+
|
|
63
|
+
all_reports = sorted(list(outputs_dir.glob("*.json")), key=os.path.getmtime)
|
|
64
|
+
if len(all_reports) > 10:
|
|
65
|
+
to_archive = all_reports[:-10]
|
|
66
|
+
print(f" [>] Archiving {len(to_archive)} historical reports...")
|
|
67
|
+
for r in to_archive:
|
|
68
|
+
shutil.move(str(r), str(report_archive / r.name))
|
|
69
|
+
|
|
70
|
+
print("[✔] Archiving Cycle Complete.")
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
import argparse
|
|
74
|
+
parser = argparse.ArgumentParser(description="Rotate and archive chronolith logs.")
|
|
75
|
+
parser.add_argument("--repo-root", default=".", help="Root directory of the project")
|
|
76
|
+
args = parser.parse_args()
|
|
77
|
+
rotate_logs(Path(args.repo_root).resolve())
|