golded-ftn-tools 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. golded_ftn_tools-1.0.1/.github/workflows/ci.yml +47 -0
  2. golded_ftn_tools-1.0.1/.github/workflows/publish.yml +32 -0
  3. golded_ftn_tools-1.0.1/.gitignore +6 -0
  4. golded_ftn_tools-1.0.1/LICENSE +21 -0
  5. golded_ftn_tools-1.0.1/PKG-INFO +81 -0
  6. golded_ftn_tools-1.0.1/README.md +67 -0
  7. golded_ftn_tools-1.0.1/docs/JSON.md +152 -0
  8. golded_ftn_tools-1.0.1/docs/PLAN.md +231 -0
  9. golded_ftn_tools-1.0.1/docs/SPEC.md +195 -0
  10. golded_ftn_tools-1.0.1/docs/VERIFICATION.md +100 -0
  11. golded_ftn_tools-1.0.1/examples/message-squish.json +17 -0
  12. golded_ftn_tools-1.0.1/examples/message.json +12 -0
  13. golded_ftn_tools-1.0.1/examples/messages.jsonl +2 -0
  14. golded_ftn_tools-1.0.1/pyproject.toml +50 -0
  15. golded_ftn_tools-1.0.1/scripts/build_reading_pages.py +700 -0
  16. golded_ftn_tools-1.0.1/scripts/check_reading_pages.py +88 -0
  17. golded_ftn_tools-1.0.1/scripts/sdist_hook.py +33 -0
  18. golded_ftn_tools-1.0.1/scripts/verify_distribution.py +155 -0
  19. golded_ftn_tools-1.0.1/src/golded_ftn_tools/__init__.py +17 -0
  20. golded_ftn_tools-1.0.1/src/golded_ftn_tools/__main__.py +4 -0
  21. golded_ftn_tools-1.0.1/src/golded_ftn_tools/adapters.py +89 -0
  22. golded_ftn_tools-1.0.1/src/golded_ftn_tools/api.py +394 -0
  23. golded_ftn_tools-1.0.1/src/golded_ftn_tools/catalog.py +84 -0
  24. golded_ftn_tools-1.0.1/src/golded_ftn_tools/cli.py +300 -0
  25. golded_ftn_tools-1.0.1/src/golded_ftn_tools/errors.py +91 -0
  26. golded_ftn_tools-1.0.1/src/golded_ftn_tools/json_contract.py +196 -0
  27. golded_ftn_tools-1.0.1/src/golded_ftn_tools/py.typed +0 -0
  28. golded_ftn_tools-1.0.1/tests/test_api.py +75 -0
  29. golded_ftn_tools-1.0.1/tests/test_cli.py +295 -0
  30. golded_ftn_tools-1.0.1/tests/test_failures.py +156 -0
