jsonpit 0.1.4__tar.gz → 4.4.2__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.
- {jsonpit-0.1.4 → jsonpit-4.4.2}/PKG-INFO +28 -6
- {jsonpit-0.1.4 → jsonpit-4.4.2}/README.md +27 -5
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/__init__.py +7 -1
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/canonical.py +5 -5
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/changes.py +52 -33
- jsonpit-4.4.2/jsonpit/cli.py +1220 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/exceptions.py +4 -0
- jsonpit-4.4.2/jsonpit/icons.py +63 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/item.py +16 -3
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/store.py +47 -9
- {jsonpit-0.1.4 → jsonpit-4.4.2}/pyproject.toml +8 -1
- jsonpit-0.1.4/ADELE_SPIRIT.md +0 -97
- jsonpit-0.1.4/GEMINI.md +0 -42
- jsonpit-0.1.4/jsonpit/cli.py +0 -594
- jsonpit-0.1.4/packages/jpit/README.md +0 -34
- jsonpit-0.1.4/packages/jpit/jpit/__init__.py +0 -5
- jsonpit-0.1.4/packages/jpit/pyproject.toml +0 -29
- jsonpit-0.1.4/tests/fixtures/Person.pit +0 -1
- jsonpit-0.1.4/tests/run_all.py +0 -73
- jsonpit-0.1.4/tests/test_canonical.py +0 -58
- jsonpit-0.1.4/tests/test_change_file.py +0 -69
- jsonpit-0.1.4/tests/test_cli.py +0 -164
- jsonpit-0.1.4/tests/test_csharp_compatibility.py +0 -51
- jsonpit-0.1.4/tests/test_delete_property_projection.py +0 -201
- jsonpit-0.1.4/tests/test_equal_timestamp_ordering.py +0 -100
- jsonpit-0.1.4/tests/test_master_ticket.py +0 -83
- jsonpit-0.1.4/tests/test_pit_item.py +0 -91
- {jsonpit-0.1.4 → jsonpit-4.4.2}/.gitignore +0 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/config.py +0 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/flags.py +0 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/fs.py +0 -0
- {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/history.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: jsonpit
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.4.2
|
|
4
4
|
Summary: Daemon-free distributed storage over Cloud Drives for developers and AI agents — multi-process, immutable history, zero dependencies.
|
|
5
5
|
Author: Dr. Rainer Burkhardt
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -27,7 +27,18 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
|
|
28
28
|
`jsonpit` stores JsonPits—JSON files with an append-only value history—across machines and servers coordinated over synchronized Cloud Drives (`OneDrive`, `Dropbox`, `GoogleDrive`, `ICloudDrive`) without requiring a centralized database daemon.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The canonical **reference (lead) implementation** is C# `JsonPit` and the `pits` CLI in [RAIkeep](https://github.com/Burkhardt/RAIkeep). `jsonpit` is the pure-Python implementation engineered for 100% behavioral and physical storage parity with zero deviation.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Cross-Tool & Cross-Language Version Alignment (`v4.4.2`)
|
|
35
|
+
|
|
36
|
+
`jsonpit` and the `jpit` CLI are version-synchronized to **`v4.4.2`** in direct lockstep with C# `JsonPit` and `pits 4.4.2` in [RAIkeep](https://github.com/Burkhardt/RAIkeep).
|
|
37
|
+
|
|
38
|
+
### Why Harmonized Versioning is Essential:
|
|
39
|
+
- **Instant Compatibility Clarity:** Operators, DevOps pipelines, and autonomous AI agents immediately know whether `jpit` and `pits` share the exact same distributed protocol revision. If both tools report `4.4.x`, they guarantee identical master leasing rules, receipt mechanisms, 7-digit UTC timestamp parsing, and CR041 clean change file conventions.
|
|
40
|
+
- **Elimination of Multi-Language Matrix Confusion:** In heterogeneous environments (e.g. C# backend daemons paired with Python agent orchestrators and data science tooling), unified SemVer tags eliminate complex cross-compatibility lookup tables.
|
|
41
|
+
- **Joint Protocol Governance:** Architectural changes (such as CR040 anti-Read-Modify-Write rules and CR041 clean change file formatting) are certified across both languages under the exact same version milestone before release.
|
|
31
42
|
|
|
32
43
|
---
|
|
33
44
|
|
|
@@ -137,12 +148,23 @@ Person/
|
|
|
137
148
|
|
|
138
149
|
## Development & Testing
|
|
139
150
|
|
|
151
|
+
### Cross-Library Differential Parity Battery
|
|
152
|
+
|
|
153
|
+
To ensure 100% interoperability with C# `JsonPit`, this repository includes a dedicated **Differential Cross-Testing Suite** (`tests/suites/*.json5` & `tests/sequence_runner.py`). The test harness orchestrates bidirectional interaction between the Python runtime (`jpit`) and the C# CLI binary (`pits`):
|
|
154
|
+
|
|
155
|
+
- **Python Writes $\rightarrow$ C# Validates:** Python writes `.pit` snapshots and sparse changes; C# `pits export` and `pits verify` assert physical byte formatting, timestamp precision, and schema invariants.
|
|
156
|
+
- **C# Writes $\rightarrow$ Python Validates:** C# `pits seed`, `delete-property`, and `maintain` mutate state; Python `jpit get`, `history`, and `PitStore` verify projections and receipt mechanics.
|
|
157
|
+
- **7 Parity Suites (41 Cross-Engine Steps):** Covers persistence CRUD, CR041 clean change files, receipt grace periods, nested property tombstones, compaction/events archiving, process flag lifecycles, and split-master partition recovery.
|
|
158
|
+
|
|
159
|
+
For full suite descriptions, architecture diagrams, and certification tables, see:
|
|
160
|
+
👉 **[`doc/CROSS_ENGINE_PARITY_SPECIFICATION.md`](doc/CROSS_ENGINE_PARITY_SPECIFICATION.md)**
|
|
161
|
+
|
|
140
162
|
```bash
|
|
141
|
-
# Run
|
|
142
|
-
|
|
163
|
+
# Run the full test battery (Unit Tests + 7 Cross-Engine Parity Suites):
|
|
164
|
+
python3 tests/run_all.py
|
|
143
165
|
|
|
144
|
-
#
|
|
145
|
-
|
|
166
|
+
# Run a specific declarative parity suite directly:
|
|
167
|
+
python3 -m tests.sequence_runner tests/suites/01_persistence_and_crud.json5
|
|
146
168
|
```
|
|
147
169
|
|
|
148
170
|
---
|
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
`jsonpit` stores JsonPits—JSON files with an append-only value history—across machines and servers coordinated over synchronized Cloud Drives (`OneDrive`, `Dropbox`, `GoogleDrive`, `ICloudDrive`) without requiring a centralized database daemon.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The canonical **reference (lead) implementation** is C# `JsonPit` and the `pits` CLI in [RAIkeep](https://github.com/Burkhardt/RAIkeep). `jsonpit` is the pure-Python implementation engineered for 100% behavioral and physical storage parity with zero deviation.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Cross-Tool & Cross-Language Version Alignment (`v4.4.2`)
|
|
13
|
+
|
|
14
|
+
`jsonpit` and the `jpit` CLI are version-synchronized to **`v4.4.2`** in direct lockstep with C# `JsonPit` and `pits 4.4.2` in [RAIkeep](https://github.com/Burkhardt/RAIkeep).
|
|
15
|
+
|
|
16
|
+
### Why Harmonized Versioning is Essential:
|
|
17
|
+
- **Instant Compatibility Clarity:** Operators, DevOps pipelines, and autonomous AI agents immediately know whether `jpit` and `pits` share the exact same distributed protocol revision. If both tools report `4.4.x`, they guarantee identical master leasing rules, receipt mechanisms, 7-digit UTC timestamp parsing, and CR041 clean change file conventions.
|
|
18
|
+
- **Elimination of Multi-Language Matrix Confusion:** In heterogeneous environments (e.g. C# backend daemons paired with Python agent orchestrators and data science tooling), unified SemVer tags eliminate complex cross-compatibility lookup tables.
|
|
19
|
+
- **Joint Protocol Governance:** Architectural changes (such as CR040 anti-Read-Modify-Write rules and CR041 clean change file formatting) are certified across both languages under the exact same version milestone before release.
|
|
9
20
|
|
|
10
21
|
---
|
|
11
22
|
|
|
@@ -115,12 +126,23 @@ Person/
|
|
|
115
126
|
|
|
116
127
|
## Development & Testing
|
|
117
128
|
|
|
129
|
+
### Cross-Library Differential Parity Battery
|
|
130
|
+
|
|
131
|
+
To ensure 100% interoperability with C# `JsonPit`, this repository includes a dedicated **Differential Cross-Testing Suite** (`tests/suites/*.json5` & `tests/sequence_runner.py`). The test harness orchestrates bidirectional interaction between the Python runtime (`jpit`) and the C# CLI binary (`pits`):
|
|
132
|
+
|
|
133
|
+
- **Python Writes $\rightarrow$ C# Validates:** Python writes `.pit` snapshots and sparse changes; C# `pits export` and `pits verify` assert physical byte formatting, timestamp precision, and schema invariants.
|
|
134
|
+
- **C# Writes $\rightarrow$ Python Validates:** C# `pits seed`, `delete-property`, and `maintain` mutate state; Python `jpit get`, `history`, and `PitStore` verify projections and receipt mechanics.
|
|
135
|
+
- **7 Parity Suites (41 Cross-Engine Steps):** Covers persistence CRUD, CR041 clean change files, receipt grace periods, nested property tombstones, compaction/events archiving, process flag lifecycles, and split-master partition recovery.
|
|
136
|
+
|
|
137
|
+
For full suite descriptions, architecture diagrams, and certification tables, see:
|
|
138
|
+
👉 **[`doc/CROSS_ENGINE_PARITY_SPECIFICATION.md`](doc/CROSS_ENGINE_PARITY_SPECIFICATION.md)**
|
|
139
|
+
|
|
118
140
|
```bash
|
|
119
|
-
# Run
|
|
120
|
-
|
|
141
|
+
# Run the full test battery (Unit Tests + 7 Cross-Engine Parity Suites):
|
|
142
|
+
python3 tests/run_all.py
|
|
121
143
|
|
|
122
|
-
#
|
|
123
|
-
|
|
144
|
+
# Run a specific declarative parity suite directly:
|
|
145
|
+
python3 -m tests.sequence_runner tests/suites/01_persistence_and_crud.json5
|
|
124
146
|
```
|
|
125
147
|
|
|
126
148
|
---
|
|
@@ -9,26 +9,32 @@ from .exceptions import (
|
|
|
9
9
|
PitCorruptError,
|
|
10
10
|
PitInstanceConflictError,
|
|
11
11
|
PitNotFoundError,
|
|
12
|
+
ProtectedAttributeError,
|
|
12
13
|
TombstoneError,
|
|
13
14
|
)
|
|
14
15
|
from .fs import OsConfig
|
|
15
16
|
from .history import PitItems
|
|
17
|
+
from .icons import Icons
|
|
16
18
|
from .item import PitItem
|
|
19
|
+
from .store import Pit
|
|
20
|
+
|
|
17
21
|
try:
|
|
18
22
|
import importlib.metadata
|
|
19
23
|
__version__ = importlib.metadata.version("jsonpit")
|
|
20
24
|
except Exception:
|
|
21
|
-
__version__ = "
|
|
25
|
+
__version__ = "4.4.2"
|
|
22
26
|
|
|
23
27
|
__all__ = [
|
|
24
28
|
"Pit",
|
|
25
29
|
"PitItem",
|
|
26
30
|
"PitItems",
|
|
27
31
|
"OsConfig",
|
|
32
|
+
"Icons",
|
|
28
33
|
"JsonPitError",
|
|
29
34
|
"PitNotFoundError",
|
|
30
35
|
"PitCorruptError",
|
|
31
36
|
"PitConcurrencyError",
|
|
32
37
|
"PitInstanceConflictError",
|
|
38
|
+
"ProtectedAttributeError",
|
|
33
39
|
"TombstoneError",
|
|
34
40
|
]
|
|
@@ -45,16 +45,16 @@ def utc_ticks_to_datetime(ticks: int) -> datetime.datetime:
|
|
|
45
45
|
|
|
46
46
|
def format_iso_timestamp(dt: datetime.datetime) -> str:
|
|
47
47
|
"""
|
|
48
|
-
Formats a UTC datetime in
|
|
49
|
-
'yyyy-MM-ddTHH:mm:ss.
|
|
48
|
+
Formats a UTC datetime in exact .NET 'o' round-trip ISO-8601 format:
|
|
49
|
+
'yyyy-MM-ddTHH:mm:ss.fffffffZ' (7 fractional digits).
|
|
50
|
+
Guarantees 100% parity with .NET DateTimeOffset.ParseExact(..., "o", ...).
|
|
50
51
|
"""
|
|
51
52
|
if dt.tzinfo is None:
|
|
52
53
|
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
|
53
54
|
else:
|
|
54
55
|
dt = dt.astimezone(datetime.timezone.utc)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
56
|
+
base = dt.strftime("%Y-%m-%dT%H:%M:%S")
|
|
57
|
+
return f"{base}.{dt.microsecond:06d}0Z"
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def parse_iso_timestamp(ts: str) -> datetime.datetime:
|
|
@@ -42,29 +42,44 @@ class ChangeFile:
|
|
|
42
42
|
def compose_name(
|
|
43
43
|
modified: datetime.datetime,
|
|
44
44
|
exact_process_identity: str,
|
|
45
|
-
sha256: str,
|
|
45
|
+
sha256: str | None = None,
|
|
46
46
|
) -> str:
|
|
47
|
-
"""
|
|
47
|
+
"""
|
|
48
|
+
Composes the change-file stem.
|
|
49
|
+
CR041 clean format: {UtcTicks}_{ExactProcessIdentity}.
|
|
50
|
+
CR003 legacy format: {UtcTicks}_{ExactProcessIdentity}_{Sha256}.
|
|
51
|
+
"""
|
|
48
52
|
ticks = datetime_to_utc_ticks(modified)
|
|
49
|
-
|
|
53
|
+
if sha256:
|
|
54
|
+
return f"{ticks}_{exact_process_identity}_{sha256}"
|
|
55
|
+
return f"{ticks}_{exact_process_identity}"
|
|
50
56
|
|
|
51
57
|
@classmethod
|
|
52
|
-
def compose_name_for(
|
|
58
|
+
def compose_name_for(
|
|
59
|
+
cls,
|
|
60
|
+
fragment: PitItem,
|
|
61
|
+
exact_process_identity: str,
|
|
62
|
+
include_sha: bool = False,
|
|
63
|
+
) -> str:
|
|
53
64
|
"""Composes the change-file stem directly from a PitItem fragment."""
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
if include_sha:
|
|
66
|
+
_, sha = cls.canonical_payload_for(fragment)
|
|
67
|
+
return cls.compose_name(fragment.modified, exact_process_identity, sha)
|
|
68
|
+
return cls.compose_name(fragment.modified, exact_process_identity)
|
|
56
69
|
|
|
57
70
|
@staticmethod
|
|
58
|
-
def try_parse_name(name_without_extension: str) -> tuple[int, str, str] | None:
|
|
71
|
+
def try_parse_name(name_without_extension: str) -> tuple[int, str, str | None] | None:
|
|
59
72
|
"""
|
|
60
73
|
Parses a change-file name without extension.
|
|
61
|
-
|
|
74
|
+
Supports both CR041 clean format ({ticks}_{identity}) and
|
|
75
|
+
CR003 legacy format ({ticks}_{identity}_{sha256}).
|
|
76
|
+
Returns (utc_ticks, exact_process_identity, sha256_or_none) or None if invalid.
|
|
62
77
|
"""
|
|
63
78
|
if not name_without_extension:
|
|
64
79
|
return None
|
|
80
|
+
|
|
65
81
|
first_sep = name_without_extension.find("_")
|
|
66
|
-
|
|
67
|
-
if first_sep <= 0 or last_sep <= first_sep:
|
|
82
|
+
if first_sep <= 0 or first_sep == len(name_without_extension) - 1:
|
|
68
83
|
return None
|
|
69
84
|
|
|
70
85
|
ticks_str = name_without_extension[:first_sep]
|
|
@@ -73,15 +88,22 @@ class ChangeFile:
|
|
|
73
88
|
except ValueError:
|
|
74
89
|
return None
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return None
|
|
91
|
+
remainder = name_without_extension[first_sep + 1 :]
|
|
92
|
+
last_sep = remainder.rfind("_")
|
|
79
93
|
|
|
80
|
-
|
|
81
|
-
if
|
|
82
|
-
|
|
94
|
+
# Check if remainder has a trailing 64-char hex SHA (CR003 legacy format)
|
|
95
|
+
if last_sep > 0:
|
|
96
|
+
sha_candidate = remainder[last_sep + 1 :]
|
|
97
|
+
if len(sha_candidate) == 64 and all(c in "0123456789abcdef" for c in sha_candidate.lower()):
|
|
98
|
+
identity = remainder[:last_sep]
|
|
99
|
+
if identity:
|
|
100
|
+
return ticks, identity, sha_candidate.lower()
|
|
101
|
+
|
|
102
|
+
# CR041 clean format: {ticks}_{identity}
|
|
103
|
+
if remainder:
|
|
104
|
+
return ticks, remainder, None
|
|
83
105
|
|
|
84
|
-
return
|
|
106
|
+
return None
|
|
85
107
|
|
|
86
108
|
@classmethod
|
|
87
109
|
def identity_of(cls, name_without_extension: str) -> str | None:
|
|
@@ -89,21 +111,14 @@ class ChangeFile:
|
|
|
89
111
|
parsed = cls.try_parse_name(name_without_extension)
|
|
90
112
|
if parsed:
|
|
91
113
|
return parsed[1]
|
|
92
|
-
# Legacy format fallback: {ticks}_{identity}
|
|
93
|
-
sep = name_without_extension.find("_")
|
|
94
|
-
if sep > 0 and sep < len(name_without_extension) - 1:
|
|
95
|
-
try:
|
|
96
|
-
int(name_without_extension[:sep])
|
|
97
|
-
return name_without_extension[sep + 1 :]
|
|
98
|
-
except ValueError:
|
|
99
|
-
pass
|
|
100
114
|
return None
|
|
101
115
|
|
|
102
116
|
@classmethod
|
|
103
117
|
def read_validated(cls, file_path: Path) -> list[list[dict[str, Any]]] | None:
|
|
104
118
|
"""
|
|
105
119
|
Reads and validates a change file.
|
|
106
|
-
|
|
120
|
+
For legacy files with embedded SHA-256, verifies content matches hash.
|
|
121
|
+
For clean CR041 files, verifies valid JSON structure.
|
|
107
122
|
Returns parsed JSON payload or None if invalid or corrupt.
|
|
108
123
|
"""
|
|
109
124
|
content = safe_read_text(file_path)
|
|
@@ -113,9 +128,10 @@ class ChangeFile:
|
|
|
113
128
|
parsed_meta = cls.try_parse_name(file_path.stem)
|
|
114
129
|
if parsed_meta:
|
|
115
130
|
_, _, expected_sha = parsed_meta
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
if expected_sha is not None:
|
|
132
|
+
actual_sha = sha256_hex(content)
|
|
133
|
+
if actual_sha != expected_sha:
|
|
134
|
+
return None
|
|
119
135
|
|
|
120
136
|
try:
|
|
121
137
|
data = json.loads(content)
|
|
@@ -133,14 +149,17 @@ class ChangeFile:
|
|
|
133
149
|
exact_process_identity: str,
|
|
134
150
|
) -> Path:
|
|
135
151
|
"""
|
|
136
|
-
Writes a fragment as an ordinary collision-safe change file in pit_dir.
|
|
137
|
-
Filename: {Modified.UtcTicks}_{ExactProcessIdentity}
|
|
152
|
+
Writes a fragment as an ordinary collision-safe clean change file in pit_dir (CR041).
|
|
153
|
+
Filename: {Modified.UtcTicks}_{ExactProcessIdentity}.json.
|
|
138
154
|
Exact byte contract: canonical UTF-8 JSON without trailing newline.
|
|
139
155
|
"""
|
|
140
|
-
payload,
|
|
141
|
-
stem = cls.compose_name(fragment.modified, exact_process_identity
|
|
156
|
+
payload, _ = cls.canonical_payload_for(fragment)
|
|
157
|
+
stem = cls.compose_name(fragment.modified, exact_process_identity)
|
|
142
158
|
target_path = pit_dir / f"{stem}.json"
|
|
143
159
|
|
|
160
|
+
if not target_path.is_file():
|
|
161
|
+
safe_write_in_place(target_path, payload)
|
|
162
|
+
|
|
144
163
|
if not target_path.is_file():
|
|
145
164
|
safe_write_in_place(target_path, payload)
|
|
146
165
|
|