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.
- golded_ftn_tools-1.0.1/.github/workflows/ci.yml +47 -0
- golded_ftn_tools-1.0.1/.github/workflows/publish.yml +32 -0
- golded_ftn_tools-1.0.1/.gitignore +6 -0
- golded_ftn_tools-1.0.1/LICENSE +21 -0
- golded_ftn_tools-1.0.1/PKG-INFO +81 -0
- golded_ftn_tools-1.0.1/README.md +67 -0
- golded_ftn_tools-1.0.1/docs/JSON.md +152 -0
- golded_ftn_tools-1.0.1/docs/PLAN.md +231 -0
- golded_ftn_tools-1.0.1/docs/SPEC.md +195 -0
- golded_ftn_tools-1.0.1/docs/VERIFICATION.md +100 -0
- golded_ftn_tools-1.0.1/examples/message-squish.json +17 -0
- golded_ftn_tools-1.0.1/examples/message.json +12 -0
- golded_ftn_tools-1.0.1/examples/messages.jsonl +2 -0
- golded_ftn_tools-1.0.1/pyproject.toml +50 -0
- golded_ftn_tools-1.0.1/scripts/build_reading_pages.py +700 -0
- golded_ftn_tools-1.0.1/scripts/check_reading_pages.py +88 -0
- golded_ftn_tools-1.0.1/scripts/sdist_hook.py +33 -0
- golded_ftn_tools-1.0.1/scripts/verify_distribution.py +155 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/__init__.py +17 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/__main__.py +4 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/adapters.py +89 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/api.py +394 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/catalog.py +84 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/cli.py +300 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/errors.py +91 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/json_contract.py +196 -0
- golded_ftn_tools-1.0.1/src/golded_ftn_tools/py.typed +0 -0
- golded_ftn_tools-1.0.1/tests/test_api.py +75 -0
- golded_ftn_tools-1.0.1/tests/test_cli.py +295 -0
- 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,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.
|