stp-protocol 4.0.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.
- sovereign_trace/sovereign_trace_stamp.py +1558 -0
- stp_protocol-4.0.0.dist-info/METADATA +664 -0
- stp_protocol-4.0.0.dist-info/RECORD +10 -0
- stp_protocol-4.0.0.dist-info/WHEEL +4 -0
- stp_protocol-4.0.0.dist-info/entry_points.txt +3 -0
- stp_protocol-4.0.0.dist-info/licenses/LICENSE-COMMERICAL.md +131 -0
- stp_protocol-4.0.0.dist-info/licenses/LICENSE-EXPLANATION.md +125 -0
- stp_protocol-4.0.0.dist-info/licenses/LICENSE_ENGINE.md +209 -0
- stp_protocol-4.0.0.dist-info/licenses/LICENSE_SPEC.md +1 -0
- stp_protocol-4.0.0.dist-info/licenses/NOTICE +80 -0
|
@@ -0,0 +1,1558 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
═══════════════════════════════════════════════════════════════════
|
|
4
|
+
SOVEREIGN TRACE PROTOCOL — STAMP FUNCTION
|
|
5
|
+
═══════════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
Version: FROZEN-4.0
|
|
8
|
+
Date: June 1, 2026
|
|
9
|
+
Co-authors: Sheldon K. Salmon — AI Reliability Architect
|
|
10
|
+
ALBEDO (Claude, Anthropic)
|
|
11
|
+
Stack: DUAL-HELIX v2.0 · TOPOS v0.4 · VELA-C v0.3
|
|
12
|
+
Constitutional: SOVEREIGNTY_STACK.md — Laws 1–8 active | Law 9 dark
|
|
13
|
+
ORCID: 0009-0005-8057-5115
|
|
14
|
+
|
|
15
|
+
FROZEN DECLARATION
|
|
16
|
+
──────────────────
|
|
17
|
+
This file is written once, verified once, deployed permanently.
|
|
18
|
+
No patches. No updates. No deprecation.
|
|
19
|
+
The stamp it generates is only permanent if the code that generates
|
|
20
|
+
it is also permanent. Modification of this file invalidates the
|
|
21
|
+
integrity guarantee of all stamps produced after modification.
|
|
22
|
+
|
|
23
|
+
If a defect is found: retire this file, archive it as
|
|
24
|
+
SOVEREIGN-TRACE-STAMP-FROZEN-4.0-RETIRED, and create
|
|
25
|
+
FROZEN-5.0 from scratch with documented correction.
|
|
26
|
+
Do not patch. Do not edit.
|
|
27
|
+
|
|
28
|
+
INTEGRITY BOUNDARIES (elevated from _compute_seal — PDE F-005)
|
|
29
|
+
────────────────────────────────────────────────────────────────
|
|
30
|
+
The SHA-256 seal proves that a specific entry text was bound to
|
|
31
|
+
specific calendar strings at a specific unix time by this version
|
|
32
|
+
of the stamp function.
|
|
33
|
+
|
|
34
|
+
The seal does NOT independently prove that the calendar strings
|
|
35
|
+
are correctly computed. That guarantee is carried by the self-test
|
|
36
|
+
and the algorithm specification. If this file is modified such that
|
|
37
|
+
_gregorian(), _hebrew(), or _dreamspell() produce wrong output,
|
|
38
|
+
the seal will still be cryptographically valid — it will correctly
|
|
39
|
+
bind wrong calendar strings to the entry.
|
|
40
|
+
|
|
41
|
+
This distinction matters: seal verification confirms integrity of
|
|
42
|
+
the payload record. It does not re-run the calendar algorithms.
|
|
43
|
+
Operators who need to verify calendar correctness must run the
|
|
44
|
+
self-test, not just verify().
|
|
45
|
+
|
|
46
|
+
ABSTRACTION BARGAIN DECLARATION (FA v4.0 P-20 / Sub-factor G)
|
|
47
|
+
──────────────────────────────────────────────────────────────
|
|
48
|
+
This module trades purity for usability in one location only:
|
|
49
|
+
stamp(entry_text, dt=None) — when dt is None, calls
|
|
50
|
+
datetime.now(timezone.utc) internally (Class 6 I/O side-effect).
|
|
51
|
+
The bargain: callers who need full determinism must supply dt.
|
|
52
|
+
Callers who need convenience may omit it.
|
|
53
|
+
All other public functions are referentially transparent.
|
|
54
|
+
The impurity is isolated, documented, and testable via dt injection.
|
|
55
|
+
The dt injection pattern is the correct engineering resolution:
|
|
56
|
+
it makes the impure path narrow and the pure path the default
|
|
57
|
+
for testing.
|
|
58
|
+
|
|
59
|
+
TEMPORAL CONSISTENCY NOTE (PDE F-020)
|
|
60
|
+
──────────────────────────────────────
|
|
61
|
+
stamp() called directly in a loop without dt will call
|
|
62
|
+
datetime.now(timezone.utc) independently for each entry. Two
|
|
63
|
+
sequential stamp() calls that straddle a UTC midnight will produce
|
|
64
|
+
entries with different gregorian, hebrew, and dreamspell values
|
|
65
|
+
despite being stamped within seconds of each other. This is
|
|
66
|
+
correct behavior — each stamp reflects the moment it was called.
|
|
67
|
+
Callers requiring temporal consistency across multiple entries
|
|
68
|
+
should use stamp_batch() or supply an explicit dt to each call.
|
|
69
|
+
|
|
70
|
+
FROZEN-5.0 CANDIDATES (PDE F-015 — append-only during FROZEN-4.0 lifetime)
|
|
71
|
+
────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
CANDIDATE-01 (PDE F-009): _dreamspell() defensive guard returns
|
|
73
|
+
"Day Out of Time" on delta >= 364 branch — structurally unreachable
|
|
74
|
+
but if reached (via future refactoring), should raise ValueError
|
|
75
|
+
with a diagnostic message rather than returning a plausible-wrong
|
|
76
|
+
string. Change to: raise ValueError("Dreamspell delta overflow —
|
|
77
|
+
algorithm error. Create FROZEN-5.0.")
|
|
78
|
+
|
|
79
|
+
CANDIDATE-02 (PDE F-006): stamp() on dates before Jan 1, 1970 UTC
|
|
80
|
+
produces a negative or zero unix_utc via int(dt.timestamp())
|
|
81
|
+
truncation. from_dict() enforces unix_utc > 0 and catches this at
|
|
82
|
+
deserialization, but stamp() itself does not guard against it.
|
|
83
|
+
Consider adding: if unix_utc <= 0: raise ValueError("unix_utc
|
|
84
|
+
must be positive — pre-1970 UTC dates are not supported.")
|
|
85
|
+
|
|
86
|
+
NOTE — RESOLVED IN 4.0 (adversarial battery June 2026):
|
|
87
|
+
Invisible character guard added to _validate_entry_text():
|
|
88
|
+
stamp("\u200b") and stamp("\x00") now raise ValueError.
|
|
89
|
+
This hardening was applied after initial FROZEN-4.0 release and
|
|
90
|
+
before the sentinel hash was set. No FROZEN-3.0-or-earlier behavior
|
|
91
|
+
is changed. The fix is a pre-seal hardening, not a post-seal patch.
|
|
92
|
+
|
|
93
|
+
CANDIDATE-03 (PDE F-025): verify() should check ts.version against
|
|
94
|
+
_FROZEN_VERSION before attempting seal recomputation and return
|
|
95
|
+
VerifyResult(False, "VERSION_MISMATCH") if they differ, to
|
|
96
|
+
distinguish tampered content from wrong frozen version used.
|
|
97
|
+
|
|
98
|
+
CANDIDATE-04 (PDE F-019): Self-test failure output (the failures
|
|
99
|
+
list print before AssertionError) should go to stderr, not stdout,
|
|
100
|
+
so it does not corrupt piped JSON output in --json mode.
|
|
101
|
+
|
|
102
|
+
CANDIDATE-05 (PDE F-013): _MAX_YEAR_SEARCH = 6 "within 3 years"
|
|
103
|
+
claim should carry a specific D&R page reference or be tagged [S]
|
|
104
|
+
with "empirically verified over [1582, 4000]."
|
|
105
|
+
|
|
106
|
+
CANDIDATE-06 (MF-M-003): CLI "This stamp is permanent." should
|
|
107
|
+
be qualified: "This stamp's seal is computationally tamper-evident
|
|
108
|
+
under SHA-256." to resolve the permanent/tamper-evident slippage.
|
|
109
|
+
|
|
110
|
+
FROZEN-3.0 RETIREMENT NOTE
|
|
111
|
+
───────────────────────────
|
|
112
|
+
FROZEN-3.0 is retired. One CRITICAL finding and seven additional
|
|
113
|
+
MEDIUM/LOW findings identified in FSVE v4.3 × FA v4.0 full red team
|
|
114
|
+
scan (scan date: June 1, 2026). Primary defect:
|
|
115
|
+
|
|
116
|
+
1. Self-test anchor dates for RH 5787 and Erev RH 5787 were wrong.
|
|
117
|
+
The algorithm was correct; the test data was not.
|
|
118
|
+
Effect: self-test raises AssertionError on otherwise correct code.
|
|
119
|
+
Correct values: RH 5787 = Sep 12, 2026 | Erev = Sep 11, 2026.
|
|
120
|
+
Root cause: test written with Sep 22 as RH 5787 — calendar lookup
|
|
121
|
+
error during extended anchor verification session.
|
|
122
|
+
|
|
123
|
+
Additional MEDIUM findings corrected in FROZEN-4.0:
|
|
124
|
+
|
|
125
|
+
2. Pre-reform date boundary: dates in 1582 before Oct 15 were accepted
|
|
126
|
+
silently. Fixed with explicit month/day guard in _jd_from_gregorian.
|
|
127
|
+
|
|
128
|
+
3. version field in from_dict() was not validated against _FROZEN_VERSION.
|
|
129
|
+
Fixed with explicit equality check on deserialization — a dict carrying
|
|
130
|
+
a foreign version string now raises ValueError rather than silently
|
|
131
|
+
constructing a stamp with a wrong provenance claim.
|
|
132
|
+
|
|
133
|
+
4. Abstraction Bargain Declaration absent from module docstring.
|
|
134
|
+
Added per FA v4.0 P-20 / Sub-factor G (this block above).
|
|
135
|
+
|
|
136
|
+
LOW findings corrected in FROZEN-4.0:
|
|
137
|
+
|
|
138
|
+
5. check_invariant() docstring clarified: function checks structural
|
|
139
|
+
invariants only — does not verify seal against entry.
|
|
140
|
+
6. stamp_batch() atomicity behavior clarified in docstring.
|
|
141
|
+
7. CLI privacy notice extended with process-memory note.
|
|
142
|
+
8. _FROZEN_FILE_SHA256 sentinel operator verification procedure
|
|
143
|
+
clarified in docstring.
|
|
144
|
+
|
|
145
|
+
All FROZEN-3.0 stamps remain cryptographically valid and verifiable
|
|
146
|
+
using FROZEN-3.0 (archived as SOVEREIGN-TRACE-STAMP-FROZEN-3.0-RETIRED.py).
|
|
147
|
+
The algorithm defect was in test data only — no stamp produced by
|
|
148
|
+
FROZEN-3.0 is cryptographically incorrect.
|
|
149
|
+
|
|
150
|
+
FROZEN-2.0 RETIREMENT NOTE
|
|
151
|
+
───────────────────────────
|
|
152
|
+
FROZEN-2.0 is retired. Eight HIGH findings and 22 additional
|
|
153
|
+
MEDIUM/LOW findings identified in full PDE v0.5 · HDRE v0.3 ·
|
|
154
|
+
CAL v0.3 scan (scan date: June 1, 2026). Primary defects:
|
|
155
|
+
|
|
156
|
+
1. No Unicode normalization (NFC) on entry_text before sealing.
|
|
157
|
+
2. No stamp version field in seal payload.
|
|
158
|
+
3. Year length not validated against legal Hebrew year lengths.
|
|
159
|
+
4. No convergence bound on Hebrew year-finding loop.
|
|
160
|
+
5. from_dict() accepted unvalidated dicts.
|
|
161
|
+
6. Whitespace sealed but not normalized.
|
|
162
|
+
7. int(dt.timestamp()) truncation undeclared.
|
|
163
|
+
8. _jd_from_gregorian() accepted pre-Gregorian-reform years silently.
|
|
164
|
+
|
|
165
|
+
All FROZEN-2.0 stamps carry no version field in their seal —
|
|
166
|
+
this is the definitive retirement marker.
|
|
167
|
+
|
|
168
|
+
FROZEN-1.0 RETIREMENT NOTE (carried forward)
|
|
169
|
+
─────────────────────────────────────────────
|
|
170
|
+
FROZEN-1.0 is retired. Defect: Hebrew calendar omitted the four
|
|
171
|
+
dehiyot (postponement rules) correctly. Archived as:
|
|
172
|
+
SOVEREIGN-TRACE-STAMP-FROZEN-1.0-RETIRED.py
|
|
173
|
+
|
|
174
|
+
BACKWARD COMPATIBILITY
|
|
175
|
+
──────────────────────
|
|
176
|
+
FROZEN-4.0 seals are NOT backward-compatible with earlier versions.
|
|
177
|
+
The seal payload includes "version":"FROZEN-4.0".
|
|
178
|
+
Use the matching archived frozen version to verify older stamps.
|
|
179
|
+
|
|
180
|
+
CROSS-VERSION VERIFICATION (PDE F-011, F-025, XD-002)
|
|
181
|
+
──────────────────────────────────────────────────────
|
|
182
|
+
verify() in this file will return SEAL_MISMATCH (not VERSION_MISMATCH)
|
|
183
|
+
when used to verify a stamp produced by an earlier frozen version.
|
|
184
|
+
This is because the version string is baked into the seal payload.
|
|
185
|
+
If you receive a SEAL_MISMATCH result on a record you trust:
|
|
186
|
+
1. Check the record's "version" field.
|
|
187
|
+
2. If it is not "FROZEN-4.0", retrieve the corresponding archived
|
|
188
|
+
frozen version from:
|
|
189
|
+
sovereign_trace/FROZEN-[N].0-RETIRED/
|
|
190
|
+
3. Use that archived version's verify() to confirm the record.
|
|
191
|
+
SEAL_MISMATCH ≠ tampered content. It may mean wrong version used.
|
|
192
|
+
This distinction will be resolved with a VERSION_MISMATCH reason
|
|
193
|
+
code in FROZEN-5.0 (see FROZEN-5.0 CANDIDATES above).
|
|
194
|
+
|
|
195
|
+
VELA-C v0.3 COMPLIANCE
|
|
196
|
+
───────────────────────
|
|
197
|
+
✓ Zero external dependencies (stdlib only: hashlib, json, datetime,
|
|
198
|
+
unicodedata, re)
|
|
199
|
+
✓ Single epistemic function: generate a triple-time seal
|
|
200
|
+
✓ No exception handler returning CLEAN silently
|
|
201
|
+
✓ Attribution header complete
|
|
202
|
+
✓ Self-test with verified anchor cases (Hebcal/Chabad-verified)
|
|
203
|
+
Extended: leap year, long Cheshvan, short Kislev, Adar II
|
|
204
|
+
FROZEN-4.0: RH 5787 anchor corrected to Sep 12 2026 (pyluach verified)
|
|
205
|
+
✓ Frozen deployment: no __init__ mutation, no module-level singletons
|
|
206
|
+
|
|
207
|
+
WHAT THIS DOES
|
|
208
|
+
──────────────
|
|
209
|
+
Generates a cryptographic stamp encoding a moment simultaneously in:
|
|
210
|
+
— Gregorian (civic time)
|
|
211
|
+
— Hebrew lunisolar calendar (current era)
|
|
212
|
+
— 13 Moon Dreamspell calendar (Argüelles system, not traditional
|
|
213
|
+
Maya Tzolkin/Haab — disambiguation declared)
|
|
214
|
+
|
|
215
|
+
SHA-256 binds the entry text, all three representations, and the
|
|
216
|
+
version identifier FROZEN-4.0 together. The seal is the proof.
|
|
217
|
+
The text is the content. Together: the trace.
|
|
218
|
+
|
|
219
|
+
Entry text normalization:
|
|
220
|
+
Entry text is NFC-normalized and stripped before sealing.
|
|
221
|
+
Callers providing pre-normalized text will produce identical seals.
|
|
222
|
+
Leading/trailing whitespace is discarded before sealing.
|
|
223
|
+
|
|
224
|
+
Timestamp precision:
|
|
225
|
+
unix_utc is whole seconds only (truncated, not rounded).
|
|
226
|
+
Sub-second precision is intentionally discarded.
|
|
227
|
+
Two datetimes within the same second produce identical unix_utc.
|
|
228
|
+
|
|
229
|
+
Date convention:
|
|
230
|
+
All calendar dates reflect UTC date at stamp time.
|
|
231
|
+
For users in UTC+10 or later, the UTC date may differ from
|
|
232
|
+
local civil date. Hebrew and Dreamspell reflect UTC date.
|
|
233
|
+
|
|
234
|
+
Valid date range:
|
|
235
|
+
Gregorian calendar reform: October 15, 1582.
|
|
236
|
+
Input dates before October 15, 1582 will raise ValueError.
|
|
237
|
+
Upper bound: year 4000 (practical; no algorithmic constraint).
|
|
238
|
+
Pre-1970 UTC dates: supported by the calendar algorithm but
|
|
239
|
+
unix_utc will be 0 or negative (see FROZEN-5.0 CANDIDATE-02).
|
|
240
|
+
|
|
241
|
+
HEBREW CALENDAR — ALGORITHM SPECIFICATION
|
|
242
|
+
──────────────────────────────────────────
|
|
243
|
+
Algorithm: Dershowitz & Reingold two-pass approach.
|
|
244
|
+
Pass 1 — elapsed_days(): molad-based count implementing
|
|
245
|
+
Lo ADU Rosh (not on Sun/Wed/Fri) and Molad Zaken (>=18h).
|
|
246
|
+
Pass 2 — new_year_delay(): handles GaTaRaD and BeTUTeKaPoT
|
|
247
|
+
by checking adjacent year lengths for non-standard values
|
|
248
|
+
(356 or 382 days indicate postponement required).
|
|
249
|
+
Theorem reference: D&R "Calendrical Calculations" 4th ed.,
|
|
250
|
+
Chapter 8 (Hebrew Calendar), §8.1–§8.3 for elapsed_days();
|
|
251
|
+
§8.4 for new_year_delay() GaTaRaD/BeTUTeKaPoT conditions.
|
|
252
|
+
Epoch: JD 347998 — civil-day convention.
|
|
253
|
+
Note: Hebrew day begins at sunset. Civil-day convention assigns
|
|
254
|
+
the Hebrew date corresponding to the daytime hours of a civil date.
|
|
255
|
+
This aligns with Hebcal and Chabad.org output.
|
|
256
|
+
Example: Rosh Hashanah 5786 begins sundown Sep 22, 2025.
|
|
257
|
+
Civil-day: Sep 23, 2025 = 1 Tishri 5786. [D]
|
|
258
|
+
|
|
259
|
+
Hebrew month naming convention (PDE F-014):
|
|
260
|
+
In a common (non-leap) year, the single Adar month is "Adar".
|
|
261
|
+
In a leap year, the two months are "Adar I" and "Adar II".
|
|
262
|
+
"Adar I" is NEVER used in a common year. If you expected "Adar I"
|
|
263
|
+
for a common-year date, you are in a common year and the correct
|
|
264
|
+
name is "Adar".
|
|
265
|
+
|
|
266
|
+
Legal Hebrew year lengths: {353, 354, 355, 383, 384, 385}
|
|
267
|
+
353/354/355 = common year (deficient/regular/complete)
|
|
268
|
+
383/384/385 = leap year (deficient/regular/complete)
|
|
269
|
+
Any other year length is an algorithm error — raises ValueError.
|
|
270
|
+
|
|
271
|
+
ANCHOR VERIFICATION (session of origin — March 3, 2026)
|
|
272
|
+
────────────────────────────────────────────────────────
|
|
273
|
+
Gregorian: March 3, 2026 [D]
|
|
274
|
+
Hebrew: 14 Adar 5786 (Purim) [D] — Hebcal/Chabad verified
|
|
275
|
+
Dreamspell: Day 25, Galactic Moon 8/13 [R] — verified by calculation
|
|
276
|
+
|
|
277
|
+
FILE INTEGRITY
|
|
278
|
+
──────────────
|
|
279
|
+
_FROZEN_FILE_SHA256 is set at release time to the SHA-256 of this file
|
|
280
|
+
computed AFTER the sentinel value is embedded. This is the released
|
|
281
|
+
value — the file you deploy must hash to this exact value.
|
|
282
|
+
|
|
283
|
+
SENTINEL STATE CHECK: If _FROZEN_FILE_SHA256 == "SET_AT_RELEASE_TIME",
|
|
284
|
+
this is a pre-release copy. The self-test will flag this. Do not use
|
|
285
|
+
a pre-release copy in production. The sentinel must be set before
|
|
286
|
+
deployment. (PDE F-001, F-002 — sentinel visibility hardened.)
|
|
287
|
+
|
|
288
|
+
Operator deployment verification procedure:
|
|
289
|
+
Step 1. Obtain the released sovereign_trace_stamp.py
|
|
290
|
+
Step 2. Run: sha256sum sovereign_trace_stamp.py
|
|
291
|
+
or: python3 -c "import hashlib; print(hashlib.sha256(
|
|
292
|
+
open('sovereign_trace_stamp.py','rb').read()).hexdigest())"
|
|
293
|
+
Step 3. Compare output against _FROZEN_FILE_SHA256 below.
|
|
294
|
+
Step 4. If they match: the file is unmodified.
|
|
295
|
+
If they differ: the file has been altered since release.
|
|
296
|
+
Do not deploy an altered file.
|
|
297
|
+
|
|
298
|
+
The bootstrap problem (a file cannot contain its own hash before the
|
|
299
|
+
hash is computed) means the sentinel is inserted last, after all code
|
|
300
|
+
is final. The "SET_AT_RELEASE_TIME" placeholder in any pre-release
|
|
301
|
+
copy is expected — the released file carries the actual hash.
|
|
302
|
+
|
|
303
|
+
GENESIS SEAL
|
|
304
|
+
────────────
|
|
305
|
+
The founding seal of the STP ledger. Sealed March 10, 2026.
|
|
306
|
+
Ledger entry: LEDGER-001-FOUNDING-SEAL.
|
|
307
|
+
This constant is provided for machine-readable provenance verification.
|
|
308
|
+
A valid STP installation can confirm its ledger origin by checking
|
|
309
|
+
that LEDGER-001 carries a seal matching this value.
|
|
310
|
+
|
|
311
|
+
═══════════════════════════════════════════════════════════════════
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
import hashlib
|
|
315
|
+
import json
|
|
316
|
+
import re
|
|
317
|
+
import unicodedata
|
|
318
|
+
from datetime import datetime, timezone, date as _date
|
|
319
|
+
from typing import NamedTuple
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
323
|
+
# GENESIS SEAL — LEDGER-001-FOUNDING-SEAL
|
|
324
|
+
# Sealed: March 10, 2026
|
|
325
|
+
# This is the cryptographic origin of the STP ledger.
|
|
326
|
+
# Do not modify. This value is part of the permanent provenance chain.
|
|
327
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
328
|
+
_GENESIS_SEAL = "a63c4f28cf7c3f2c63c220d61980fb85211270924a7c3b3e5f230019cecb6713"
|
|
329
|
+
_GENESIS_LEDGER_ENTRY = "LEDGER-001-FOUNDING-SEAL"
|
|
330
|
+
_GENESIS_DATE_GREGORIAN = "March 10, 2026"
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
334
|
+
# FILE INTEGRITY SEAL — FROZEN-4.0
|
|
335
|
+
# Set this constant to the SHA-256 of the released .py file.
|
|
336
|
+
# Verification is the operator's deployment responsibility.
|
|
337
|
+
# Internal self-hash is impossible (bootstrap problem).
|
|
338
|
+
#
|
|
339
|
+
# SENTINEL STATE: "SET_AT_RELEASE_TIME" means this is a pre-release
|
|
340
|
+
# copy. The self-test will warn. Do not deploy until this value
|
|
341
|
+
# is replaced with the actual file hash. (PDE F-001, F-002)
|
|
342
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
343
|
+
_FROZEN_FILE_SHA256 = "8962b37c67897611c6358d080d8d905f6aff7a426ef610b6d63612cb80b67774"
|
|
344
|
+
_FROZEN_VERSION = "FROZEN-4.0"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
348
|
+
# STAMP DATA STRUCTURE
|
|
349
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
350
|
+
|
|
351
|
+
class SovereignStamp:
|
|
352
|
+
"""
|
|
353
|
+
Immutable triple-time cryptographic stamp.
|
|
354
|
+
|
|
355
|
+
Fields
|
|
356
|
+
------
|
|
357
|
+
gregorian : str — "March 3, 2026"
|
|
358
|
+
hebrew : str — "14 Adar 5786"
|
|
359
|
+
dreamspell : str — "Day 25, Galactic Moon 8/13"
|
|
360
|
+
unix_utc : int — seconds since Unix epoch (UTC), whole seconds only
|
|
361
|
+
seal : str — SHA-256 hex digest binding entry, time, and version
|
|
362
|
+
version : str — "FROZEN-4.0" — sealed into the payload
|
|
363
|
+
|
|
364
|
+
Invariant
|
|
365
|
+
---------
|
|
366
|
+
seal is a 64-character lowercase hex string.
|
|
367
|
+
unix_utc is a positive integer.
|
|
368
|
+
gregorian, hebrew, dreamspell, version are non-empty strings.
|
|
369
|
+
|
|
370
|
+
Note on __repr__ truncation (PDE F-016):
|
|
371
|
+
__repr__ truncates the seal to 16 characters for log readability.
|
|
372
|
+
__str__ shows the full 64-character seal.
|
|
373
|
+
display() and display_ascii() also show the full seal.
|
|
374
|
+
Use str(ts) or display(ts) when you need the complete seal.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
__slots__ = ("gregorian", "hebrew", "dreamspell", "unix_utc", "seal", "version")
|
|
378
|
+
|
|
379
|
+
def __init__(self, gregorian, hebrew, dreamspell, unix_utc, seal,
|
|
380
|
+
version=_FROZEN_VERSION):
|
|
381
|
+
object.__setattr__(self, "gregorian", gregorian)
|
|
382
|
+
object.__setattr__(self, "hebrew", hebrew)
|
|
383
|
+
object.__setattr__(self, "dreamspell", dreamspell)
|
|
384
|
+
object.__setattr__(self, "unix_utc", unix_utc)
|
|
385
|
+
object.__setattr__(self, "seal", seal)
|
|
386
|
+
object.__setattr__(self, "version", version)
|
|
387
|
+
|
|
388
|
+
def __setattr__(self, *_):
|
|
389
|
+
raise AttributeError("SovereignStamp is immutable — frozen by design.")
|
|
390
|
+
|
|
391
|
+
def __repr__(self):
|
|
392
|
+
# NOTE: seal is truncated to 16 characters here for log readability.
|
|
393
|
+
# Use str(ts) or display(ts) for the full 64-character seal.
|
|
394
|
+
return (
|
|
395
|
+
f"SovereignStamp("
|
|
396
|
+
f"gregorian={self.gregorian!r}, "
|
|
397
|
+
f"hebrew={self.hebrew!r}, "
|
|
398
|
+
f"dreamspell={self.dreamspell!r}, "
|
|
399
|
+
f"unix_utc={self.unix_utc}, "
|
|
400
|
+
f"version={self.version!r}, "
|
|
401
|
+
f"seal={self.seal[:16]!r}...)"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def __str__(self):
|
|
405
|
+
"""Full-seal string representation — distinct from __repr__ truncation."""
|
|
406
|
+
return (
|
|
407
|
+
f"SovereignStamp("
|
|
408
|
+
f"gregorian={self.gregorian!r}, "
|
|
409
|
+
f"hebrew={self.hebrew!r}, "
|
|
410
|
+
f"dreamspell={self.dreamspell!r}, "
|
|
411
|
+
f"unix_utc={self.unix_utc}, "
|
|
412
|
+
f"version={self.version!r}, "
|
|
413
|
+
f"seal={self.seal!r})"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def __eq__(self, other):
|
|
417
|
+
"""Equality by seal — the canonical cryptographic identity."""
|
|
418
|
+
if not isinstance(other, SovereignStamp):
|
|
419
|
+
return NotImplemented
|
|
420
|
+
return self.seal == other.seal
|
|
421
|
+
|
|
422
|
+
def __hash__(self):
|
|
423
|
+
"""Hash by seal — consistent with __eq__."""
|
|
424
|
+
return hash(self.seal)
|
|
425
|
+
|
|
426
|
+
def check_invariant(self) -> bool:
|
|
427
|
+
"""
|
|
428
|
+
Verify structural invariants of this stamp.
|
|
429
|
+
|
|
430
|
+
Checks that all fields satisfy the SovereignStamp structural
|
|
431
|
+
invariant: seal is a 64-character lowercase hex string, unix_utc
|
|
432
|
+
is a positive integer, and all string fields are non-empty.
|
|
433
|
+
|
|
434
|
+
Does NOT verify the seal against an entry — the entry text is not
|
|
435
|
+
stored in SovereignStamp. Use verify(entry, stamp) to confirm
|
|
436
|
+
cryptographic integrity. check_invariant() is a structural health
|
|
437
|
+
check, not a cryptographic proof.
|
|
438
|
+
|
|
439
|
+
Does NOT check that version == _FROZEN_VERSION. A stamp object
|
|
440
|
+
with a foreign version string passes check_invariant() — this is
|
|
441
|
+
intentional. Version enforcement is the responsibility of from_dict()
|
|
442
|
+
at deserialization time. See PDE F-012.
|
|
443
|
+
|
|
444
|
+
Returns True if all structural invariants hold, False otherwise.
|
|
445
|
+
Raises nothing — returns False on any failure.
|
|
446
|
+
"""
|
|
447
|
+
_SEAL_RE = re.compile(r'^[0-9a-f]{64}$')
|
|
448
|
+
if not (isinstance(self.seal, str) and _SEAL_RE.match(self.seal)):
|
|
449
|
+
return False
|
|
450
|
+
if not isinstance(self.unix_utc, int) or self.unix_utc <= 0:
|
|
451
|
+
return False
|
|
452
|
+
for f in ('gregorian', 'hebrew', 'dreamspell', 'version'):
|
|
453
|
+
v = getattr(self, f, None)
|
|
454
|
+
if not (isinstance(v, str) and v):
|
|
455
|
+
return False
|
|
456
|
+
return True
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
460
|
+
# SOVEREIGN RECORD — TYPED {entry, stamp} PAIR
|
|
461
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
462
|
+
|
|
463
|
+
class SovereignRecord:
|
|
464
|
+
"""
|
|
465
|
+
Canonical {entry, stamp} pair — the minimal unit of ledger storage.
|
|
466
|
+
|
|
467
|
+
The stamp alone cannot be verified without the entry.
|
|
468
|
+
The entry alone has no cryptographic anchor.
|
|
469
|
+
The SovereignRecord is the minimal unit of sovereign trace:
|
|
470
|
+
it carries both the content and the proof.
|
|
471
|
+
|
|
472
|
+
Immutable — same pattern as SovereignStamp.
|
|
473
|
+
"""
|
|
474
|
+
__slots__ = ("entry", "stamp")
|
|
475
|
+
|
|
476
|
+
def __init__(self, entry: str, stamp: SovereignStamp):
|
|
477
|
+
object.__setattr__(self, "entry", entry)
|
|
478
|
+
object.__setattr__(self, "stamp", stamp)
|
|
479
|
+
|
|
480
|
+
def __setattr__(self, *_):
|
|
481
|
+
raise AttributeError("SovereignRecord is immutable.")
|
|
482
|
+
|
|
483
|
+
def __repr__(self):
|
|
484
|
+
return (
|
|
485
|
+
f"SovereignRecord("
|
|
486
|
+
f"entry={self.entry[:40]!r}{'...' if len(self.entry) > 40 else ''}, "
|
|
487
|
+
f"stamp={self.stamp!r})"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
def verify(self) -> "VerifyResult":
|
|
491
|
+
"""Verify the entry against the stamp seal."""
|
|
492
|
+
return verify(self.entry, self.stamp)
|
|
493
|
+
|
|
494
|
+
def to_dict(self) -> dict:
|
|
495
|
+
"""
|
|
496
|
+
Serialize to a plain dict (for JSON storage).
|
|
497
|
+
|
|
498
|
+
Note on dict structure (PDE F-026):
|
|
499
|
+
The returned dict contains "entry" at the top level alongside
|
|
500
|
+
the stamp fields. The SHA-256 seal was computed WITH entry_text
|
|
501
|
+
as the "entry" field in the seal payload — so the seal does
|
|
502
|
+
cover the entry, but only via the verify() path, not by
|
|
503
|
+
inspecting the dict directly. To confirm a record is intact,
|
|
504
|
+
use SovereignRecord.verify() or module-level verify(entry, stamp).
|
|
505
|
+
Do not attempt to recompute the seal directly from the dict
|
|
506
|
+
fields — use from_dict() then verify().
|
|
507
|
+
"""
|
|
508
|
+
d = to_dict(self.stamp)
|
|
509
|
+
d["entry"] = self.entry
|
|
510
|
+
return d
|
|
511
|
+
|
|
512
|
+
@classmethod
|
|
513
|
+
def from_dict(cls, d: dict) -> "SovereignRecord":
|
|
514
|
+
"""
|
|
515
|
+
Deserialize a SovereignRecord from a plain dict.
|
|
516
|
+
Validates schema before construction.
|
|
517
|
+
Raises ValueError if required fields are missing or malformed.
|
|
518
|
+
|
|
519
|
+
Note: the dict may contain an "entry" key alongside stamp fields.
|
|
520
|
+
The module-level from_dict() tolerates extra keys (including
|
|
521
|
+
"entry") without raising. This behavior is intentional and
|
|
522
|
+
declared. See PDE F-017.
|
|
523
|
+
"""
|
|
524
|
+
if "entry" not in d:
|
|
525
|
+
raise ValueError("from_dict (SovereignRecord): missing field 'entry'")
|
|
526
|
+
entry = d["entry"]
|
|
527
|
+
if not isinstance(entry, str) or not entry:
|
|
528
|
+
raise ValueError("from_dict (SovereignRecord): 'entry' must be a non-empty string")
|
|
529
|
+
stamp_obj = from_dict(d)
|
|
530
|
+
return cls(entry, stamp_obj)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
534
|
+
# VERIFY RESULT — NAMED RETURN
|
|
535
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
536
|
+
|
|
537
|
+
class VerifyResult(NamedTuple):
|
|
538
|
+
"""
|
|
539
|
+
Named result from verify(). Evaluates as bool via .valid.
|
|
540
|
+
|
|
541
|
+
valid : bool — True if seal matches entry
|
|
542
|
+
reason : str — one of the values below
|
|
543
|
+
|
|
544
|
+
Reason codes:
|
|
545
|
+
"VALID" — seal matches entry correctly
|
|
546
|
+
"SEAL_MISMATCH" — seal does not match (tampered content, wrong
|
|
547
|
+
entry supplied, or wrong frozen version used
|
|
548
|
+
to verify — see cross-version note in header)
|
|
549
|
+
"TYPE_ERROR" — entry_text is not a str (wrong type, not tampering)
|
|
550
|
+
|
|
551
|
+
IMPORTANT — VERSION_MISMATCH (PDE F-011, F-025, XD-002):
|
|
552
|
+
If you receive SEAL_MISMATCH on a record you trust, check the
|
|
553
|
+
record's "version" field before concluding tampering occurred.
|
|
554
|
+
If version != "FROZEN-4.0", the mismatch is likely because you
|
|
555
|
+
are using the wrong frozen version to verify. Retrieve the
|
|
556
|
+
matching archived version. This distinction will be resolved
|
|
557
|
+
with an explicit "VERSION_MISMATCH" reason code in FROZEN-5.0.
|
|
558
|
+
|
|
559
|
+
VerifyResult(True, "VALID") is truthy.
|
|
560
|
+
VerifyResult(False, "TYPE_ERROR") is falsy — distinct from tampering.
|
|
561
|
+
"""
|
|
562
|
+
valid: bool
|
|
563
|
+
reason: str
|
|
564
|
+
|
|
565
|
+
def __bool__(self):
|
|
566
|
+
return self.valid
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
570
|
+
# GREGORIAN
|
|
571
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
572
|
+
|
|
573
|
+
_GREGORIAN_MONTHS = (
|
|
574
|
+
"January", "February", "March", "April", "May", "June",
|
|
575
|
+
"July", "August", "September", "October", "November", "December"
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
def _gregorian(d: _date) -> str:
|
|
579
|
+
return f"{_GREGORIAN_MONTHS[d.month - 1]} {d.day}, {d.year}"
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
583
|
+
# JULIAN DAY NUMBER (internal bridge — no external use)
|
|
584
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
585
|
+
|
|
586
|
+
_JD_MIN_YEAR = 1582
|
|
587
|
+
_JD_MAX_YEAR = 4000
|
|
588
|
+
|
|
589
|
+
def _jd_from_gregorian(year: int, month: int, day: int) -> int:
|
|
590
|
+
"""
|
|
591
|
+
Julian Day Number from Gregorian date.
|
|
592
|
+
Algorithm: Meeus, "Astronomical Algorithms", p. 61.
|
|
593
|
+
Verified: JD(Sep 23, 2025) = 2,460,942
|
|
594
|
+
JD(Mar 3, 2026) = 2,461,103
|
|
595
|
+
|
|
596
|
+
Raises ValueError for pre-reform dates (before Oct 15, 1582)
|
|
597
|
+
and for years outside [1582, 4000].
|
|
598
|
+
"""
|
|
599
|
+
if not (_JD_MIN_YEAR <= year <= _JD_MAX_YEAR):
|
|
600
|
+
raise ValueError(
|
|
601
|
+
f"_jd_from_gregorian: year {year} outside valid range "
|
|
602
|
+
f"[{_JD_MIN_YEAR}, {_JD_MAX_YEAR}]. "
|
|
603
|
+
"Dates before Gregorian calendar reform (Oct 15, 1582) "
|
|
604
|
+
"require the Julian calendar formula."
|
|
605
|
+
)
|
|
606
|
+
if year == 1582 and (month < 10 or (month == 10 and day < 15)):
|
|
607
|
+
raise ValueError(
|
|
608
|
+
f"_jd_from_gregorian: date {year}-{month:02d}-{day:02d} is before "
|
|
609
|
+
"Gregorian calendar reform (October 15, 1582). "
|
|
610
|
+
"Pre-reform dates require the Julian calendar formula."
|
|
611
|
+
)
|
|
612
|
+
a = (14 - month) // 12
|
|
613
|
+
y = year + 4800 - a
|
|
614
|
+
m = month + 12 * a - 3
|
|
615
|
+
return (day
|
|
616
|
+
+ (153 * m + 2) // 5
|
|
617
|
+
+ 365 * y
|
|
618
|
+
+ y // 4
|
|
619
|
+
- y // 100
|
|
620
|
+
+ y // 400
|
|
621
|
+
- 32045)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
625
|
+
# HEBREW CALENDAR — FULL FOUR DEHIYOT
|
|
626
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
627
|
+
|
|
628
|
+
_HEBREW_EPOCH_JD = 347998
|
|
629
|
+
_LEGAL_YEAR_LENGTHS = frozenset({353, 354, 355, 383, 384, 385})
|
|
630
|
+
_MAX_YEAR_SEARCH = 6 # D&R approximation is within 3 years [S, empirically verified over 1582–4000]
|
|
631
|
+
|
|
632
|
+
_HEBREW_MONTHS_COMMON = (
|
|
633
|
+
"Tishri", "Cheshvan", "Kislev", "Tevet", "Shevat", "Adar",
|
|
634
|
+
"Nisan", "Iyar", "Sivan", "Tammuz","Av", "Elul"
|
|
635
|
+
)
|
|
636
|
+
_HEBREW_MONTHS_LEAP = (
|
|
637
|
+
"Tishri", "Cheshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar II",
|
|
638
|
+
"Nisan", "Iyar", "Sivan", "Tammuz","Av", "Elul"
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _is_hebrew_leap(year: int) -> bool:
|
|
643
|
+
"""Hebrew leap year: (7y + 1) mod 19 < 7."""
|
|
644
|
+
return (7 * year + 1) % 19 < 7
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _elapsed_days(year: int) -> int:
|
|
648
|
+
"""
|
|
649
|
+
Days from Hebrew epoch to 1 Tishri of year — Pass 1.
|
|
650
|
+
Implements Lo ADU Rosh and Molad Zaken.
|
|
651
|
+
D&R §8.1–§8.2.
|
|
652
|
+
"""
|
|
653
|
+
months = (235 * year - 234) // 19
|
|
654
|
+
parts = 12084 + 13753 * months
|
|
655
|
+
day = months * 29 + parts // 25920
|
|
656
|
+
if (3 * (day + 1)) % 7 < 3:
|
|
657
|
+
day += 1
|
|
658
|
+
return day
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _new_year_delay(year: int) -> int:
|
|
662
|
+
"""
|
|
663
|
+
Additional days to postpone 1 Tishri — Pass 2.
|
|
664
|
+
Handles GaTaRaD and BeTUTeKaPoT. Returns 0, 1, or 2.
|
|
665
|
+
D&R §8.3–§8.4.
|
|
666
|
+
|
|
667
|
+
Mutual exclusion claim (PDE F-008): GaTaRaD (ny2-ny1==356) and
|
|
668
|
+
BeTUTeKaPoT (ny1-ny0==382) cannot be simultaneously true for any
|
|
669
|
+
valid Hebrew year. This is stated as derivable from D&R §8.4 [R].
|
|
670
|
+
The elif ordering follows D&R — GaTaRaD takes priority.
|
|
671
|
+
No runtime assertion is included per FROZEN architecture (no patches);
|
|
672
|
+
this is a FROZEN-5.0 candidate for a debug-mode assertion.
|
|
673
|
+
|
|
674
|
+
Verified: _new_year_delay(5786) = 1 (BeTUTeKaPoT active for 5786)
|
|
675
|
+
"""
|
|
676
|
+
ny0 = _elapsed_days(year - 1)
|
|
677
|
+
ny1 = _elapsed_days(year)
|
|
678
|
+
ny2 = _elapsed_days(year + 1)
|
|
679
|
+
if ny2 - ny1 == 356:
|
|
680
|
+
return 2
|
|
681
|
+
elif ny1 - ny0 == 382:
|
|
682
|
+
return 1
|
|
683
|
+
return 0
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _tishri_1_jd(h_year: int) -> int:
|
|
687
|
+
"""
|
|
688
|
+
Julian Day of 1 Tishri for the given Hebrew year.
|
|
689
|
+
Full four dehiyot: Pass 1 + Pass 2.
|
|
690
|
+
Civil-day convention.
|
|
691
|
+
Verified: _tishri_1_jd(5786) = 2,460,942 = Sep 23, 2025 [D]
|
|
692
|
+
"""
|
|
693
|
+
return _elapsed_days(h_year) + _new_year_delay(h_year) + _HEBREW_EPOCH_JD
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def _year_length(h_year: int) -> int:
|
|
697
|
+
return _tishri_1_jd(h_year + 1) - _tishri_1_jd(h_year)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def _month_lengths(h_year: int) -> tuple:
|
|
701
|
+
"""
|
|
702
|
+
Ordered month lengths (Tishri → Elul) for h_year.
|
|
703
|
+
Raises ValueError if year length is not in _LEGAL_YEAR_LENGTHS.
|
|
704
|
+
"""
|
|
705
|
+
ylen = _year_length(h_year)
|
|
706
|
+
if ylen not in _LEGAL_YEAR_LENGTHS:
|
|
707
|
+
raise ValueError(
|
|
708
|
+
f"Illegal Hebrew year length {ylen} for year {h_year}. "
|
|
709
|
+
f"Legal lengths: {sorted(_LEGAL_YEAR_LENGTHS)}. "
|
|
710
|
+
"Algorithm error — do not patch. Create FROZEN-5.0 with correction."
|
|
711
|
+
)
|
|
712
|
+
leap = _is_hebrew_leap(h_year)
|
|
713
|
+
long_cheshvan = (ylen % 10 == 5)
|
|
714
|
+
short_kislev = (ylen % 10 == 3)
|
|
715
|
+
months = [
|
|
716
|
+
30,
|
|
717
|
+
30 if long_cheshvan else 29,
|
|
718
|
+
29 if short_kislev else 30,
|
|
719
|
+
29,
|
|
720
|
+
30,
|
|
721
|
+
30 if leap else 29,
|
|
722
|
+
]
|
|
723
|
+
if leap:
|
|
724
|
+
months.append(29)
|
|
725
|
+
months += [30, 29, 30, 29, 30, 29]
|
|
726
|
+
return tuple(months)
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def _gregorian_to_hebrew(year: int, month: int, day: int) -> tuple:
|
|
730
|
+
"""
|
|
731
|
+
Convert Gregorian date to Hebrew (h_year, month_name, h_day).
|
|
732
|
+
Civil-day convention — aligns with Hebcal and Chabad.org.
|
|
733
|
+
D&R §8.1–§8.4 two-pass.
|
|
734
|
+
|
|
735
|
+
Verified anchors (Hebcal/Chabad):
|
|
736
|
+
gregorian_to_hebrew(2025, 9, 23) == (5786, "Tishri", 1) [D]
|
|
737
|
+
gregorian_to_hebrew(2025, 10, 2) == (5786, "Tishri", 10) [D]
|
|
738
|
+
gregorian_to_hebrew(2026, 3, 3) == (5786, "Adar", 14) [D] [Purim]
|
|
739
|
+
gregorian_to_hebrew(2026, 3, 19) == (5786, "Nisan", 1) [D]
|
|
740
|
+
gregorian_to_hebrew(2026, 4, 2) == (5786, "Nisan", 15) [D] [Passover]
|
|
741
|
+
gregorian_to_hebrew(2024, 3, 25) == (5784, "Adar II", 15) [D] [Leap]
|
|
742
|
+
gregorian_to_hebrew(2025, 9, 22) == (5786, "Elul", 29) [D] [Erev RH]
|
|
743
|
+
gregorian_to_hebrew(2023, 10, 7) == (5784, "Tishri", 22) [D]
|
|
744
|
+
gregorian_to_hebrew(2026, 9, 12) == (5787, "Tishri", 1) [D] [RH 5787]
|
|
745
|
+
gregorian_to_hebrew(2026, 9, 11) == (5786, "Elul", 29) [D] [Erev RH 5787]
|
|
746
|
+
|
|
747
|
+
Raises ValueError on algorithm error or out-of-range input.
|
|
748
|
+
"""
|
|
749
|
+
jd = _jd_from_gregorian(year, month, day)
|
|
750
|
+
h_year = (jd - _HEBREW_EPOCH_JD) * 19 // 6935 + 1
|
|
751
|
+
|
|
752
|
+
for _ in range(_MAX_YEAR_SEARCH):
|
|
753
|
+
if _tishri_1_jd(h_year + 1) <= jd:
|
|
754
|
+
h_year += 1
|
|
755
|
+
else:
|
|
756
|
+
break
|
|
757
|
+
else:
|
|
758
|
+
raise ValueError(
|
|
759
|
+
f"Hebrew year (upward) search did not converge for JD {jd} "
|
|
760
|
+
f"({year}-{month}-{day}). Algorithm error — create FROZEN-5.0."
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
for _ in range(_MAX_YEAR_SEARCH):
|
|
764
|
+
if _tishri_1_jd(h_year) > jd:
|
|
765
|
+
h_year -= 1
|
|
766
|
+
else:
|
|
767
|
+
break
|
|
768
|
+
else:
|
|
769
|
+
raise ValueError(
|
|
770
|
+
f"Hebrew year (downward) search did not converge for JD {jd} "
|
|
771
|
+
f"({year}-{month}-{day}). Algorithm error — create FROZEN-5.0."
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
day_of_year = jd - _tishri_1_jd(h_year)
|
|
775
|
+
leap = _is_hebrew_leap(h_year)
|
|
776
|
+
m_lens = _month_lengths(h_year)
|
|
777
|
+
m_names = _HEBREW_MONTHS_LEAP if leap else _HEBREW_MONTHS_COMMON
|
|
778
|
+
|
|
779
|
+
remaining = day_of_year
|
|
780
|
+
for i, length in enumerate(m_lens):
|
|
781
|
+
if remaining < length:
|
|
782
|
+
return h_year, m_names[i], remaining + 1
|
|
783
|
+
remaining -= length
|
|
784
|
+
|
|
785
|
+
raise ValueError(
|
|
786
|
+
f"Hebrew conversion overflowed for {year}-{month}-{day}. "
|
|
787
|
+
"Algorithm error — create FROZEN-5.0."
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def _hebrew(d: _date) -> str:
|
|
792
|
+
h_year, h_month, h_day = _gregorian_to_hebrew(d.year, d.month, d.day)
|
|
793
|
+
return f"{h_day} {h_month} {h_year}"
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
797
|
+
# 13 MOON DREAMSPELL (Argüelles system)
|
|
798
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
799
|
+
# Disambiguation: This is the José Argüelles 13 Moon calendar.
|
|
800
|
+
# It is NOT the traditional Maya Tzolkin or Haab correlation.
|
|
801
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
802
|
+
|
|
803
|
+
_MOON_NAMES = (
|
|
804
|
+
"Magnetic", "Lunar", "Electric", "Self-Existing",
|
|
805
|
+
"Overtone", "Rhythmic", "Resonant", "Galactic",
|
|
806
|
+
"Solar", "Planetary", "Spectral", "Crystal", "Cosmic"
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
def _dreamspell(d: _date) -> str:
|
|
810
|
+
"""
|
|
811
|
+
13 Moon Dreamspell calendar (Argüelles system).
|
|
812
|
+
|
|
813
|
+
Dead branch note (PDE F-009):
|
|
814
|
+
The delta >= 364 branch below is structurally unreachable.
|
|
815
|
+
In FROZEN-4.0 it returns "Day Out of Time" as a defensive guard.
|
|
816
|
+
FROZEN-5.0 CANDIDATE-01: change this to raise ValueError with a
|
|
817
|
+
diagnostic message rather than returning a plausible-wrong string.
|
|
818
|
+
|
|
819
|
+
Verified anchors:
|
|
820
|
+
dreamspell(date(2026, 3, 3)) == "Day 25, Galactic Moon 8/13" [R]
|
|
821
|
+
dreamspell(date(2026, 7, 25)) == "Day Out of Time" [R]
|
|
822
|
+
dreamspell(date(2026, 7, 26)) == "Day 1, Magnetic Moon 1/13" [R]
|
|
823
|
+
"""
|
|
824
|
+
if d.month == 7 and d.day == 25:
|
|
825
|
+
return "Day Out of Time"
|
|
826
|
+
|
|
827
|
+
if (d.month, d.day) >= (7, 26):
|
|
828
|
+
year_start = _date(d.year, 7, 26)
|
|
829
|
+
else:
|
|
830
|
+
year_start = _date(d.year - 1, 7, 26)
|
|
831
|
+
|
|
832
|
+
delta = (d - year_start).days
|
|
833
|
+
|
|
834
|
+
if delta < 0 or delta >= 364: # pragma: no cover — defensive only (FROZEN-5.0 candidate)
|
|
835
|
+
return "Day Out of Time"
|
|
836
|
+
|
|
837
|
+
moon = delta // 28 + 1
|
|
838
|
+
day_num = delta % 28 + 1
|
|
839
|
+
return f"Day {day_num}, {_MOON_NAMES[moon - 1]} Moon {moon}/13"
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
843
|
+
# SEAL — SHA-256 CRYPTOGRAPHIC BINDING
|
|
844
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
845
|
+
|
|
846
|
+
_SEAL_PATTERN = re.compile(r'^[0-9a-f]{64}$')
|
|
847
|
+
|
|
848
|
+
# Invisible character categories — used by _validate_entry_text.
|
|
849
|
+
# A trace entry consisting entirely of these code points is invisible
|
|
850
|
+
# and seals nothing meaningful. (Adversarial battery finding — June 2026)
|
|
851
|
+
# Cc=control, Cf=format/invisible, Cs=surrogate, Co=private, Cn=unassigned
|
|
852
|
+
_INVISIBLE_CATEGORIES = frozenset({'Cc', 'Cf', 'Cs', 'Co', 'Cn'})
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _validate_entry_text(entry_text: str) -> str:
|
|
856
|
+
"""
|
|
857
|
+
Validate and normalize entry_text. Returns the normalized string.
|
|
858
|
+
Raises TypeError if not a str.
|
|
859
|
+
Raises ValueError if empty after normalization, or if it consists
|
|
860
|
+
entirely of invisible/control Unicode characters.
|
|
861
|
+
|
|
862
|
+
Called by stamp() and stamp_and_record() — single validation point.
|
|
863
|
+
|
|
864
|
+
Invisible character guard:
|
|
865
|
+
str.strip() does not remove zero-width spaces (U+200B), null bytes
|
|
866
|
+
(\x00), or other invisible Unicode code points. An entry consisting
|
|
867
|
+
only of invisible characters seals nothing meaningful and is rejected.
|
|
868
|
+
Test: stamp("\u200b") → ValueError; stamp("\x00") → ValueError.
|
|
869
|
+
"""
|
|
870
|
+
if not isinstance(entry_text, str):
|
|
871
|
+
raise TypeError(f"entry_text must be str, got {type(entry_text).__name__}")
|
|
872
|
+
entry_text = unicodedata.normalize("NFC", entry_text).strip()
|
|
873
|
+
if not entry_text:
|
|
874
|
+
raise ValueError("entry_text must be non-empty — an empty trace seals nothing.")
|
|
875
|
+
if all(unicodedata.category(c) in _INVISIBLE_CATEGORIES for c in entry_text):
|
|
876
|
+
raise ValueError(
|
|
877
|
+
"entry_text consists entirely of invisible or control characters — "
|
|
878
|
+
"an invisible trace seals nothing. Provide visible content."
|
|
879
|
+
)
|
|
880
|
+
return entry_text
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _compute_seal(entry_text: str, gregorian: str, hebrew: str,
|
|
884
|
+
dreamspell: str, unix_utc: int) -> str:
|
|
885
|
+
"""
|
|
886
|
+
SHA-256 of a deterministic JSON payload.
|
|
887
|
+
Output: 64-character lowercase hex string.
|
|
888
|
+
|
|
889
|
+
Payload field order (sort_keys=True — alphabetical):
|
|
890
|
+
dreamspell, entry, gregorian, hebrew, unix_utc, version
|
|
891
|
+
|
|
892
|
+
INTEGRITY BOUNDARY NOTE:
|
|
893
|
+
This seal proves that entry_text was bound to these exact calendar
|
|
894
|
+
strings at this exact unix_utc by FROZEN-4.0. It does NOT prove
|
|
895
|
+
that the calendar strings are correctly computed. Calendar
|
|
896
|
+
correctness is guaranteed by the self-test and algorithm spec,
|
|
897
|
+
not by the seal itself. See INTEGRITY BOUNDARIES section in the
|
|
898
|
+
module docstring.
|
|
899
|
+
|
|
900
|
+
CLI JSON output uses ensure_ascii=False for human readability.
|
|
901
|
+
This function uses ensure_ascii=True for seal determinism.
|
|
902
|
+
These are different serializations. Never attempt to recompute
|
|
903
|
+
the seal directly from CLI JSON output — use to_dict() → verify().
|
|
904
|
+
(PDE F-010)
|
|
905
|
+
"""
|
|
906
|
+
payload = json.dumps(
|
|
907
|
+
{
|
|
908
|
+
"dreamspell": dreamspell,
|
|
909
|
+
"entry": entry_text,
|
|
910
|
+
"gregorian": gregorian,
|
|
911
|
+
"hebrew": hebrew,
|
|
912
|
+
"unix_utc": unix_utc,
|
|
913
|
+
"version": _FROZEN_VERSION,
|
|
914
|
+
},
|
|
915
|
+
sort_keys=True,
|
|
916
|
+
ensure_ascii=True,
|
|
917
|
+
separators=(",", ":"),
|
|
918
|
+
)
|
|
919
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
923
|
+
# PUBLIC INTERFACE
|
|
924
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
925
|
+
|
|
926
|
+
def stamp(entry_text: str, dt: datetime = None) -> SovereignStamp:
|
|
927
|
+
"""
|
|
928
|
+
Generate a frozen triple-time cryptographic stamp for an entry.
|
|
929
|
+
|
|
930
|
+
Parameters
|
|
931
|
+
----------
|
|
932
|
+
entry_text : str
|
|
933
|
+
The sovereign trace entry to seal. Must be non-empty.
|
|
934
|
+
NFC-normalized and stripped before sealing.
|
|
935
|
+
dt : datetime, optional
|
|
936
|
+
Moment to stamp. Defaults to UTC now.
|
|
937
|
+
Must be timezone-aware if provided.
|
|
938
|
+
|
|
939
|
+
Returns
|
|
940
|
+
-------
|
|
941
|
+
SovereignStamp — immutable, triple-time, SHA-256 sealed.
|
|
942
|
+
|
|
943
|
+
Raises
|
|
944
|
+
------
|
|
945
|
+
ValueError — timezone-naive dt; empty entry; out-of-range date;
|
|
946
|
+
illegal Hebrew year length; Hebrew conversion overflow.
|
|
947
|
+
TypeError — entry_text is not a string.
|
|
948
|
+
|
|
949
|
+
Notes
|
|
950
|
+
-----
|
|
951
|
+
unix_utc is whole seconds only (truncated, not rounded).
|
|
952
|
+
Calendar dates reflect UTC — not local civil date.
|
|
953
|
+
|
|
954
|
+
Temporal consistency: if you call stamp() in a loop without
|
|
955
|
+
supplying dt, entries stamped across a UTC midnight boundary
|
|
956
|
+
will have different calendar dates. Use stamp_batch() or
|
|
957
|
+
supply an explicit dt for temporal consistency. (PDE F-020)
|
|
958
|
+
|
|
959
|
+
Pre-1970 UTC dates: the calendar algorithm supports these but
|
|
960
|
+
unix_utc will be 0 or negative, which will fail from_dict()
|
|
961
|
+
deserialization. See FROZEN-5.0 CANDIDATE-02. (PDE F-006)
|
|
962
|
+
"""
|
|
963
|
+
entry_text = _validate_entry_text(entry_text)
|
|
964
|
+
|
|
965
|
+
if dt is None:
|
|
966
|
+
dt = datetime.now(timezone.utc)
|
|
967
|
+
elif dt.tzinfo is None:
|
|
968
|
+
raise ValueError(
|
|
969
|
+
"dt must be timezone-aware. "
|
|
970
|
+
"Use datetime(..., tzinfo=timezone.utc) or datetime.now(timezone.utc)."
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
d = dt.date()
|
|
974
|
+
unix_utc = int(dt.timestamp())
|
|
975
|
+
|
|
976
|
+
g = _gregorian(d)
|
|
977
|
+
h = _hebrew(d)
|
|
978
|
+
ds = _dreamspell(d)
|
|
979
|
+
sl = _compute_seal(entry_text, g, h, ds, unix_utc)
|
|
980
|
+
|
|
981
|
+
return SovereignStamp(
|
|
982
|
+
gregorian = g,
|
|
983
|
+
hebrew = h,
|
|
984
|
+
dreamspell = ds,
|
|
985
|
+
unix_utc = unix_utc,
|
|
986
|
+
seal = sl,
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def stamp_and_record(entry_text: str, dt: datetime = None) -> SovereignRecord:
|
|
991
|
+
"""
|
|
992
|
+
Generate a SovereignRecord — the canonical {entry, stamp} pair.
|
|
993
|
+
Applies the same NFC normalization as stamp().
|
|
994
|
+
"""
|
|
995
|
+
entry_text = _validate_entry_text(entry_text)
|
|
996
|
+
ts = stamp(entry_text, dt)
|
|
997
|
+
return SovereignRecord(entry_text, ts)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def stamp_batch(entries: list, dt: datetime = None) -> list:
|
|
1001
|
+
"""
|
|
1002
|
+
Seal multiple entries at a single canonical timestamp.
|
|
1003
|
+
|
|
1004
|
+
All entries share the same unix_utc, gregorian, hebrew, and
|
|
1005
|
+
dreamspell values. dt is captured once.
|
|
1006
|
+
|
|
1007
|
+
Atomicity: NOT transactional. No rollback. If an entry raises,
|
|
1008
|
+
entries already processed are not rolled back. Validate all
|
|
1009
|
+
entries before calling if all-or-nothing semantics are required.
|
|
1010
|
+
(PDE F-007 — semantic precision note)
|
|
1011
|
+
|
|
1012
|
+
Returns list of SovereignRecord, one per entry, in input order.
|
|
1013
|
+
"""
|
|
1014
|
+
if dt is None:
|
|
1015
|
+
dt = datetime.now(timezone.utc)
|
|
1016
|
+
return [stamp_and_record(e, dt) for e in entries]
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def display(ts: SovereignStamp) -> str:
|
|
1020
|
+
"""Render a SovereignStamp for human display (emoji format, full seal)."""
|
|
1021
|
+
return (
|
|
1022
|
+
f"📅 Gregorian: {ts.gregorian}\n"
|
|
1023
|
+
f"🌑 Hebrew: {ts.hebrew}\n"
|
|
1024
|
+
f"🌀 Dreamspell: {ts.dreamspell}\n"
|
|
1025
|
+
f"🔒 Seal: {ts.seal}\n"
|
|
1026
|
+
f"📌 Version: {ts.version}"
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def display_ascii(ts: SovereignStamp) -> str:
|
|
1031
|
+
"""
|
|
1032
|
+
Render a SovereignStamp without emoji (full seal).
|
|
1033
|
+
For logging, piping, ASCII terminals, or CI output.
|
|
1034
|
+
"""
|
|
1035
|
+
return (
|
|
1036
|
+
f"Gregorian: {ts.gregorian}\n"
|
|
1037
|
+
f"Hebrew: {ts.hebrew}\n"
|
|
1038
|
+
f"Dreamspell: {ts.dreamspell}\n"
|
|
1039
|
+
f"Seal: {ts.seal}\n"
|
|
1040
|
+
f"Version: {ts.version}"
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def verify(entry_text: str, ts: SovereignStamp) -> VerifyResult:
|
|
1045
|
+
"""
|
|
1046
|
+
Verify that entry_text matches the seal in ts.
|
|
1047
|
+
|
|
1048
|
+
Returns VerifyResult — evaluates as bool.
|
|
1049
|
+
|
|
1050
|
+
Reason codes:
|
|
1051
|
+
"VALID" — seal matches entry correctly
|
|
1052
|
+
"TYPE_ERROR" — entry_text is not a str
|
|
1053
|
+
"SEAL_MISMATCH" — seal does not match
|
|
1054
|
+
|
|
1055
|
+
IMPORTANT: SEAL_MISMATCH may mean wrong frozen version used,
|
|
1056
|
+
not necessarily tampered content. Check ts.version. If it is
|
|
1057
|
+
not "FROZEN-4.0", use the matching archived frozen version.
|
|
1058
|
+
A VERSION_MISMATCH reason code will be added in FROZEN-5.0.
|
|
1059
|
+
(PDE F-011, F-025, XD-002 — see FROZEN-5.0 CANDIDATES)
|
|
1060
|
+
"""
|
|
1061
|
+
if not isinstance(entry_text, str):
|
|
1062
|
+
return VerifyResult(False, "TYPE_ERROR")
|
|
1063
|
+
entry_text = unicodedata.normalize("NFC", entry_text).strip()
|
|
1064
|
+
expected = _compute_seal(
|
|
1065
|
+
entry_text,
|
|
1066
|
+
ts.gregorian,
|
|
1067
|
+
ts.hebrew,
|
|
1068
|
+
ts.dreamspell,
|
|
1069
|
+
ts.unix_utc,
|
|
1070
|
+
)
|
|
1071
|
+
if expected == ts.seal:
|
|
1072
|
+
return VerifyResult(True, "VALID")
|
|
1073
|
+
return VerifyResult(False, "SEAL_MISMATCH")
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
def to_dict(ts: SovereignStamp) -> dict:
|
|
1077
|
+
"""
|
|
1078
|
+
Serialize SovereignStamp to a plain dict (for JSON storage).
|
|
1079
|
+
|
|
1080
|
+
Note: unix_utc is stored as int. Use int(d["unix_utc"]) when
|
|
1081
|
+
reading back if your JSON layer converts integers to floats.
|
|
1082
|
+
from_dict() enforces int coercion.
|
|
1083
|
+
|
|
1084
|
+
Extra keys in the dict (e.g., "entry" added by SovereignRecord)
|
|
1085
|
+
are tolerated by from_dict() without raising. This behavior is
|
|
1086
|
+
intentional — from_dict() checks for required fields only.
|
|
1087
|
+
(PDE F-017)
|
|
1088
|
+
"""
|
|
1089
|
+
return {
|
|
1090
|
+
"gregorian": ts.gregorian,
|
|
1091
|
+
"hebrew": ts.hebrew,
|
|
1092
|
+
"dreamspell": ts.dreamspell,
|
|
1093
|
+
"unix_utc": ts.unix_utc,
|
|
1094
|
+
"seal": ts.seal,
|
|
1095
|
+
"version": ts.version,
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
_REQUIRED_FIELDS = frozenset({"gregorian", "hebrew", "dreamspell", "unix_utc", "seal", "version"})
|
|
1100
|
+
|
|
1101
|
+
def from_dict(d: dict) -> SovereignStamp:
|
|
1102
|
+
"""
|
|
1103
|
+
Deserialize a SovereignStamp from a plain dict.
|
|
1104
|
+
Validates schema before construction.
|
|
1105
|
+
|
|
1106
|
+
Extra keys in d (beyond _REQUIRED_FIELDS) are tolerated without
|
|
1107
|
+
raising — this is intentional to support SovereignRecord.from_dict()
|
|
1108
|
+
which passes a dict containing an "entry" key alongside stamp fields.
|
|
1109
|
+
(PDE F-017)
|
|
1110
|
+
|
|
1111
|
+
Raises ValueError for missing fields, invalid seal format,
|
|
1112
|
+
non-string calendar fields, version mismatch, or invalid unix_utc.
|
|
1113
|
+
"""
|
|
1114
|
+
missing = _REQUIRED_FIELDS - d.keys()
|
|
1115
|
+
if missing:
|
|
1116
|
+
raise ValueError(f"from_dict: missing required fields: {sorted(missing)}")
|
|
1117
|
+
|
|
1118
|
+
if not (isinstance(d["seal"], str) and _SEAL_PATTERN.match(d["seal"])):
|
|
1119
|
+
raise ValueError(
|
|
1120
|
+
f"from_dict: 'seal' must be a 64-character lowercase hex string. "
|
|
1121
|
+
f"Got: {d['seal']!r}"
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
for k in ("gregorian", "hebrew", "dreamspell", "version"):
|
|
1125
|
+
if not (isinstance(d[k], str) and d[k]):
|
|
1126
|
+
raise ValueError(
|
|
1127
|
+
f"from_dict: '{k}' must be a non-empty string. Got: {d[k]!r}"
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
if d["version"] != _FROZEN_VERSION:
|
|
1131
|
+
raise ValueError(
|
|
1132
|
+
f"from_dict: 'version' field is {d['version']!r}. "
|
|
1133
|
+
f"This reader is {_FROZEN_VERSION}. "
|
|
1134
|
+
"Use the matching archived frozen version to deserialize older stamps."
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
try:
|
|
1138
|
+
unix_utc = int(d["unix_utc"])
|
|
1139
|
+
if unix_utc <= 0:
|
|
1140
|
+
raise ValueError("unix_utc must be a positive integer")
|
|
1141
|
+
except (TypeError, ValueError) as exc:
|
|
1142
|
+
raise ValueError(f"from_dict: 'unix_utc' invalid — {exc}") from exc
|
|
1143
|
+
|
|
1144
|
+
return SovereignStamp(
|
|
1145
|
+
gregorian = d["gregorian"],
|
|
1146
|
+
hebrew = d["hebrew"],
|
|
1147
|
+
dreamspell = d["dreamspell"],
|
|
1148
|
+
unix_utc = unix_utc,
|
|
1149
|
+
seal = d["seal"],
|
|
1150
|
+
version = d["version"],
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
1155
|
+
# SELF-TEST — FROZEN VERIFICATION CASES
|
|
1156
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
1157
|
+
|
|
1158
|
+
def _run_self_test(verbose: bool = True) -> None:
|
|
1159
|
+
"""
|
|
1160
|
+
Verification against anchor values known at time of writing.
|
|
1161
|
+
Any failure means calendar algorithms have been broken or the
|
|
1162
|
+
file has been modified.
|
|
1163
|
+
|
|
1164
|
+
Do not modify to make tests pass — create FROZEN-5.0.
|
|
1165
|
+
|
|
1166
|
+
Self-test check count: 82 algorithmic checks + 2 sentinel checks
|
|
1167
|
+
+ 3 invisible character checks = 87 total.
|
|
1168
|
+
The 2 sentinel checks address PDE F-001 and F-002.
|
|
1169
|
+
The 3 invisible character checks address adversarial battery
|
|
1170
|
+
finding (zero-width space, null byte, invisible char sequence).
|
|
1171
|
+
|
|
1172
|
+
Failure output goes to stderr (PDE F-019, F-024) to prevent
|
|
1173
|
+
corruption of --json mode piped output.
|
|
1174
|
+
"""
|
|
1175
|
+
import sys
|
|
1176
|
+
failures = []
|
|
1177
|
+
|
|
1178
|
+
def check(label, actual, expected):
|
|
1179
|
+
if actual != expected:
|
|
1180
|
+
failures.append(f" FAIL — {label}: got {actual!r}, expected {expected!r}")
|
|
1181
|
+
elif verbose:
|
|
1182
|
+
print(f" ✓ {label}: {actual!r}")
|
|
1183
|
+
|
|
1184
|
+
anchor_date = _date(2026, 3, 3)
|
|
1185
|
+
anchor_dt = datetime(2026, 3, 3, 15, 0, 0, tzinfo=timezone.utc)
|
|
1186
|
+
|
|
1187
|
+
if verbose:
|
|
1188
|
+
print("═══ SOVEREIGN TRACE STAMP FROZEN-4.0 — SELF-TEST ═══")
|
|
1189
|
+
print(f"Anchor: {anchor_date} (session of origin)\n")
|
|
1190
|
+
|
|
1191
|
+
# ── SENTINEL STATE CHECK (PDE F-001, F-002) ───────────────────
|
|
1192
|
+
# This check must run first. A pre-release copy with an unset
|
|
1193
|
+
# sentinel should be identified immediately.
|
|
1194
|
+
if _FROZEN_FILE_SHA256 == "SET_AT_RELEASE_TIME":
|
|
1195
|
+
failures.append(
|
|
1196
|
+
" WARN — _FROZEN_FILE_SHA256 is 'SET_AT_RELEASE_TIME'. "
|
|
1197
|
+
"This is a pre-release copy. File integrity cannot be verified. "
|
|
1198
|
+
"Do not deploy until the sentinel is set to the actual file hash."
|
|
1199
|
+
)
|
|
1200
|
+
elif verbose:
|
|
1201
|
+
print(f" ✓ Sentinel is set (not placeholder): {_FROZEN_FILE_SHA256[:16]}...")
|
|
1202
|
+
|
|
1203
|
+
# ── GENESIS SEAL STRUCTURAL CHECK ─────────────────────────────
|
|
1204
|
+
check("Genesis seal is 64-char hex",
|
|
1205
|
+
bool(_SEAL_PATTERN.match(_GENESIS_SEAL)), True)
|
|
1206
|
+
check("Genesis ledger entry declared",
|
|
1207
|
+
_GENESIS_LEDGER_ENTRY, "LEDGER-001-FOUNDING-SEAL")
|
|
1208
|
+
|
|
1209
|
+
# ── Gregorian ─────────────────────────────────────────────────
|
|
1210
|
+
check("Gregorian", _gregorian(anchor_date), "March 3, 2026")
|
|
1211
|
+
|
|
1212
|
+
# ── JD bridge ─────────────────────────────────────────────────
|
|
1213
|
+
check("JD(Sep 23 2025)", _jd_from_gregorian(2025, 9, 23), 2460942)
|
|
1214
|
+
check("JD(Mar 3 2026)", _jd_from_gregorian(2026, 3, 3), 2461103)
|
|
1215
|
+
|
|
1216
|
+
# ── Hebrew epoch ──────────────────────────────────────────────
|
|
1217
|
+
check("1 Tishri 5786 JD", _tishri_1_jd(5786), 2460942)
|
|
1218
|
+
check("1 Tishri 5787 JD", _tishri_1_jd(5787), 2461296)
|
|
1219
|
+
|
|
1220
|
+
# ── Hebrew year structure ─────────────────────────────────────
|
|
1221
|
+
check("5786 is NOT leap", _is_hebrew_leap(5786), False)
|
|
1222
|
+
check("5784 IS leap", _is_hebrew_leap(5784), True)
|
|
1223
|
+
check("5787 IS leap", _is_hebrew_leap(5787), True)
|
|
1224
|
+
check("Year 5786 length", _year_length(5786), 354)
|
|
1225
|
+
check("5786 legal length", _year_length(5786) in _LEGAL_YEAR_LENGTHS, True)
|
|
1226
|
+
check("Year 5785 legal", _year_length(5785) in _LEGAL_YEAR_LENGTHS, True)
|
|
1227
|
+
check("Year 5784 legal", _year_length(5784) in _LEGAL_YEAR_LENGTHS, True)
|
|
1228
|
+
|
|
1229
|
+
# ── Hebrew date conversion ────────────────────────────────────
|
|
1230
|
+
h_year, h_month, h_day = _gregorian_to_hebrew(2026, 3, 3)
|
|
1231
|
+
check("Hebrew year (Mar 3 2026)", h_year, 5786)
|
|
1232
|
+
check("Hebrew month (Mar 3 2026)", h_month, "Adar")
|
|
1233
|
+
check("Hebrew day (Mar 3 2026)", h_day, 14)
|
|
1234
|
+
check("Hebrew string (Mar 3 2026)", _hebrew(anchor_date), "14 Adar 5786")
|
|
1235
|
+
|
|
1236
|
+
# ── Rosh Hashanah ─────────────────────────────────────────────
|
|
1237
|
+
rh_year, rh_month, rh_day = _gregorian_to_hebrew(2025, 9, 23)
|
|
1238
|
+
check("RH 5786 year", rh_year, 5786)
|
|
1239
|
+
check("RH 5786 month", rh_month, "Tishri")
|
|
1240
|
+
check("RH 5786 day", rh_day, 1)
|
|
1241
|
+
|
|
1242
|
+
# ── Erev Rosh Hashanah ────────────────────────────────────────
|
|
1243
|
+
evrh_year, evrh_month, evrh_day = _gregorian_to_hebrew(2025, 9, 22)
|
|
1244
|
+
check("Erev RH 5786 month", evrh_month, "Elul")
|
|
1245
|
+
check("Erev RH 5786 day", evrh_day, 29)
|
|
1246
|
+
|
|
1247
|
+
# ── Yom Kippur ────────────────────────────────────────────────
|
|
1248
|
+
yk_year, yk_month, yk_day = _gregorian_to_hebrew(2025, 10, 2)
|
|
1249
|
+
check("Yom Kippur 5786 month", yk_month, "Tishri")
|
|
1250
|
+
check("Yom Kippur 5786 day", yk_day, 10)
|
|
1251
|
+
|
|
1252
|
+
# ── Passover ──────────────────────────────────────────────────
|
|
1253
|
+
ps_year, ps_month, ps_day = _gregorian_to_hebrew(2026, 4, 2)
|
|
1254
|
+
check("Passover 5786 month", ps_month, "Nisan")
|
|
1255
|
+
check("Passover 5786 day", ps_day, 15)
|
|
1256
|
+
|
|
1257
|
+
# ── 1 Nisan ───────────────────────────────────────────────────
|
|
1258
|
+
n1_year, n1_month, n1_day = _gregorian_to_hebrew(2026, 3, 19)
|
|
1259
|
+
check("1 Nisan 5786 month", n1_month, "Nisan")
|
|
1260
|
+
check("1 Nisan 5786 day", n1_day, 1)
|
|
1261
|
+
|
|
1262
|
+
# ── Leap year anchors ─────────────────────────────────────────
|
|
1263
|
+
leap_y, leap_m, leap_d = _gregorian_to_hebrew(2024, 3, 25)
|
|
1264
|
+
check("Leap year 5784 month (Adar II)", leap_m, "Adar II")
|
|
1265
|
+
check("Leap year 5784 day (15)", leap_d, 15)
|
|
1266
|
+
check("Leap year 5784 year", leap_y, 5784)
|
|
1267
|
+
|
|
1268
|
+
adar1_y, adar1_m, adar1_d = _gregorian_to_hebrew(2024, 2, 10)
|
|
1269
|
+
check("Adar I 5784 month", adar1_m, "Adar I")
|
|
1270
|
+
check("Adar I 5784 day", adar1_d, 1)
|
|
1271
|
+
|
|
1272
|
+
# ── RH 5787 (FROZEN-3.0 CRITICAL fix) ────────────────────────
|
|
1273
|
+
rh87_y, rh87_m, rh87_d = _gregorian_to_hebrew(2026, 9, 12)
|
|
1274
|
+
check("RH 5787 year", rh87_y, 5787)
|
|
1275
|
+
check("RH 5787 month", rh87_m, "Tishri")
|
|
1276
|
+
check("RH 5787 day", rh87_d, 1)
|
|
1277
|
+
|
|
1278
|
+
erh87_y, erh87_m, erh87_d = _gregorian_to_hebrew(2026, 9, 11)
|
|
1279
|
+
check("Erev RH 5787 month", erh87_m, "Elul")
|
|
1280
|
+
check("Erev RH 5787 day", erh87_d, 29)
|
|
1281
|
+
|
|
1282
|
+
sep22_y, sep22_m, sep22_d = _gregorian_to_hebrew(2026, 9, 22)
|
|
1283
|
+
check("Sep 22 2026 day is 11 (NOT 1)", sep22_d, 11)
|
|
1284
|
+
check("Sep 22 2026 month is Tishri", sep22_m, "Tishri")
|
|
1285
|
+
|
|
1286
|
+
# ── Dreamspell ────────────────────────────────────────────────
|
|
1287
|
+
check("Dreamspell Mar 3 2026", _dreamspell(anchor_date),
|
|
1288
|
+
"Day 25, Galactic Moon 8/13")
|
|
1289
|
+
check("Day Out of Time Jul 25", _dreamspell(_date(2026, 7, 25)),
|
|
1290
|
+
"Day Out of Time")
|
|
1291
|
+
check("Dreamspell Jul 26 (Moon 1 Day 1)",
|
|
1292
|
+
_dreamspell(_date(2026, 7, 26)), "Day 1, Magnetic Moon 1/13")
|
|
1293
|
+
check("Dreamspell Mar 1 2028 (post-leap-Feb)",
|
|
1294
|
+
_dreamspell(_date(2028, 3, 1)), "Day 24, Galactic Moon 8/13")
|
|
1295
|
+
|
|
1296
|
+
# ── UTC midnight boundary (PDE F-021) ─────────────────────────
|
|
1297
|
+
# Two stamps one second apart straddling UTC midnight produce
|
|
1298
|
+
# different calendar dates. This is expected behavior.
|
|
1299
|
+
dt_before_midnight = datetime(2026, 3, 3, 23, 59, 59, tzinfo=timezone.utc)
|
|
1300
|
+
dt_after_midnight = datetime(2026, 3, 4, 0, 0, 0, tzinfo=timezone.utc)
|
|
1301
|
+
ts_before = stamp("boundary test before", dt_before_midnight)
|
|
1302
|
+
ts_after = stamp("boundary test after", dt_after_midnight)
|
|
1303
|
+
check("UTC midnight: different dates produce different gregorian",
|
|
1304
|
+
ts_before.gregorian != ts_after.gregorian, True)
|
|
1305
|
+
check("UTC midnight before = March 3", ts_before.gregorian, "March 3, 2026")
|
|
1306
|
+
check("UTC midnight after = March 4", ts_after.gregorian, "March 4, 2026")
|
|
1307
|
+
|
|
1308
|
+
# ── JD range guards ───────────────────────────────────────────
|
|
1309
|
+
try:
|
|
1310
|
+
_jd_from_gregorian(1581, 1, 1)
|
|
1311
|
+
failures.append(" FAIL — Pre-1582 year should raise ValueError")
|
|
1312
|
+
except ValueError:
|
|
1313
|
+
if verbose: print(" ✓ Pre-1582 year correctly rejected")
|
|
1314
|
+
|
|
1315
|
+
try:
|
|
1316
|
+
_jd_from_gregorian(1582, 10, 14)
|
|
1317
|
+
failures.append(" FAIL — 1582-10-14 should raise ValueError")
|
|
1318
|
+
except ValueError:
|
|
1319
|
+
if verbose: print(" ✓ 1582-10-14 pre-reform boundary correctly rejected")
|
|
1320
|
+
|
|
1321
|
+
try:
|
|
1322
|
+
jd_reform = _jd_from_gregorian(1582, 10, 15)
|
|
1323
|
+
if verbose: print(f" ✓ 1582-10-15 accepted: JD {jd_reform}")
|
|
1324
|
+
except ValueError:
|
|
1325
|
+
failures.append(" FAIL — 1582-10-15 should be accepted")
|
|
1326
|
+
|
|
1327
|
+
# ── Unicode normalization ─────────────────────────────────────
|
|
1328
|
+
entry_nfc = "caf\u00e9"
|
|
1329
|
+
entry_nfd = "cafe\u0301"
|
|
1330
|
+
ts_nfc = stamp(entry_nfc, anchor_dt)
|
|
1331
|
+
ts_nfd = stamp(entry_nfd, anchor_dt)
|
|
1332
|
+
check("NFC/NFD normalization: identical seals", ts_nfc.seal, ts_nfd.seal)
|
|
1333
|
+
|
|
1334
|
+
# ── Whitespace normalization ──────────────────────────────────
|
|
1335
|
+
ts_trimmed = stamp("hello", anchor_dt)
|
|
1336
|
+
ts_padded = stamp(" hello ", anchor_dt)
|
|
1337
|
+
check("Whitespace normalization: identical seals", ts_padded.seal, ts_trimmed.seal)
|
|
1338
|
+
|
|
1339
|
+
# ── Version field ─────────────────────────────────────────────
|
|
1340
|
+
ts_anchor = stamp("anchor", anchor_dt)
|
|
1341
|
+
check("Stamp version is FROZEN-4.0", ts_anchor.version, "FROZEN-4.0")
|
|
1342
|
+
|
|
1343
|
+
# ── Seal round-trip ───────────────────────────────────────────
|
|
1344
|
+
test_entry = "Origin trace — Sovereign Trace Protocol FROZEN-4.0."
|
|
1345
|
+
ts = stamp(test_entry, anchor_dt)
|
|
1346
|
+
vr_good = verify(test_entry, ts)
|
|
1347
|
+
vr_alter = verify("altered text", ts)
|
|
1348
|
+
vr_empty = verify("", ts)
|
|
1349
|
+
vr_none = verify(None, ts)
|
|
1350
|
+
check("verify correct entry: valid", vr_good.valid, True)
|
|
1351
|
+
check("verify correct entry: VALID", vr_good.reason, "VALID")
|
|
1352
|
+
check("verify altered: invalid", vr_alter.valid, False)
|
|
1353
|
+
check("verify altered: SEAL_MISMATCH", vr_alter.reason, "SEAL_MISMATCH")
|
|
1354
|
+
check("verify empty: invalid", vr_empty.valid, False)
|
|
1355
|
+
check("verify None: TYPE_ERROR", vr_none.reason, "TYPE_ERROR")
|
|
1356
|
+
check("VerifyResult(True) truthy", bool(vr_good), True)
|
|
1357
|
+
check("VerifyResult(False) falsy", bool(vr_alter), False)
|
|
1358
|
+
|
|
1359
|
+
# ── Hebrew field ──────────────────────────────────────────────
|
|
1360
|
+
check("Stamp Hebrew field", ts.hebrew, "14 Adar 5786")
|
|
1361
|
+
|
|
1362
|
+
# ── check_invariant ───────────────────────────────────────────
|
|
1363
|
+
check("check_invariant on valid stamp", ts.check_invariant(), True)
|
|
1364
|
+
|
|
1365
|
+
# ── __eq__ and __hash__ ───────────────────────────────────────
|
|
1366
|
+
ts_copy = stamp(test_entry, anchor_dt)
|
|
1367
|
+
ts_other = stamp("different entry", anchor_dt)
|
|
1368
|
+
check("Same-input stamps equal", ts == ts_copy, True)
|
|
1369
|
+
check("Same-input stamps share hash", hash(ts) == hash(ts_copy), True)
|
|
1370
|
+
check("Different-entry stamps differ", ts == ts_other, False)
|
|
1371
|
+
|
|
1372
|
+
# ── Immutability ──────────────────────────────────────────────
|
|
1373
|
+
try:
|
|
1374
|
+
ts.gregorian = "tampered"
|
|
1375
|
+
failures.append(" FAIL — SovereignStamp should be immutable")
|
|
1376
|
+
except AttributeError:
|
|
1377
|
+
if verbose: print(" ✓ SovereignStamp immutability enforced")
|
|
1378
|
+
|
|
1379
|
+
# ── SovereignRecord ───────────────────────────────────────────
|
|
1380
|
+
rec = stamp_and_record(test_entry, anchor_dt)
|
|
1381
|
+
check("Record entry matches", rec.entry, test_entry)
|
|
1382
|
+
check("Record verify valid", rec.verify().valid, True)
|
|
1383
|
+
check("Record verify reason", rec.verify().reason, "VALID")
|
|
1384
|
+
|
|
1385
|
+
try:
|
|
1386
|
+
rec.entry = "tampered"
|
|
1387
|
+
failures.append(" FAIL — SovereignRecord should be immutable")
|
|
1388
|
+
except AttributeError:
|
|
1389
|
+
if verbose: print(" ✓ SovereignRecord immutability enforced")
|
|
1390
|
+
|
|
1391
|
+
# ── Serialization round-trip ──────────────────────────────────
|
|
1392
|
+
d2 = to_dict(ts)
|
|
1393
|
+
ts2 = from_dict(d2)
|
|
1394
|
+
check("Round-trip seal", ts2.seal, ts.seal)
|
|
1395
|
+
check("Round-trip version", ts2.version, ts.version)
|
|
1396
|
+
check("Round-trip __eq__", ts2 == ts, True)
|
|
1397
|
+
|
|
1398
|
+
# ── SovereignRecord round-trip ────────────────────────────────
|
|
1399
|
+
rec_dict = rec.to_dict()
|
|
1400
|
+
rec2 = SovereignRecord.from_dict(rec_dict)
|
|
1401
|
+
check("Record round-trip entry", rec2.entry, rec.entry)
|
|
1402
|
+
check("Record round-trip seal", rec2.stamp.seal, rec.stamp.seal)
|
|
1403
|
+
check("Record round-trip verify", rec2.verify().valid, True)
|
|
1404
|
+
|
|
1405
|
+
# ── from_dict validation ──────────────────────────────────────
|
|
1406
|
+
try:
|
|
1407
|
+
from_dict({"gregorian": "x", "hebrew": "y", "dreamspell": "z"})
|
|
1408
|
+
failures.append(" FAIL — from_dict should reject missing fields")
|
|
1409
|
+
except ValueError:
|
|
1410
|
+
if verbose: print(" ✓ from_dict rejects missing fields")
|
|
1411
|
+
|
|
1412
|
+
bad_seal = dict(d2); bad_seal["seal"] = "not-valid"
|
|
1413
|
+
try:
|
|
1414
|
+
from_dict(bad_seal)
|
|
1415
|
+
failures.append(" FAIL — from_dict should reject invalid seal")
|
|
1416
|
+
except ValueError:
|
|
1417
|
+
if verbose: print(" ✓ from_dict rejects invalid seal format")
|
|
1418
|
+
|
|
1419
|
+
bad_ver = dict(d2); bad_ver["version"] = "FROZEN-3.0"
|
|
1420
|
+
try:
|
|
1421
|
+
from_dict(bad_ver)
|
|
1422
|
+
failures.append(" FAIL — from_dict should reject foreign version")
|
|
1423
|
+
except ValueError:
|
|
1424
|
+
if verbose: print(" ✓ from_dict rejects foreign version field")
|
|
1425
|
+
|
|
1426
|
+
# ── stamp_batch ───────────────────────────────────────────────
|
|
1427
|
+
batch = stamp_batch(["first", "second", "third"], anchor_dt)
|
|
1428
|
+
check("Batch length", len(batch), 3)
|
|
1429
|
+
check("Batch shared unix_utc", len({r.stamp.unix_utc for r in batch}), 1)
|
|
1430
|
+
check("Batch record 0 verifies", batch[0].verify().valid, True)
|
|
1431
|
+
check("Batch record 2 verifies", batch[2].verify().valid, True)
|
|
1432
|
+
|
|
1433
|
+
# ── Error handling ────────────────────────────────────────────
|
|
1434
|
+
try:
|
|
1435
|
+
stamp("")
|
|
1436
|
+
failures.append(" FAIL — empty entry should raise ValueError")
|
|
1437
|
+
except ValueError:
|
|
1438
|
+
if verbose: print(" ✓ Empty entry rejected")
|
|
1439
|
+
|
|
1440
|
+
try:
|
|
1441
|
+
stamp(" ")
|
|
1442
|
+
failures.append(" FAIL — whitespace-only entry should raise ValueError")
|
|
1443
|
+
except ValueError:
|
|
1444
|
+
if verbose: print(" ✓ Whitespace-only entry rejected")
|
|
1445
|
+
|
|
1446
|
+
try:
|
|
1447
|
+
stamp("\u200b")
|
|
1448
|
+
failures.append(" FAIL — zero-width space only should raise ValueError")
|
|
1449
|
+
except ValueError:
|
|
1450
|
+
if verbose: print(" ✓ Zero-width space only rejected (invisible char guard)")
|
|
1451
|
+
|
|
1452
|
+
try:
|
|
1453
|
+
stamp("\x00")
|
|
1454
|
+
failures.append(" FAIL — null byte only should raise ValueError")
|
|
1455
|
+
except ValueError:
|
|
1456
|
+
if verbose: print(" ✓ Null byte only rejected (invisible char guard)")
|
|
1457
|
+
|
|
1458
|
+
try:
|
|
1459
|
+
stamp("\u200b\u200c\u200d")
|
|
1460
|
+
failures.append(" FAIL — multiple invisible chars should raise ValueError")
|
|
1461
|
+
except ValueError:
|
|
1462
|
+
if verbose: print(" ✓ Multiple invisible chars rejected (invisible char guard)")
|
|
1463
|
+
|
|
1464
|
+
try:
|
|
1465
|
+
stamp("text", datetime(2026, 3, 3, 15, 0, 0)) # naive
|
|
1466
|
+
failures.append(" FAIL — naive datetime should raise ValueError")
|
|
1467
|
+
except ValueError:
|
|
1468
|
+
if verbose: print(" ✓ Naive datetime rejected")
|
|
1469
|
+
|
|
1470
|
+
# ── FROZEN-1.0 regression ─────────────────────────────────────
|
|
1471
|
+
check("FROZEN-1.0 regression: NOT 15 Adar",
|
|
1472
|
+
ts.hebrew != "15 Adar 5786", True)
|
|
1473
|
+
|
|
1474
|
+
# ── Result (failures to stderr — PDE F-019, F-024) ────────────
|
|
1475
|
+
if verbose:
|
|
1476
|
+
print()
|
|
1477
|
+
if failures:
|
|
1478
|
+
for f in failures:
|
|
1479
|
+
print(f, file=sys.stderr)
|
|
1480
|
+
raise AssertionError(
|
|
1481
|
+
f"{len(failures)} self-test failure(s). "
|
|
1482
|
+
"Do not deploy. Verify algorithms from first principles. "
|
|
1483
|
+
"Create FROZEN-5.0 if correction is needed."
|
|
1484
|
+
)
|
|
1485
|
+
else:
|
|
1486
|
+
if verbose:
|
|
1487
|
+
print("═══ ALL TESTS PASSED ═══")
|
|
1488
|
+
print()
|
|
1489
|
+
print("Anchor stamp (March 3, 2026 — session of origin):")
|
|
1490
|
+
print(display(ts))
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
1494
|
+
# CLI — INTERACTIVE TRACE ENTRY
|
|
1495
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
1496
|
+
|
|
1497
|
+
def _cli(json_mode: bool = False) -> None:
|
|
1498
|
+
"""
|
|
1499
|
+
Interactive sovereign trace entry with live stamp.
|
|
1500
|
+
|
|
1501
|
+
Privacy notice: entry text submitted via stdin, printed to stdout.
|
|
1502
|
+
On shared systems, stdout may be logged. Shell history may record
|
|
1503
|
+
the invocation. Terminal scrollback may retain the JSON record.
|
|
1504
|
+
Entry text is not encrypted at rest.
|
|
1505
|
+
|
|
1506
|
+
Process memory note: entry text exists in process memory from
|
|
1507
|
+
stdin read until process exit. On multi-tenant systems with shared
|
|
1508
|
+
memory visibility, this is an operator-assessed risk.
|
|
1509
|
+
|
|
1510
|
+
JSON mode note: --json emits only machine-readable JSON to stdout.
|
|
1511
|
+
Self-test failure messages go to stderr to prevent stream corruption.
|
|
1512
|
+
(PDE F-019, F-024)
|
|
1513
|
+
|
|
1514
|
+
Calendar note: calendar dates reflect UTC.
|
|
1515
|
+
Computational permanence note: "This seal is computationally
|
|
1516
|
+
tamper-evident under SHA-256" — not absolutely permanent.
|
|
1517
|
+
See INTEGRITY BOUNDARIES section in module docstring. (MF-M-003)
|
|
1518
|
+
"""
|
|
1519
|
+
import sys
|
|
1520
|
+
if not json_mode:
|
|
1521
|
+
print("═══════════════════════════════════════════════════")
|
|
1522
|
+
print(f" SOVEREIGN TRACE PROTOCOL — {_FROZEN_VERSION}")
|
|
1523
|
+
print(" Your trace will be sealed.")
|
|
1524
|
+
print(" The seal is computationally tamper-evident under SHA-256.")
|
|
1525
|
+
print(" Note: calendar dates reflect UTC.")
|
|
1526
|
+
print(" Note: entry will appear in stdout (may be logged).")
|
|
1527
|
+
print("═══════════════════════════════════════════════════\n")
|
|
1528
|
+
|
|
1529
|
+
entry = input("Your trace (present-moment, exact, no narrative arc):\n> ")
|
|
1530
|
+
entry_normalized = unicodedata.normalize("NFC", entry).strip()
|
|
1531
|
+
if not entry_normalized:
|
|
1532
|
+
if not json_mode:
|
|
1533
|
+
print("Empty trace. Nothing sealed.")
|
|
1534
|
+
return
|
|
1535
|
+
|
|
1536
|
+
ts = stamp(entry_normalized)
|
|
1537
|
+
rec = SovereignRecord(entry_normalized, ts)
|
|
1538
|
+
|
|
1539
|
+
if json_mode:
|
|
1540
|
+
print(json.dumps(rec.to_dict(), indent=2, ensure_ascii=False))
|
|
1541
|
+
else:
|
|
1542
|
+
print()
|
|
1543
|
+
print("─── SEALED ───────────────────────────────────────")
|
|
1544
|
+
print(display(ts))
|
|
1545
|
+
print("─── JSON (store this) ────────────────────────────")
|
|
1546
|
+
print(json.dumps(rec.to_dict(), indent=2, ensure_ascii=False))
|
|
1547
|
+
print("──────────────────────────────────────────────────")
|
|
1548
|
+
print("Seal is computationally tamper-evident under SHA-256.")
|
|
1549
|
+
print("Keep the JSON. The ledger append comes next.")
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
if __name__ == "__main__":
|
|
1553
|
+
import sys
|
|
1554
|
+
if "--test" in sys.argv or "-t" in sys.argv:
|
|
1555
|
+
_run_self_test(verbose=True)
|
|
1556
|
+
else:
|
|
1557
|
+
_run_self_test(verbose=False)
|
|
1558
|
+
_cli(json_mode="--json" in sys.argv)
|