golded-ftn-jam 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
8
+ .agent-compose-backup-*/
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 1.2.0 — 2026-10-05
4
+
5
+ - Add offline create/read/append/update/delete sessions, message revisions, record locks and rollback.
6
+ - Preserve raw subfields and maintain JAM indices, CRCs and counters.
7
+ - GoldED coexistence remains disabled pending build-specific integration tests.
8
+
9
+ - Replace controls and MSGID across JHR/JDT placements; preserve omitted structured fields and reject conflicting controls.
10
+
11
+ ## 1.1.0 — Unreleased
12
+
13
+ Add reported archive reading: keep bounded oversized subfields and malformed
14
+ TZUTC metadata, retain distinct PID/FLAGS/TZUTC controls, try configured fallback
15
+ for mislabeled ASCII, and skip failed indexed records. Ambiguous structure stops
16
+ with a report. Strict reading remains the default.
17
+
18
+ ## 1.0.0
19
+
20
+ - Read indexed JAM revision 1 areas with strict binary and decoding checks.
21
+ - Preserve message text, supported subfields, reply links and provenance.
22
+ - Ship typed public exports and independent synthetic fixtures.
23
+ - Report charset conflict offsets against stored subfield bytes.
24
+ - Keep development-only uv sources out of source distributions.
@@ -0,0 +1,22 @@
1
+ # Contributing
2
+
3
+ Use Python 3.12+ and uv with the sibling `golded-ftn` checkout. Run the checks in
4
+ README before proposing a change. Keep runtime dependencies limited to core.
5
+
6
+ Build fixtures from the JAM specification, independently of reader helpers.
7
+ Use synthetic messages; keep private archives out of tests and distributions.
8
+ Verify changed behavior through `JamReader.read`, including failures and offsets.
9
+
10
+ Keep this package focused on JAM reading and editing. Area discovery, databases,
11
+ packing and repair belong outside this package. Generated AGENTS.md comes from agent-compose.toml and the
12
+ local project fragment; edit those sources and preview, build, check.
13
+
14
+ Protect writer behavior through the public create/open/read/append/update/delete
15
+ API and independent raw records. Check omitted patch fields, explicit clearing,
16
+ controls in each supported physical placement, reply structures and unrelated
17
+ message revisions. Use controlled helper processes for lock conflicts and the
18
+ internal I/O seam for write, truncate, flush and rollback failures. Never reopen
19
+ the lock file while its operation lock is held. Preserve existing lastread data.
20
+
21
+ Keep GoldED closed during editing. A matching write lock alone does not prove
22
+ safe concurrent reads or refresh; build interoperability remains deferred.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GoldED.dev contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.5
2
+ Name: golded-ftn-jam
3
+ Version: 1.2.0
4
+ Summary: Strict reader and offline writer for JAM revision 1 message areas
5
+ Project-URL: Repository, https://github.com/golded-dev/golded-ftn-jam-python
6
+ Project-URL: Issues, https://github.com/golded-dev/golded-ftn-jam-python/issues
7
+ Project-URL: Changelog, https://github.com/golded-dev/golded-ftn-jam-python/blob/main/CHANGELOG.md
8
+ Author: GoldED.dev contributors
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: golded-ftn<2,>=1.2.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # golded-ftn-jam
22
+
23
+ Repository: [`golded-ftn-jam-python`](https://github.com/golded-dev/golded-ftn-jam-python).
24
+ The distribution remains `golded-ftn-jam`; imports use `golded_ftn_jam`.
25
+ The source is public on GitHub. This package has not been released on PyPI.
26
+
27
+ Read and edit JAM revision 1 areas through the `golded-ftn` models. Python 3.12+.
28
+ Version 1.2.0 is prepared locally; these writer changes are unreleased.
29
+
30
+ ```sh
31
+ git clone https://github.com/golded-dev/golded-ftn-python.git
32
+ git clone https://github.com/golded-dev/golded-ftn-jam-python.git
33
+ cd golded-ftn-jam-python
34
+ uv sync --locked
35
+ ```
36
+
37
+ ```python
38
+ from pathlib import Path
39
+ from tempfile import TemporaryDirectory
40
+
41
+ from golded_ftn import MessageBaseReader, ReaderOptions
42
+ from golded_ftn_jam import JamReader
43
+
44
+ # A minimal empty area. Real callers pass the area basename, without an extension.
45
+ with TemporaryDirectory() as directory:
46
+ base = Path(directory) / "example"
47
+ header = bytearray(1024)
48
+ header[:4] = b"JAM\0"
49
+ header[20:24] = (1).to_bytes(4, "little")
50
+ base.with_suffix(".JHR").write_bytes(header)
51
+ base.with_suffix(".JDT").write_bytes(b"")
52
+ base.with_suffix(".JDX").write_bytes(b"")
53
+ reader: MessageBaseReader = JamReader()
54
+ messages = list(reader.read(base, ReaderOptions(fallback_charset="CP850")))
55
+ assert messages == []
56
+ ```
57
+
58
+ The basename is exact; `.JHR`, `.JDT` and `.JDX` extensions are matched without
59
+ regard to case. Each must identify one regular file; symlinks and ambiguous
60
+ extension variants are rejected. The standalone reader ignores `.JLR`; editing
61
+ preserves it and creation initializes an empty file.
62
+
63
+ The index determines message numbers and which headers are live. Index holes and
64
+ deleted messages are skipped. Old unindexed headers are ignored. The reader
65
+ validates signatures, revision, record boundaries, offsets, subfield lengths,
66
+ message numbers and reused header offsets. It reads the whole area into memory
67
+ and validates all indexed records before returning a tuple. A malformed later
68
+ record cannot leave a caller with a partial result. Filesystem errors remain
69
+ filesystem errors; damaged data and decoding errors raise `ParserException` with
70
+ source path, byte offset and a chained cause.
71
+
72
+ The standalone `JamReader` requires a stable area with no concurrent writes. It
73
+ does not lock the area or produce a snapshot across the three files. Stop the
74
+ writer or use a consistent copy before reading.
75
+
76
+ Decoding is strict, with core charset detection and CP850 fallback. Header
77
+ FTSKLUDGE declarations are considered before body declarations. Conflicting
78
+ charset declarations and IDs fail. Unknown charset names use the configured
79
+ fallback. Mojibake repair is the caller's choice.
80
+
81
+ `body_text` contains only normalized `.JDT` text. Header control and routing
82
+ subfields go into `control_lines`, ahead of body metadata. Repeated controls and
83
+ routing entries keep their order within the core model's kludges, seen_by and
84
+ path sequences. Unknown subfields and nonzero HiID fields are bounds-checked
85
+ and skipped. The first originating/destination address is preserved as decoded
86
+ text, including node 0, point and domain. Other single-value subfields must agree
87
+ when repeated. Header MSGID/REPLYID take precedence over matching body IDs;
88
+ missing MSGID uses the core synthetic ID over decoded, normalized fields.
89
+
90
+ Dates are naive `1970-01-01 + DateWritten seconds`, independent of the machine's
91
+ timezone; zero means `None`. TZUTC is retained as a control line. Raw attributes,
92
+ all three reply links and `.JHR` provenance are retained; zero reply links become
93
+ `None`. Unknown area metadata stays `None`.
94
+
95
+ Compressed, encrypted and escaped active messages are unsupported. Area
96
+ discovery, databases, packing and repair are outside this package.
97
+
98
+ ## Development
99
+
100
+ ```sh
101
+ uv sync --locked --python 3.14
102
+ uv run pytest
103
+ uv run ruff check .
104
+ uv run ruff format --check .
105
+ uv run mypy
106
+ uv run python -m mypy.stubtest golded_ftn_jam
107
+ uv build
108
+ uv run twine check dist/*
109
+ uv run python scripts/verify_distribution.py
110
+ ```
111
+
112
+ uv uses the sibling `../golded-ftn-python` checkout during development. Published metadata
113
+ contains only `golded-ftn>=1.2.0,<2`. The sdist build hook removes `tool.uv.sources`
114
+ from the packed `pyproject.toml`; the development lock is also excluded.
115
+ Unpacked sources use the public dependency constraint.
116
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [release checks](docs/release.md).
117
+
118
+ ## Format references and credits
119
+
120
+ - [JAM-001 revision 1](https://raw.githubusercontent.com/Mithgol/node-fidonet-jam/master/JAM.txt)
121
+ - [GoldED JAM structures](https://github.com/golded-dev/golded-linux-macos/blob/main/goldlib/gmb3/gmojamm.h)
122
+ - The sibling `laravel-ftn-jam/src/JamReader.php` and its reader tests informed
123
+ model mapping. Its sequential scan and permissive truncation are not used.
124
+
125
+ JAM(mbp) - Copyright 1993 Joaquim Homrighausen, Andrew Milner, Mats Birch, Mats Wallin. ALL RIGHTS RESERVED.
126
+
127
+ The package code is MIT licensed; the JAM specification has its own terms.
128
+
129
+ ## Archive mode
130
+
131
+ Strict reading remains the default. Archive mode requires a report callback:
132
+
133
+ ```python
134
+ from golded_ftn import ReaderIssue, ReaderOptions
135
+
136
+ issues: list[ReaderIssue] = []
137
+ options = ReaderOptions(archive_mode=True, on_issue=issues.append)
138
+ # Pass options to JamReader().read(source, options).
139
+ ```
140
+
141
+ Issues carry `recovered`, `skipped` or `stopped`, the actual filename, record
142
+ identity and physical offset. Their detail contains no message contents. A stop
143
+ means the traversal is incomplete; a validated prefix may still be returned.
144
+ Multiple issues can describe one record, including recovery followed by a skip.
145
+ Filesystem errors and callback exceptions propagate. Files must remain stable.
146
+
147
+ Bounded fields exceeding JAM's specification limits are retained and reported.
148
+ Malformed TZUTC metadata is retained without interpretation. Distinct PID, FLAGS
149
+ and TZUTC subfields stay in source order. Conflicting names, subjects, IDs or
150
+ charset declarations are skipped rather than guessed. Failed records are skipped
151
+ using the next fixed index slot; reused header offsets stop traversal.
152
+
153
+ If declared ASCII cannot decode a payload, the configured fallback is tried
154
+ strictly and reported. The original charset control stays unchanged. Other
155
+ decoding failures are skipped; there is no lossy decoding or mojibake repair.
156
+
157
+ ## Writing
158
+
159
+ `JamWriter.create(base)` creates `.JHR`, `.JDT`, `.JDX` and an empty `.JLR`.
160
+ Existing files are refused. The initial message number is 1.
161
+
162
+ ```python
163
+ from golded_ftn import MessagePatch, OutgoingMessage
164
+ from golded_ftn_jam import JamWriter
165
+
166
+ writer = JamWriter()
167
+ writer.create("new-area")
168
+ with writer.open("new-area") as session:
169
+ result = session.append(
170
+ OutgoingMessage(
171
+ from_name="Alice",
172
+ to_name="Bob",
173
+ subject="Hello",
174
+ body_text="Hello Bob",
175
+ )
176
+ )
177
+ result = session.update(
178
+ result.identity, MessagePatch(subject="Changed"), result.revision
179
+ )
180
+ session.delete(result.identity, result.revision)
181
+ ```
182
+
183
+ Every operation rereads and validates the index, referenced headers, text ranges
184
+ and active-message count under the byte-0 `.JHR` record lock. A session read returns
185
+ `SessionMessage` with a raw-byte SHA-256 revision. Changes to other messages do not
186
+ invalidate it. The revision includes identity, physical index/header/text locations
187
+ and SHA-256 over raw header, subfield and text bytes. An omitted patch field
188
+ retains its raw metadata; explicit `None` clears optional metadata. Names, subject, text and attributes reject `None`.
189
+
190
+ `control_lines` replaces general controls in both header subfields and inline
191
+ text. MSGID, address controls and routing retain their separate fields when
192
+ omitted; conflicting explicit controls are rejected. `external_id` and routing
193
+ patches remove obsolete inline copies as well as replacing their header metadata.
194
+ Body-only changes retain existing inline controls and routing. Unknown subfields
195
+ and nonzero HiID values survive unless their supported field is explicitly changed.
196
+
197
+ Content updates append a header, subfields and text, then redirect the index.
198
+ Header-only changes retain the text bytes and record position. Unknown subfields,
199
+ reserved words, timestamps and reply links remain intact. Delete marks the header,
200
+ sets the recipient index CRC to FFFFFFFF, and decrements the active count. Message
201
+ numbers and lastread data remain unchanged.
202
+
203
+ CP850 is the default. Encoding is strict; conflicting charset declarations,
204
+ truncation, unsupported compression flags and arbitrary reply lists are refused.
205
+ No MSGID, routing or duplicate detection is generated. Full-file snapshots support
206
+ in-place rollback during ordinary I/O failures; rollback failure poisons the session.
207
+ This provides no process-kill or power-loss transaction guarantee.
208
+
209
+ GoldED coexistence is disabled on every platform (`concurrent=True` is refused).
210
+ macOS/Linux use POSIX record locking. Core provides Windows offline record locks
211
+ and I/O, but this checkout has only been tested on macOS. Windows and Linux
212
+ execution and GoldED interoperability remain unverified. Keep GoldED closed and
213
+ avoid direct file access while these sessions operate. GoldED builds and
214
+ integration tests are deferred.
215
+
216
+ ### Original-source evidence
217
+
218
+ The reference checkout is `golded-open-source`, commit
219
+ `600266252b73174ff5116cee697ef9a97aeb1859`. It contains
220
+ `goldlib/gmb3/gmojamm.h` (`JamHdrInfo`,
221
+ `JamHdr`, `JamIndex`, subfield IDs), `gmojamm2.cpp` (`open_area` initialization,
222
+ `scan` indexing), and `gmojamm4.cpp` (`lock`, `unlock`, `save_message`).
223
+ `lock` takes byte 0 length 1 in `.JHR`; `save_message` maintains recipient/MSGID/
224
+ REPLY CRCs, active count and modification counter. Its CRC seed and missing final
225
+ complement are confirmed by `goldlib/gall/gcrcs32.cpp::strCrc32`.
226
+ These source checks establish layout and write semantics. They do not establish
227
+ that a running GoldED reader refreshes safely after external changes.
@@ -0,0 +1,207 @@
1
+ # golded-ftn-jam
2
+
3
+ Repository: [`golded-ftn-jam-python`](https://github.com/golded-dev/golded-ftn-jam-python).
4
+ The distribution remains `golded-ftn-jam`; imports use `golded_ftn_jam`.
5
+ The source is public on GitHub. This package has not been released on PyPI.
6
+
7
+ Read and edit JAM revision 1 areas through the `golded-ftn` models. Python 3.12+.
8
+ Version 1.2.0 is prepared locally; these writer changes are unreleased.
9
+
10
+ ```sh
11
+ git clone https://github.com/golded-dev/golded-ftn-python.git
12
+ git clone https://github.com/golded-dev/golded-ftn-jam-python.git
13
+ cd golded-ftn-jam-python
14
+ uv sync --locked
15
+ ```
16
+
17
+ ```python
18
+ from pathlib import Path
19
+ from tempfile import TemporaryDirectory
20
+
21
+ from golded_ftn import MessageBaseReader, ReaderOptions
22
+ from golded_ftn_jam import JamReader
23
+
24
+ # A minimal empty area. Real callers pass the area basename, without an extension.
25
+ with TemporaryDirectory() as directory:
26
+ base = Path(directory) / "example"
27
+ header = bytearray(1024)
28
+ header[:4] = b"JAM\0"
29
+ header[20:24] = (1).to_bytes(4, "little")
30
+ base.with_suffix(".JHR").write_bytes(header)
31
+ base.with_suffix(".JDT").write_bytes(b"")
32
+ base.with_suffix(".JDX").write_bytes(b"")
33
+ reader: MessageBaseReader = JamReader()
34
+ messages = list(reader.read(base, ReaderOptions(fallback_charset="CP850")))
35
+ assert messages == []
36
+ ```
37
+
38
+ The basename is exact; `.JHR`, `.JDT` and `.JDX` extensions are matched without
39
+ regard to case. Each must identify one regular file; symlinks and ambiguous
40
+ extension variants are rejected. The standalone reader ignores `.JLR`; editing
41
+ preserves it and creation initializes an empty file.
42
+
43
+ The index determines message numbers and which headers are live. Index holes and
44
+ deleted messages are skipped. Old unindexed headers are ignored. The reader
45
+ validates signatures, revision, record boundaries, offsets, subfield lengths,
46
+ message numbers and reused header offsets. It reads the whole area into memory
47
+ and validates all indexed records before returning a tuple. A malformed later
48
+ record cannot leave a caller with a partial result. Filesystem errors remain
49
+ filesystem errors; damaged data and decoding errors raise `ParserException` with
50
+ source path, byte offset and a chained cause.
51
+
52
+ The standalone `JamReader` requires a stable area with no concurrent writes. It
53
+ does not lock the area or produce a snapshot across the three files. Stop the
54
+ writer or use a consistent copy before reading.
55
+
56
+ Decoding is strict, with core charset detection and CP850 fallback. Header
57
+ FTSKLUDGE declarations are considered before body declarations. Conflicting
58
+ charset declarations and IDs fail. Unknown charset names use the configured
59
+ fallback. Mojibake repair is the caller's choice.
60
+
61
+ `body_text` contains only normalized `.JDT` text. Header control and routing
62
+ subfields go into `control_lines`, ahead of body metadata. Repeated controls and
63
+ routing entries keep their order within the core model's kludges, seen_by and
64
+ path sequences. Unknown subfields and nonzero HiID fields are bounds-checked
65
+ and skipped. The first originating/destination address is preserved as decoded
66
+ text, including node 0, point and domain. Other single-value subfields must agree
67
+ when repeated. Header MSGID/REPLYID take precedence over matching body IDs;
68
+ missing MSGID uses the core synthetic ID over decoded, normalized fields.
69
+
70
+ Dates are naive `1970-01-01 + DateWritten seconds`, independent of the machine's
71
+ timezone; zero means `None`. TZUTC is retained as a control line. Raw attributes,
72
+ all three reply links and `.JHR` provenance are retained; zero reply links become
73
+ `None`. Unknown area metadata stays `None`.
74
+
75
+ Compressed, encrypted and escaped active messages are unsupported. Area
76
+ discovery, databases, packing and repair are outside this package.
77
+
78
+ ## Development
79
+
80
+ ```sh
81
+ uv sync --locked --python 3.14
82
+ uv run pytest
83
+ uv run ruff check .
84
+ uv run ruff format --check .
85
+ uv run mypy
86
+ uv run python -m mypy.stubtest golded_ftn_jam
87
+ uv build
88
+ uv run twine check dist/*
89
+ uv run python scripts/verify_distribution.py
90
+ ```
91
+
92
+ uv uses the sibling `../golded-ftn-python` checkout during development. Published metadata
93
+ contains only `golded-ftn>=1.2.0,<2`. The sdist build hook removes `tool.uv.sources`
94
+ from the packed `pyproject.toml`; the development lock is also excluded.
95
+ Unpacked sources use the public dependency constraint.
96
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [release checks](docs/release.md).
97
+
98
+ ## Format references and credits
99
+
100
+ - [JAM-001 revision 1](https://raw.githubusercontent.com/Mithgol/node-fidonet-jam/master/JAM.txt)
101
+ - [GoldED JAM structures](https://github.com/golded-dev/golded-linux-macos/blob/main/goldlib/gmb3/gmojamm.h)
102
+ - The sibling `laravel-ftn-jam/src/JamReader.php` and its reader tests informed
103
+ model mapping. Its sequential scan and permissive truncation are not used.
104
+
105
+ JAM(mbp) - Copyright 1993 Joaquim Homrighausen, Andrew Milner, Mats Birch, Mats Wallin. ALL RIGHTS RESERVED.
106
+
107
+ The package code is MIT licensed; the JAM specification has its own terms.
108
+
109
+ ## Archive mode
110
+
111
+ Strict reading remains the default. Archive mode requires a report callback:
112
+
113
+ ```python
114
+ from golded_ftn import ReaderIssue, ReaderOptions
115
+
116
+ issues: list[ReaderIssue] = []
117
+ options = ReaderOptions(archive_mode=True, on_issue=issues.append)
118
+ # Pass options to JamReader().read(source, options).
119
+ ```
120
+
121
+ Issues carry `recovered`, `skipped` or `stopped`, the actual filename, record
122
+ identity and physical offset. Their detail contains no message contents. A stop
123
+ means the traversal is incomplete; a validated prefix may still be returned.
124
+ Multiple issues can describe one record, including recovery followed by a skip.
125
+ Filesystem errors and callback exceptions propagate. Files must remain stable.
126
+
127
+ Bounded fields exceeding JAM's specification limits are retained and reported.
128
+ Malformed TZUTC metadata is retained without interpretation. Distinct PID, FLAGS
129
+ and TZUTC subfields stay in source order. Conflicting names, subjects, IDs or
130
+ charset declarations are skipped rather than guessed. Failed records are skipped
131
+ using the next fixed index slot; reused header offsets stop traversal.
132
+
133
+ If declared ASCII cannot decode a payload, the configured fallback is tried
134
+ strictly and reported. The original charset control stays unchanged. Other
135
+ decoding failures are skipped; there is no lossy decoding or mojibake repair.
136
+
137
+ ## Writing
138
+
139
+ `JamWriter.create(base)` creates `.JHR`, `.JDT`, `.JDX` and an empty `.JLR`.
140
+ Existing files are refused. The initial message number is 1.
141
+
142
+ ```python
143
+ from golded_ftn import MessagePatch, OutgoingMessage
144
+ from golded_ftn_jam import JamWriter
145
+
146
+ writer = JamWriter()
147
+ writer.create("new-area")
148
+ with writer.open("new-area") as session:
149
+ result = session.append(
150
+ OutgoingMessage(
151
+ from_name="Alice",
152
+ to_name="Bob",
153
+ subject="Hello",
154
+ body_text="Hello Bob",
155
+ )
156
+ )
157
+ result = session.update(
158
+ result.identity, MessagePatch(subject="Changed"), result.revision
159
+ )
160
+ session.delete(result.identity, result.revision)
161
+ ```
162
+
163
+ Every operation rereads and validates the index, referenced headers, text ranges
164
+ and active-message count under the byte-0 `.JHR` record lock. A session read returns
165
+ `SessionMessage` with a raw-byte SHA-256 revision. Changes to other messages do not
166
+ invalidate it. The revision includes identity, physical index/header/text locations
167
+ and SHA-256 over raw header, subfield and text bytes. An omitted patch field
168
+ retains its raw metadata; explicit `None` clears optional metadata. Names, subject, text and attributes reject `None`.
169
+
170
+ `control_lines` replaces general controls in both header subfields and inline
171
+ text. MSGID, address controls and routing retain their separate fields when
172
+ omitted; conflicting explicit controls are rejected. `external_id` and routing
173
+ patches remove obsolete inline copies as well as replacing their header metadata.
174
+ Body-only changes retain existing inline controls and routing. Unknown subfields
175
+ and nonzero HiID values survive unless their supported field is explicitly changed.
176
+
177
+ Content updates append a header, subfields and text, then redirect the index.
178
+ Header-only changes retain the text bytes and record position. Unknown subfields,
179
+ reserved words, timestamps and reply links remain intact. Delete marks the header,
180
+ sets the recipient index CRC to FFFFFFFF, and decrements the active count. Message
181
+ numbers and lastread data remain unchanged.
182
+
183
+ CP850 is the default. Encoding is strict; conflicting charset declarations,
184
+ truncation, unsupported compression flags and arbitrary reply lists are refused.
185
+ No MSGID, routing or duplicate detection is generated. Full-file snapshots support
186
+ in-place rollback during ordinary I/O failures; rollback failure poisons the session.
187
+ This provides no process-kill or power-loss transaction guarantee.
188
+
189
+ GoldED coexistence is disabled on every platform (`concurrent=True` is refused).
190
+ macOS/Linux use POSIX record locking. Core provides Windows offline record locks
191
+ and I/O, but this checkout has only been tested on macOS. Windows and Linux
192
+ execution and GoldED interoperability remain unverified. Keep GoldED closed and
193
+ avoid direct file access while these sessions operate. GoldED builds and
194
+ integration tests are deferred.
195
+
196
+ ### Original-source evidence
197
+
198
+ The reference checkout is `golded-open-source`, commit
199
+ `600266252b73174ff5116cee697ef9a97aeb1859`. It contains
200
+ `goldlib/gmb3/gmojamm.h` (`JamHdrInfo`,
201
+ `JamHdr`, `JamIndex`, subfield IDs), `gmojamm2.cpp` (`open_area` initialization,
202
+ `scan` indexing), and `gmojamm4.cpp` (`lock`, `unlock`, `save_message`).
203
+ `lock` takes byte 0 length 1 in `.JHR`; `save_message` maintains recipient/MSGID/
204
+ REPLY CRCs, active count and modification counter. Its CRC seed and missing final
205
+ complement are confirmed by `goldlib/gall/gcrcs32.cpp::strCrc32`.
206
+ These source checks establish layout and write semantics. They do not establish
207
+ that a running GoldED reader refreshes safely after external changes.
@@ -0,0 +1,14 @@
1
+ # Security
2
+
3
+ Treat message archives as untrusted input. Readers validate record boundaries,
4
+ offsets and decoding, but archives must remain stable while being read.
5
+
6
+ Report vulnerabilities privately through [GitHub security advisories](https://github.com/golded-dev/golded-ftn-jam-python/security/advisories/new).
7
+ Do not put private archives, message contents or credentials in public issues.
8
+ Security review targets the current 1.2.x development line. The writer changes
9
+ are prepared locally and have not been published on PyPI.
10
+
11
+ Writer sessions validate the base under their operation lock and roll back
12
+ handled I/O failures. Keep GoldED closed; direct file access bypasses this lock.
13
+ No recovery guarantee covers process termination or power loss. Archive mode is
14
+ for reading damaged records, never for editing them.
@@ -0,0 +1,86 @@
1
+ # Release checks
2
+
3
+ Run every README development check from a clean checkout with sibling core.
4
+ `scripts/verify_distribution.py` inspects wheel/sdist contents and metadata,
5
+ rebuilds the wheel from sdist, compares archive bytes, builds a local core wheel,
6
+ and tests both JAM wheels in separate environments outside the repositories.
7
+ It also checks runtime imports, README execution, strict test typing and stubtest.
8
+
9
+ CI runs full checks on Linux Python 3.12, 3.13 and 3.14, with pytest on Windows
10
+ and macOS Python 3.14. Local checks do not establish that CI passed.
11
+
12
+ Inspect CHANGELOG and license notices. Commit only when requested. A local
13
+ 1.2.0 build does not publish a release. Remote creation, push, tagging and package
14
+ publication each require explicit authorization.
15
+
16
+ For writer changes, check create/read/append/update/delete through public sessions,
17
+ independent binary fixtures, stale revisions, controlled lock contention and
18
+ handled write/flush failures. Run examples against the installed wheel. Record
19
+ platform results separately: local macOS tests do not establish Linux or Windows
20
+ execution, or GoldED compatibility. Keep `concurrent=True` disabled until both
21
+ competing writes and GoldED read/cache/refresh checks pass against a pinned build.
22
+ The current GoldED build and integration tests are deferred.
23
+
24
+ ## Local 1.2.0 release candidate — 2026-10-05
25
+
26
+ Verified on macOS 27.0 arm64 with CPython 3.14.6. The checkout contains
27
+ uncommitted changes; these checks cover the working tree, not a tagged release.
28
+
29
+ - `uv sync --locked`: passed.
30
+ - Ruff lint and format checks, strict mypy: passed.
31
+ - `uv run pytest -q`: 127 passed, 1 skipped.
32
+ - `uv build` and `uv run twine check dist/*`: passed for wheel and sdist.
33
+ - `scripts/verify_distribution.py`: passed metadata and package-content checks,
34
+ byte comparison against a wheel rebuilt from sdist, isolated installed-package
35
+ tests and strict consumer typing. Format packages also pass installed stubtest.
36
+ - `agent-compose check`: passed using the local mostly-agents tool.
37
+ - `git diff --check`: passed (whitespace only).
38
+
39
+ GitHub's API reports the repository as public and private vulnerability reporting
40
+ as enabled. PyPI's project JSON endpoint returned HTTP 404 on this date. No package
41
+ was uploaded. Local checks do not establish Linux/Windows or remote CI results.
42
+ GoldED build interoperability remains deferred; concurrent use stays disabled.
43
+
44
+ Release order: publish `golded-ftn==1.2.0` first, then the four format packages.
45
+ Each format package requires `golded-ftn>=1.2.0,<2`. Before publication, commit
46
+ and review CI for these exact sources, create the intended release tag, and
47
+ confirm the package-index destination and publishing authority. After core is
48
+ available, verify resolution from that index without local uv sources. Publish
49
+ only the reviewed archives, then check public installation and update the shared
50
+ guide's commit pins to the released commits. These remote actions are not part
51
+ of this local preparation.
52
+
53
+ Archive checksums are recorded separately in `RELEASE-SHA256.txt` at the
54
+ repository root, outside the archives, after the final build.
55
+
56
+ ## PyPI Trusted Publishing
57
+
58
+ Create a PyPI account, verify its email and configure two-factor authentication.
59
+ For a first publication, add a pending publisher at
60
+ <https://pypi.org/manage/account/publishing/> with these exact fields:
61
+
62
+ - PyPI project: `golded-ftn-jam`
63
+ - GitHub owner: `golded-dev`
64
+ - GitHub repository: `golded-ftn-jam-python`
65
+ - Workflow filename: `publish.yml`
66
+ - Environment: `pypi`
67
+
68
+ See [PyPI's pending-publisher instructions](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/).
69
+ No API token or password is required by the workflow. The account setup is a
70
+ manual prerequisite; a GitHub release does not create the PyPI project.
71
+
72
+ After this tag's CI succeeds and the GitHub release contains both archives and
73
+ `RELEASE-SHA256.txt`, run:
74
+
75
+ ```sh
76
+ gh workflow run publish.yml --repo golded-dev/golded-ftn-jam-python -f tag=v1.2.0
77
+ ```
78
+
79
+ The workflow verifies SHA-256 and uploads those exact release assets. Publish
80
+ core first, verify installation from PyPI, then dispatch the format workflows.
81
+ Confirm the workflow result, PyPI version and hashes, and installation in a fresh
82
+ environment. Do not store publishing credentials in this repository.
83
+
84
+ The deterministic process-lock helper uses native `msvcrt` byte locks on
85
+ Windows and `fcntl` record locks on POSIX. Both coordinate through an explicit
86
+ ready signal and release request; the test does not depend on random timing.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.26"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "golded-ftn-jam"
7
+ version = "1.2.0"
8
+ description = "Strict reader and offline writer for JAM revision 1 message areas"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "GoldED.dev contributors"}]
14
+ dependencies = ["golded-ftn>=1.2.0,<2"]
15
+ classifiers = ["Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Typing :: Typed", "Operating System :: OS Independent"]
16
+
17
+ [project.urls]
18
+ Repository = "https://github.com/golded-dev/golded-ftn-jam-python"
19
+ Issues = "https://github.com/golded-dev/golded-ftn-jam-python/issues"
20
+ Changelog = "https://github.com/golded-dev/golded-ftn-jam-python/blob/main/CHANGELOG.md"
21
+
22
+ [dependency-groups]
23
+ dev = ["pytest>=8", "ruff>=0.11", "mypy>=1.15", "build>=1.2", "twine>=6", "hatchling>=1.26"]
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["src/golded_ftn_jam"]
27
+
28
+ [tool.hatch.build.targets.sdist]
29
+ include = ["src", "tests", "scripts", "docs", "README.md", "CHANGELOG.md", "CONTRIBUTING.md", "SECURITY.md", "LICENSE", "pyproject.toml"]
30
+
31
+ [tool.hatch.build.targets.sdist.hooks.custom]
32
+ path = "scripts/sdist_hook.py"
33
+
34
+ [tool.ruff]
35
+ target-version = "py312"
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E", "F", "I", "UP", "B"]
39
+
40
+ [tool.mypy]
41
+ strict = true
42
+ files = ["src", "tests", "scripts"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+