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.
Files changed (32) hide show
  1. {jsonpit-0.1.4 → jsonpit-4.4.2}/PKG-INFO +28 -6
  2. {jsonpit-0.1.4 → jsonpit-4.4.2}/README.md +27 -5
  3. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/__init__.py +7 -1
  4. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/canonical.py +5 -5
  5. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/changes.py +52 -33
  6. jsonpit-4.4.2/jsonpit/cli.py +1220 -0
  7. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/exceptions.py +4 -0
  8. jsonpit-4.4.2/jsonpit/icons.py +63 -0
  9. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/item.py +16 -3
  10. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/store.py +47 -9
  11. {jsonpit-0.1.4 → jsonpit-4.4.2}/pyproject.toml +8 -1
  12. jsonpit-0.1.4/ADELE_SPIRIT.md +0 -97
  13. jsonpit-0.1.4/GEMINI.md +0 -42
  14. jsonpit-0.1.4/jsonpit/cli.py +0 -594
  15. jsonpit-0.1.4/packages/jpit/README.md +0 -34
  16. jsonpit-0.1.4/packages/jpit/jpit/__init__.py +0 -5
  17. jsonpit-0.1.4/packages/jpit/pyproject.toml +0 -29
  18. jsonpit-0.1.4/tests/fixtures/Person.pit +0 -1
  19. jsonpit-0.1.4/tests/run_all.py +0 -73
  20. jsonpit-0.1.4/tests/test_canonical.py +0 -58
  21. jsonpit-0.1.4/tests/test_change_file.py +0 -69
  22. jsonpit-0.1.4/tests/test_cli.py +0 -164
  23. jsonpit-0.1.4/tests/test_csharp_compatibility.py +0 -51
  24. jsonpit-0.1.4/tests/test_delete_property_projection.py +0 -201
  25. jsonpit-0.1.4/tests/test_equal_timestamp_ordering.py +0 -100
  26. jsonpit-0.1.4/tests/test_master_ticket.py +0 -83
  27. jsonpit-0.1.4/tests/test_pit_item.py +0 -91
  28. {jsonpit-0.1.4 → jsonpit-4.4.2}/.gitignore +0 -0
  29. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/config.py +0 -0
  30. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/flags.py +0 -0
  31. {jsonpit-0.1.4 → jsonpit-4.4.2}/jsonpit/fs.py +0 -0
  32. {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: 0.1.4
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
- It is the authoritative Python implementation of the JsonPit protocol, designed to operate in full, zero-deviation parity alongside the C# `JsonPit` engine and the `pits` CLI toolchain in [RAIkeep](https://github.com/Burkhardt/RAIkeep).
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 pytest test suite:
142
- pytest
163
+ # Run the full test battery (Unit Tests + 7 Cross-Engine Parity Suites):
164
+ python3 tests/run_all.py
143
165
 
144
- # Type checking (strict):
145
- mypy jsonpit
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
- It is the authoritative Python implementation of the JsonPit protocol, designed to operate in full, zero-deviation parity alongside the C# `JsonPit` engine and the `pits` CLI toolchain in [RAIkeep](https://github.com/Burkhardt/RAIkeep).
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 pytest test suite:
120
- pytest
141
+ # Run the full test battery (Unit Tests + 7 Cross-Engine Parity Suites):
142
+ python3 tests/run_all.py
121
143
 
122
- # Type checking (strict):
123
- mypy jsonpit
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__ = "0.1.4"
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 standard round-trip ISO-8601 format:
49
- 'yyyy-MM-ddTHH:mm:ss.ffffffZ' (or 'yyyy-MM-ddTHH:mm:ssZ' if zero microseconds).
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
- if dt.microsecond == 0:
56
- return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
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
- """Composes the change-file stem: {UtcTicks}_{ExactProcessIdentity}_{Sha256}."""
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
- return f"{ticks}_{exact_process_identity}_{sha256}"
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(cls, fragment: PitItem, exact_process_identity: str) -> str:
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
- _, sha = cls.canonical_payload_for(fragment)
55
- return cls.compose_name(fragment.modified, exact_process_identity, sha)
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
- Returns (utc_ticks, exact_process_identity, sha256) or None if invalid.
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
- last_sep = name_without_extension.rfind("_")
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
- sha = name_without_extension[last_sep + 1 :]
77
- if len(sha) != 64 or not all(c in "0123456789abcdef" for c in sha.lower()):
78
- return None
91
+ remainder = name_without_extension[first_sep + 1 :]
92
+ last_sep = remainder.rfind("_")
79
93
 
80
- identity = name_without_extension[first_sep + 1 : last_sep]
81
- if not identity:
82
- return None
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 ticks, identity, sha.lower()
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
- Verifies the file content matches the embedded SHA-256 hash.
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
- actual_sha = sha256_hex(content)
117
- if actual_sha != expected_sha:
118
- return None
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}_{Sha256}.json.
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, sha = cls.canonical_payload_for(fragment)
141
- stem = cls.compose_name(fragment.modified, exact_process_identity, sha)
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