@@ -0,0 +1,47 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ checks:
5
+ strategy:
6
+ matrix:
7
+ # Windows is unsupported for now. JAM read --revision reports an
8
+ # invalid base, and a closed stdout pipe exits 120.
9
+ os:
10
+ - ubuntu-latest
11
+ - macos-latest
12
+ # - windows-latest
13
+ python: ['3.12', '3.14']
14
+ runs-on: ${{ matrix.os }}
15
+ defaults:
16
+ run:
17
+ shell: bash
18
+ working-directory: golded-ftn-tools
19
+ steps:
20
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
21
+ with:
22
+ path: golded-ftn-tools
23
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
24
+ with:
25
+ repository: golded-dev/golded-ftn-python
26
+ path: golded-ftn-python
27
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
28
+ with:
29
+ repository: golded-dev/golded-ftn-msg-python
30
+ path: golded-ftn-msg-python
31
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
32
+ with:
33
+ repository: golded-dev/golded-ftn-jam-python
34
+ path: golded-ftn-jam-python
35
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
36
+ with:
37
+ repository: golded-dev/golded-ftn-squish-python
38
+ path: golded-ftn-squish-python
39
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
40
+ with:
41
+ repository: golded-dev/golded-ftn-hudson-python
42
+ path: golded-ftn-hudson-python
43
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
44
+ - run: uv sync --locked --python ${{ matrix.python }}
45
+ - run: uv run ruff check . && uv run ruff format --check . && uv run mypy
46
+ - run: uv run pytest -q
47
+ - run: uv build && uv run twine check dist/* && uv run python scripts/verify_distribution.py
@@ -0,0 +1,32 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ tag:
6
+ description: Reviewed GitHub release tag
7
+ required: true
8
+ default: v1.0.1
9
+ type: string
10
+ permissions:
11
+ contents: read
12
+ concurrency:
13
+ group: pypi-${{ inputs.tag }}
14
+ cancel-in-progress: false
15
+ jobs:
16
+ publish:
17
+ runs-on: ubuntu-latest
18
+ environment: pypi
19
+ permissions:
20
+ contents: read
21
+ id-token: write
22
+ steps:
23
+ - name: Download reviewed release archives
24
+ env:
25
+ GH_TOKEN: ${{ github.token }}
26
+ RELEASE_TAG: ${{ inputs.tag }}
27
+ run: |
28
+ gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern RELEASE-SHA256.txt
29
+ gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern '*.whl' --pattern '*.tar.gz' --dir dist
30
+ sha256sum --check RELEASE-SHA256.txt
31
+ - name: Publish to PyPI with Trusted Publishing
32
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ dist/
@@ -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,81 @@
1
+ Metadata-Version: 2.5
2
+ Name: golded-ftn-tools
3
+ Version: 1.0.1
4
+ Summary: FTN message bases and text filters from the command line
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: golded-ftn-hudson<2,>=1.2.0
9
+ Requires-Dist: golded-ftn-jam<2,>=1.2.0
10
+ Requires-Dist: golded-ftn-msg<2,>=1.3.0
11
+ Requires-Dist: golded-ftn-squish<2,>=1.2.0
12
+ Requires-Dist: golded-ftn<2,>=1.2.1
13
+ Description-Content-Type: text/markdown
14
+
15
+ # FTNT💥
16
+
17
+ Six command-line tools for FTN message bases, JSON fixtures and text pipes.
18
+ Python 3.12+. MIT licensed.
19
+
20
+ **Status: implemented locally, not released.** Development uses five sibling
21
+ Python repositories. Opus writing requires the local `golded-ftn-msg` 1.3.0
22
+ extension; a public installation must wait for that dependency release.
23
+
24
+ ```sh
25
+ uv sync --locked
26
+ uv run ftnt create ./fixtures/general --format jam
27
+ uv run ftnt write ./fixtures/general --format jam < examples/message.json
28
+ uv run ftnt export ./fixtures/general --format jam |
29
+ jq -c 'select(.message.subject | contains("fixture"))'
30
+ ```
31
+
32
+ `create`, `write`, `read` and `export` use an explicit `--format`:
33
+ `msg` (FTSC), `opus`, `jam`, `squish` or `hudson`. Hudson write/read requires
34
+ `--board 1..200`. Bases must be offline: close GoldED and other users first.
35
+ The format packages own validation, encoding, locks and rollback.
36
+
37
+ `write` accepts one JSON object or a JSONL batch with `--jsonl`. Structural
38
+ validation finishes before opening a writer. Each append commits separately;
39
+ a later failure keeps earlier appends. A broken pipe can hide the receipt of
40
+ an already committed message. Rerunning may create duplicates.
41
+
42
+ `read --body` emits the reader's `body_text` exactly, including any controls
43
+ present there, without adding a newline. `read --revision` adds the writer's
44
+ identity and revision. `export --archive` reports reader issues as JSONL on
45
+ stderr and exits 7 if any occur. Export is not a byte-identical backup.
46
+
47
+ ```sh
48
+ uv run ftnt decode --charset IBMPC < old-text.txt
49
+ uv run ftnt repair --json < utf8-text.txt
50
+ ```
51
+
52
+ Both filters read the whole input into memory. Decode uses the core charset
53
+ aliases, strict decoding and preserves line endings and trailing nulls.
54
+ Repair uses the core heuristic explicitly; import/export never repair text.
55
+
56
+ The public manual is the [front page](index.html) and one page per command:
57
+ [create](create.html), [write](write.html), [read](read.html),
58
+ [export](export.html), [decode](decode.html), [repair](repair.html),
59
+ [heads](heads.html) and [catalog](catalog.html).
60
+ Examples are in `examples/`. Maintainer notes stay in `docs/`.
61
+ The pages share [DESIGN.md](DESIGN.md).
62
+
63
+ Checks:
64
+
65
+ ```sh
66
+ uv run pytest -q
67
+ uv run ruff check .
68
+ uv run ruff format --check .
69
+ uv run mypy
70
+ uv build
71
+ uv run twine check dist/*
72
+ uv run python scripts/verify_distribution.py
73
+ uv run scripts/build_reading_pages.py
74
+ ```
75
+
76
+ Distribution checks build dependency wheels from sibling checkouts and run both
77
+ CLI wheels in fresh environments outside the checkout. They do not establish
78
+ public-index dependency resolution. The sdist strips checkout-only uv sources.
79
+ The CI matrix targets Linux and macOS with Python 3.12 and 3.14. Windows is
80
+ unsupported for now. GoldED Opus runtime interoperability remains unverified.
81
+ There is no configured remote, published CLI package or Pages deployment.
@@ -0,0 +1,67 @@
1
+ # FTNT💥
2
+
3
+ Six command-line tools for FTN message bases, JSON fixtures and text pipes.
4
+ Python 3.12+. MIT licensed.
5
+
6
+ **Status: implemented locally, not released.** Development uses five sibling
7
+ Python repositories. Opus writing requires the local `golded-ftn-msg` 1.3.0
8
+ extension; a public installation must wait for that dependency release.
9
+
10
+ ```sh
11
+ uv sync --locked
12
+ uv run ftnt create ./fixtures/general --format jam
13
+ uv run ftnt write ./fixtures/general --format jam < examples/message.json
14
+ uv run ftnt export ./fixtures/general --format jam |
15
+ jq -c 'select(.message.subject | contains("fixture"))'
16
+ ```
17
+
18
+ `create`, `write`, `read` and `export` use an explicit `--format`:
19
+ `msg` (FTSC), `opus`, `jam`, `squish` or `hudson`. Hudson write/read requires
20
+ `--board 1..200`. Bases must be offline: close GoldED and other users first.
21
+ The format packages own validation, encoding, locks and rollback.
22
+
23
+ `write` accepts one JSON object or a JSONL batch with `--jsonl`. Structural
24
+ validation finishes before opening a writer. Each append commits separately;
25
+ a later failure keeps earlier appends. A broken pipe can hide the receipt of
26
+ an already committed message. Rerunning may create duplicates.
27
+
28
+ `read --body` emits the reader's `body_text` exactly, including any controls
29
+ present there, without adding a newline. `read --revision` adds the writer's
30
+ identity and revision. `export --archive` reports reader issues as JSONL on
31
+ stderr and exits 7 if any occur. Export is not a byte-identical backup.
32
+
33
+ ```sh
34
+ uv run ftnt decode --charset IBMPC < old-text.txt
35
+ uv run ftnt repair --json < utf8-text.txt
36
+ ```
37
+
38
+ Both filters read the whole input into memory. Decode uses the core charset
39
+ aliases, strict decoding and preserves line endings and trailing nulls.
40
+ Repair uses the core heuristic explicitly; import/export never repair text.
41
+
42
+ The public manual is the [front page](index.html) and one page per command:
43
+ [create](create.html), [write](write.html), [read](read.html),
44
+ [export](export.html), [decode](decode.html), [repair](repair.html),
45
+ [heads](heads.html) and [catalog](catalog.html).
46
+ Examples are in `examples/`. Maintainer notes stay in `docs/`.
47
+ The pages share [DESIGN.md](DESIGN.md).
48
+
49
+ Checks:
50
+
51
+ ```sh
52
+ uv run pytest -q
53
+ uv run ruff check .
54
+ uv run ruff format --check .
55
+ uv run mypy
56
+ uv build
57
+ uv run twine check dist/*
58
+ uv run python scripts/verify_distribution.py
59
+ uv run scripts/build_reading_pages.py
60
+ ```
61
+
62
+ Distribution checks build dependency wheels from sibling checkouts and run both
63
+ CLI wheels in fresh environments outside the checkout. They do not establish
64
+ public-index dependency resolution. The sdist strips checkout-only uv sources.
65
+ The CI matrix targets Linux and macOS with Python 3.12 and 3.14. Windows is
66
+ unsupported for now. GoldED Opus runtime interoperability remains unverified.
67
+ There is no configured remote, published CLI package or Pages deployment.
@@ -0,0 +1,152 @@
1
+ # JSON contract
2
+
3
+ Status: schema version 1 implemented locally. CLI version and schema version
4
+ are distinct. Release verification is recorded in VERIFICATION.md.
5
+
6
+ ## Import
7
+
8
+ `write` accepts a flat object matching `OutgoingMessage`. Required fields:
9
+ `from_name`, `to_name`, `subject`, `body_text`, all strings. Empty strings are
10
+ structurally valid, but format validation may reject them.
11
+
12
+ | Optional field | JSON type | Default |
13
+ | --- | --- | --- |
14
+ | `external_id` | string / null | null |
15
+ | `from_address`, `to_address` | FTN address string / null | null |
16
+ | `posted_at` | ISO 8601 datetime string / null | null |
17
+ | `attributes_raw` | integer >= 0 / null | null |
18
+ | `control_lines` | array of control objects | [] |
19
+ | `reply_to_msgno`, `reply1st_msgno`, `reply_next_msgno` | integer >= 0 / null | null |
20
+ | `reply_list` | array of integers >= 0 | [] |
21
+ | `routing_seen_by`, `routing_path` | array of strings | [] |
22
+
23
+ `posted_at` accepts `YYYY-MM-DDTHH:MM:SS` with optional `Z` or numeric UTC offset,
24
+ without microseconds. Reject invalid calendar dates. Timezone requirements are
25
+ format-specific and checked structurally before writing:
26
+
27
+ | Format | Date input |
28
+ | --- | --- |
29
+ | MSG / FTSC | Naive datetime; no timezone |
30
+ | MSG / Opus | Naive datetime; 1980–2069, even seconds, no microseconds |
31
+ | JAM | Naive (interpreted as UTC) or timezone-aware datetime |
32
+ | Squish | Timezone-aware datetime; writer converts to UTC |
33
+ | Hudson | Naive datetime; no timezone |
34
+
35
+ Opus dates use both DOS written words and the textual date. The local MSG 1.3.0
36
+ writer accepts their shared 1980–2069 range and rejects odd seconds. Omitted dates
37
+ write zero written words and an empty textual date; new arrived words are zero
38
+ because the core input model has no arrived field. Updates in the MSG package
39
+ preserve arrived words. Runtime GoldED interoperability remains unverified.
40
+
41
+ Pass datetime through without converting or removing timezone. Use
42
+ `examples/message.json` for MSG/JAM/Hudson and `examples/message-squish.json`
43
+ for Squish. Date precision and year limits belong to the writer. Omitted/null
44
+ dates become None; this does not guarantee the current time. There is no common
45
+ lossless date contract across formats.
46
+
47
+ Parse addresses with `FtnAddress.from_string`, e.g. `2:236/77.1@fidonet`. Preserve
48
+ domain and point in the model; formats may impose narrower limits. Do not expand
49
+ abbreviated addresses. Booleans are not integers. Writers validate numeric widths;
50
+ a large JSON integer does not establish a valid header value.
51
+
52
+ Control objects require string `name` and `value`. Optional string `raw` defaults
53
+ to an empty string and maps directly to `ControlLine.raw`. Preserve unknown
54
+ controls in the input model. Writers decide raw/structured serialization and
55
+ conflicts. Do not invent another raw meaning or CLI-specific kludge escaping.
56
+ Arrays preserve order and duplicates. `external_id` is external MSGID, never a
57
+ base number. Do not conceal contradictory controls, MSGID or charset declarations.
58
+
59
+ Reject unknown top-level/control keys, duplicate keys, NaN/Infinity, incorrect
60
+ nested types, BOM and invalid UTF-8 with a record number. `provenance`, `msgno`,
61
+ `identity`, `revision` and export envelopes are not write input. Provenance comes
62
+ from the source/read operation, not an imported claim about the new record.
63
+
64
+ No arbitrary binary header import or requested message number. Append assigns
65
+ the format's number. Fixture authors manage forward reply links; there is no
66
+ automatic link resolver.
67
+
68
+ ## Export and read
69
+
70
+ Messages use this envelope:
71
+
72
+ ```json
73
+ {
74
+ "schema_version": 1,
75
+ "type": "message",
76
+ "source": {"format": "jam", "base": "/resolved/path/general", "board": null},
77
+ "message": {
78
+ "msgno": 1,
79
+ "from_name": "Alice",
80
+ "to_name": "Bob",
81
+ "subject": "Encoding fixture: æøå",
82
+ "body_text": "First line.\nSecond line.",
83
+ "attributes_raw": 0
84
+ }
85
+ }
86
+ ```
87
+
88
+ `message` includes all `ParsedMessage` fields, including nulls. The example shows
89
+ only required fields. Serialize dates with `datetime.isoformat()`; addresses stay
90
+ reader strings. Serialize control metadata and provenance recursively from
91
+ dataclass fields. Tuples become arrays. Do not normalize IDs, routing or text
92
+ beyond existing reader behavior.
93
+
94
+ `source.base` is the resolved absolute base path. Hudson `source.board` comes
95
+ from the reader's `area_meta_key="hudson:N"`, set from the binary board byte.
96
+ The adapter checks N is in 1..200. A two-board literal fixture protects this
97
+ mapping and lookup; message titles are not inspected.
98
+ Do not invent unavailable fields. Paths can expose local structure; users decide
99
+ whether to share output.
100
+
101
+ Only `read --revision` adds envelope-level `identity` and `revision`. Normal
102
+ read/export omit them. Treat digest and location as opaque data. They belong to
103
+ a physical base, not a portable export ID.
104
+
105
+ ## Create and write results
106
+
107
+ ```json
108
+ {"schema_version":1,"type":"create_result","format":"jam","base":"/resolved/path/general"}
109
+ ```
110
+
111
+ ```json
112
+ {
113
+ "schema_version": 1,
114
+ "type": "write_result",
115
+ "input_record": 1,
116
+ "identity": {"format": "jam", "base": "/resolved/path/general", "msgno": 1, "board": null},
117
+ "revision": {
118
+ "identity": {"format": "jam", "base": "/resolved/path/general", "msgno": 1, "board": null},
119
+ "location": [1024, 0],
120
+ "digest": "opaque-sha256-digest"
121
+ }
122
+ }
123
+ ```
124
+
125
+ Location is illustrative; emit the writer's actual tuple unchanged. Map identity
126
+ and revision from `WriteResult`, never reconstruct them. `input_record` is
127
+ one-based. Do not emit an extra summary line on stdout.
128
+
129
+ Export can produce new write input with an explicit mapping:
130
+
131
+ ```sh
132
+ ftnt export ./old --format jam |
133
+ jq -c '.message | {from_name, to_name, subject, body_text, posted_at, external_id}' |
134
+ ftnt write ./new --format jam --jsonl
135
+ ```
136
+
137
+ This intentionally selects only some fields. It is not a complete clone.
138
+
139
+ ## Repair and issues
140
+
141
+ `repair --json` emits `schema_version: 1`, `type: "repair_result"`, and the core
142
+ fields `text`, `changed`, `confidence`. Confidence is a heuristic score, not a probability.
143
+
144
+ Archive issues on stderr contain `schema_version: 1`, `type: "reader_issue"` and
145
+ an `issue` object with every `ReaderIssue` field. Known command errors on stderr
146
+ are human-readable and include record number/commit count for batch failures.
147
+ A stable machine-readable error schema is outside v1.
148
+
149
+ Schema version 1 may gain documented optional output fields, but existing names,
150
+ types and meanings must not change. Consumers must ignore unknown output fields.
151
+ Write input stays strict. Breaking JSON changes require a new schema version
152
+ and an explicit migration.
@@ -0,0 +1,231 @@
1
+ # Implementation plan
2
+
3
+ Status: implementation started 2026-10-06. The phases below preserve the original
4
+ acceptance plan. Current implementation and verification are recorded here and
5
+ in [VERIFICATION.md](VERIFICATION.md).
6
+
7
+ ## Implementation status
8
+
9
+ - Six commands and schema v1 are implemented locally using public APIs.
10
+ - Local MSG 1.3.0 adds explicit Opus sessions; dates use the shared DOS/text
11
+ range 1980–2069 with even seconds. Arrived words default to zero and survive updates.
12
+ - Decode reads whole input in v1 and uses public `detect_charset(b"", charset)`
13
+ for alias selection. It preserves trailing nulls and line endings.
14
+ - Hudson board mapping uses area_meta_key, protected by an independent two-board fixture.
15
+ - Subprocess tests cover workflows, preflight, partial commits, archive issues,
16
+ locks, broken receipts, SIGINT and adapter error reporting.
17
+ - Linux/Windows remote CI, GoldED Opus runtime interoperability and public-index
18
+ resolution remain release gates. Publication has not been authorized.
19
+ - Design files were inventoried. FTNT inherits documentation tokens from
20
+ golded-ftn-python-docs/DESIGN.md, which uses golded-site's historical palette.
21
+ This change updates status/copy only; layout and shared visual rules are unchanged.
22
+
23
+
24
+ ## Chosen approach
25
+
26
+ A separate CLI repository, thin adapters and standard-library `argparse`.
27
+ `argparse` is sufficient for six commands and keeps the CLI's own runtime
28
+ dependencies small. Reconsider Click/Typer for a concrete need, not colored help.
29
+
30
+ Proposed dependencies: `golded-ftn>=1.2.1,<2` and the four format packages
31
+ `golded-ftn-msg>=1.3.0,<2` and `golded-ftn-jam`, `golded-ftn-squish`,
32
+ `golded-ftn-hudson` at `>=1.2.0,<2`. Install all formats in v1 so help matches actual capability.
33
+ No local `uv.sources` in public distributions. Core and format packages version
34
+ independently of the CLI. The MSG minimum version must be raised to the release
35
+ that implements and verifies Opus writing; the published MSG 1.2.0 writer rejects it; the local 1.3.0 extension implements it.
36
+
37
+ The CLI owns arguments, JSON types, text/binary streams and exit codes. Packages
38
+ own FTN rules. Use `json` with explicit field validation rather than another
39
+ runtime model framework. Choose small typed adapters over a plugin system.
40
+
41
+ ## 0. Establish API boundaries — small/medium
42
+
43
+ Read [SPEC.md](SPEC.md) and [JSON.md](JSON.md), then inspect current installed
44
+ packages again. The original plan used core 1.2.1 and format packages 1.2.0, inspected
45
+ on 2026-10-05. Current local verification uses core 1.2.2 and MSG 1.3.0. Use public exports, not private writer helpers.
46
+
47
+ Discovery before implementation:
48
+
49
+ - Find a public codec/alias seam for incremental `decode`. If missing, propose a
50
+ small core API separately; alternatively document whole-input decoding in v1.
51
+ Do not duplicate alias tables or import private names.
52
+ - Establish Hudson board mapping on `ParsedMessage` with a literal fixture and
53
+ verify read lookup across boards. Do not derive board from free text.
54
+ - Check create paths, source_type values, writer date defaults and supported
55
+ reply fields per format. Document rejections in help.
56
+ - Confirm dependency ranges resolve from PyPI without checkout sources.
57
+
58
+ Exit: boundaries are settled. Revise the specification explicitly if a seam is missing.
59
+
60
+ ## 0a. Add Opus writing to golded-ftn-msg — medium/large
61
+
62
+ Opus is a MSG header variant, not a fifth storage engine. Extend the public
63
+ `golded-ftn-msg` writer before wiring CLI writes. Existing `MsgReader("opus")`
64
+ can support reading/export while this work proceeds. No Opus serialization in
65
+ CLI code and no claim that the current writer supports it.
66
+
67
+ Use original GoldED source as the format reference. Document the public variant
68
+ selection for create/open and implement create, append, consistent read, update
69
+ and delete with the same identity, revision, offline and rollback contracts as
70
+ FTSC. Preserve FTSC behavior. Explicit selection is required; do not guess or
71
+ convert an existing area's header variant implicitly.
72
+
73
+ - Serialize written/arrived DOS timestamps at bytes 176–183 rather than FTSC
74
+ zone/point words. Preserve both existing timestamps and unknown header bytes
75
+ on updates unless explicitly changed.
76
+ - Represent zone/point through INTL/FMPT/TOPT. Specify how explicit addresses
77
+ and supplied controls agree; reject contradictions before mutation. Address
78
+ representation belongs to the format package, without adding automatic routing.
79
+ - Settle the date contract before implementation: DOS years 1980–2107,
80
+ two-second precision, textual-date fallback, omitted written/arrived timestamps,
81
+ and the limits of the existing core model. Prefer rejecting unrepresentable
82
+ values over silent rounding. Document any required core API extension separately.
83
+ - Use TDD with independent literal headers: both timestamps, zero/invalid dates,
84
+ year/precision limits, nonzero zones and points, reply links, unknown metadata,
85
+ and conflicting controls. Cover every writer operation, revisions, rollback,
86
+ and unchanged FTSC behavior; reader roundtrips alone are insufficient.
87
+ - Verify GoldED reads and edits Python-written Opus messages and Python reads
88
+ and updates GoldED-written messages. Record the exact build/platform. This is
89
+ offline compatibility testing, not authorization for concurrent use.
90
+
91
+ Exit: the public Opus writer contract is documented and tested, compatibility
92
+ results are recorded, and the CLI's MSG dependency floor names a release with
93
+ this capability. Publication remains a separately authorized step.
94
+
95
+ ## 1. CLI and JSON foundation — medium
96
+
97
+ Add `pyproject.toml`, `src/golded_ftn_tools`, entry point, test/dependency groups
98
+ and CI. Proposed separation: argument parser, JSON contract, format adapters,
99
+ commands and shared diagnostic/exit-code mapping. Split responsibilities only
100
+ where a real consumer needs it.
101
+
102
+ Use TDD at the public CLI: one observable behavior at a time. Start with help,
103
+ version, stdout/stderr separation, UTF-8, unknown keys, duplicate keys and
104
+ addresses/dates. Avoid tests that merely mirror private functions.
105
+
106
+ Exit: schema v1 parses input and serializes results without base I/O.
107
+
108
+ ## 2. Reading and text filters — medium
109
+
110
+ Implement `decode`, `repair`, `read` and `export`. Add ordinary strict reading
111
+ first, then archive issues and optional consistent revision reads. Use the actual
112
+ readers and helpers, not an alternative parser. Include explicit `--format opus`
113
+ using `MsgReader("opus")`; never interpret its timestamps as address words.
114
+ Opus `read --revision` depends on the writer added in phase 0a.
115
+
116
+ Tests: JSONL pipes, Unicode, CP850 and multibyte chunk boundaries, degree signs,
117
+ mojibake, unchanged text, LF/CRLF, body without an extra newline, sparse msgno/UID,
118
+ missing records, Hudson boards, corruption and partial exports. Cover archive
119
+ callbacks and exit code 7. Document reader buffering and offline requirements.
120
+
121
+ Exit: read/filter commands work without writes or automatic repair.
122
+
123
+ ## 3. Create/write, starting with JAM — medium/large
124
+
125
+ Implement `create` and `write` for JAM, then MSG (FTSC and Opus), Squish and
126
+ Hudson. Opus writes depend on phase 0a. Use
127
+ context-managed sessions and `append`. Reject existing destination files.
128
+ Spool JSONL preflight to a temporary file; format validation stays with writers.
129
+
130
+ Tests: structural failure on record N causes zero writes; format/encoding failure
131
+ on N preserves previous commits; receipts match actual identity/revision. Cover
132
+ charset/control conflicts, numbers, dates, null input, attribute limits, reply
133
+ fields, auxiliary files and Hudson boards 1/200. No sidecar, overwrite or retry.
134
+
135
+ Exit: all four storage formats, including both MSG header variants, can create
136
+ bases and read CLI-written messages back.
137
+ Also compare some raw bytes to independent expectations, not only roundtrips.
138
+
139
+ ## 4. Failures, pipes and platforms — medium/large
140
+
141
+ Add deterministic subprocess tests for lock timeout, broken pipe, SIGINT,
142
+ I/O failure and rollback failure. Use a controlled lock helper and focused
143
+ injection at the adapter boundary, not random timing. Reuse writer packages'
144
+ already-tested rollback and verify CLI reporting of the relevant exception.
145
+ Do not import private I/O layers to reproduce base algorithms.
146
+
147
+ Run redirected CLI subprocesses on Linux/macOS/Windows. Separate POSIX 141/SIGINT
148
+ from Windows behavior and record the observed contract. Cover failure after
149
+ append commits but before the stdout receipt. Do not describe it as rollback.
150
+
151
+ Exit: tests support SPEC's exit codes and partial-success semantics.
152
+
153
+ ## 5. Documentation and distribution — medium
154
+
155
+ Replace planned status in README/HTML only once implementation exists. Keep
156
+ HTML copy, JSON examples, help and specification aligned. Run examples from a
157
+ wheel installed outside the checkout. Build wheel and sdist, inspect metadata
158
+ and entry point, rebuild the sdist, resolve dependencies without local sources
159
+ and run consumer tests.
160
+
161
+ Update the GoldED design documents together:
162
+
163
+ - `golded-ftn-tools/DESIGN.md`: FTNT identity, reading-copy generation, code
164
+ panels, page/section navigation and sidebar scroll behavior.
165
+ - `golded-ftn-python-docs/DESIGN.md`: shared documentation rules and fixes that
166
+ also apply to the Python library documentation.
167
+ - `golded-site/DESIGN.md`: shared GoldED identity and visual rules that also
168
+ apply to the main site.
169
+
170
+ Inventory these files again when implementation begins. Compare shared palette,
171
+ typography, section labels, syntax colors, navigation, mobile and print rules.
172
+ Keep shared rules consistent and document intentional site-specific differences;
173
+ do not copy FTNT branding or CLI-specific rules into every site. Identify the
174
+ canonical source for each shared rule and cross-link it so later changes do not
175
+ drift. Check the affected HTML pages against their updated design documents,
176
+ including Safari sidebar scrolling and access to both ends of the TOC. Record
177
+ which pages and viewport states were actually checked.
178
+
179
+ Quality gate: pytest, Ruff lint/format, strict mypy, distribution checks,
180
+ README/spec examples and `git diff --check`. CI uses the same gate; local results
181
+ do not replace remote platform results. Run relevant API/format tests if a
182
+ discovery change required changes in a dependency.
183
+
184
+ Exit: SPEC acceptance criteria are met and documented; the relevant DESIGN.md
185
+ files agree on shared rules, and affected pages have been checked against them.
186
+ Tags, PyPI publication
187
+ and GitHub Pages require a later explicit task. Implementation is local. No publication is automatic.
188
+
189
+ ## Review points
190
+
191
+ Odinn can focus on:
192
+
193
+ 1. Six commands for the first release; `update/delete` can wait.
194
+ 2. Explicit `--format` instead of detection.
195
+ 3. Strict import and JSONL preflight with one commit per message.
196
+ 4. Export envelopes and separate write input instead of a false lossless roundtrip.
197
+ 5. Python packages are the engine; the CLI does not own format algorithms.
198
+
199
+ These are recommended decisions, not unanswered questions blocking review.
200
+
201
+ ## Risks and accepted limits
202
+
203
+ | Risk | Response |
204
+ | --- | --- |
205
+ | Reader and writer share a bug | Independent binary expectations in integration tests |
206
+ | Late batch failure or closed pipe | Per-record receipts; no retry/whole-batch rollback |
207
+ | Export omits unknown raw metadata | Describe export as data, not backup |
208
+ | GoldED is using the base | Offline contract; no concurrent flag or new live promises |
209
+ | Helper lacks a public streaming seam | Resolve in phase 0; separate core change if needed |
210
+ | Fixture bytes vary with base time/platform | Fixed input date/MSGID; test content, not promised identical files |
211
+ | Dependencies change API | Version bounds, clean installs and CI integration |
212
+
213
+ ## Source basis
214
+
215
+ Checked in local source repositories before writing this plan:
216
+
217
+ - Core `models.py`, `contracts.py` and exports: input model, sessions, revisions and issues.
218
+ - `MsgReader`/`JamReader`/`HudsonReader` and all four writer exports/signatures.
219
+ - Hudson `open(path, board, options, scan_path=...)` and its all-board reader behavior.
220
+ - Core strict encoding/repair and writer format validation.
221
+
222
+ Public references: [core API](https://golded-dev.github.io/golded-ftn-python-docs/core-api.html),
223
+ [writer guide](https://golded-dev.github.io/golded-ftn-python-docs/writers.html) and
224
+ [format references](https://golded-dev.github.io/golded-ftn-python-docs/).
225
+ These are entry points; inspected source takes precedence over later drift in web copy.
226
+
227
+ ## Findings from example checks
228
+
229
+ MSG/Hudson require naive dates; Squish requires timezone; JAM accepts both.
230
+ Examples therefore use separate standard and Squish inputs. Preserve this
231
+ difference in JSON validation instead of hiding it with timezone conversion.