GOLDED FOR PYTHONSource on GitHub ↗
MSG AND OPUS / FORMAT REFERENCE1.2.0 WORKING SOURCES · OFFLINE WRITERS
Individual files. Complete messages.

MSG format reference

Read classic FTSC and Opus message directories. Edit FTSC messages through golded_ftn_msg. Each message has its own numbered file and a 190-byte header. Python 3.12+ is required. Version 1.2.0 is available on PyPI.

Use the core API reference for shared models and errors, and Editing message bases for revision and patch contracts.

01

Public API

The package exports exactly MsgReader, MsgWriter and MsgSession. Construct a writer without arguments. Obtain sessions through open and use them as context managers; closing or a failed rollback makes them unusable.

API signature
MsgReader(header_format: Literal["ftsc", "opus"] = "ftsc")
MsgReader.read(path: str | PathLike[str], options: ReaderOptions | None = None)
    -> Iterator[ParsedMessage]
MsgWriter()
MsgWriter.create(path: str | PathLike[str]) -> None
MsgWriter.open(path: str | PathLike[str], options: WriterOptions | None = None,
    *, header_format: Literal["ftsc", "opus"] = "ftsc") -> MsgSession
MsgWriter.write(path: str | PathLike[str], messages: Iterable[OutgoingMessage],
    options: WriterOptions | None = None) -> int
MsgSession(base: Path, options: WriterOptions)
MsgSession.read(msgno: int) -> SessionMessage
MsgSession.append(message: OutgoingMessage) -> WriteResult
MsgSession.update(identity: MessageIdentity, patch: MessagePatch,
    expected_revision: RevisionToken) -> WriteResult
MsgSession.delete(identity: MessageIdentity,
    expected_revision: RevisionToken) -> MessageIdentity

write is a batch convenience method: it creates the directory if needed and returns the number of completed appends. Each append commits separately; an exception on a later message leaves earlier appends in place.

02

Area paths and initialization

Pass the directory, without a message filename. The reader accepts positive numeric stems with case-insensitive .msg extensions, skips symlinks and reads regular files in numeric order. Duplicate numeric identities are rejected, even when filenames differ in padding or extension case.

create accepts a missing or empty directory and creates parent directories. It refuses a nonempty directory. Opening a session initializes the private .golded-ftn-msg.lock sidecar. This stores the highest allocated number, so removing the highest message does not make its number available again. Existing lastread files are preserved; no GoldED lastread file is required or created for a new empty area.

Message identity uses format msg, the resolved area path and the filename number. It is separate from an external MSGID. Reader provenance records the actual filename and message number; byte offsets are unknown.

03

Reading and metadata

FTSC is the default. Choose MsgReader("opus") explicitly for Opus: header format detection is not automatic. FTSC words 176–183 represent zone and point; Opus uses those bytes for DOS written/arrived timestamps. Opus addresses combine header net/node with INTL/FMPT/TOPT, without interpreting timestamps as addresses. Opus provenance uses source type opus.

Text decoding is strict, using core charset detection and CP850 fallback. Kludges remain in the normalized body. Header addresses must agree with INTL/FMPT/TOPT. Invalid FTSC textual dates become None; English month names and the 1970–2069 two-digit year window are used. Opus written dates are naive, have two-second precision and cover 1980–2107. Zero or invalid Opus timestamps fall back to the textual date.

Raw 16-bit attributes are exposed as attributes_raw. The two header reply words map to reply_to_msgno and reply1st_msgno; zero means no link. General controls, external MSGID, addresses and routing remain distinct values. No MSGID, routing or duplicate detection is generated automatically.

04

Writing and limits

Writers produce FTSC only. open(..., header_format="opus") is rejected. Names permit 35 encoded bytes and subjects 71. Header text rejects nulls, line breaks and control separators. Encoding defaults to CP850 and is strict: conflicting charset declarations, replacement characters and silent truncation are refused. Dates must be naive, have no microseconds and lie in 1970–2069. Address components, raw attributes and reply links use unsigned 16-bit values. Domain addresses cannot be represented.

MSG represents only the reply-to and first-reply links. Additional reply links and arbitrary reply lists are unsupported. Provenance is never serialized and cannot be patched. Body lines serialize with CR and one terminating null byte; embedded nulls are rejected.

Updates begin with the existing raw header. Unknown header bytes and omitted fields survive. Attribute-only changes preserve original text bytes. Body-only changes preserve controls and routing. control_lines replaces general controls; omitted external MSGID, addresses and routing keep their structured values. Explicit conflicting metadata is rejected. None clears only representable optional values. Delete removes one file and leaves other messages' reply links unchanged.

05

Create, read, update and delete

This example uses an isolated temporary area. The session read issues the revision used by the update; the updated revision is used for deletion.

Python · RUNNABLE
from pathlib import Path
from tempfile import TemporaryDirectory

from golded_ftn import MessagePatch, OutgoingMessage
from golded_ftn_msg import MsgReader, MsgWriter

with TemporaryDirectory() as directory:
    area = Path(directory) / "messages"
    writer = MsgWriter()
    writer.create(area)
    with writer.open(area) as session:
        added = session.append(
            OutgoingMessage(
                from_name="Alice",
                to_name="Bob",
                subject="Hello",
                body_text="First line",
            )
        )
        current = session.read(added.identity.msgno)
        changed = session.update(
            current.identity,
            MessagePatch(body_text="A longer second line"),
            current.revision,
        )
        assert session.read(changed.identity.msgno).message.body_text.endswith(
            "A longer second line"
        )
        assert session.delete(changed.identity, changed.revision) == changed.identity
    assert list(MsgReader().read(area)) == []
06

Archive reading

Strict reading stops with ParserException for malformed message files; filesystem errors remain filesystem errors. ReaderOptions(archive_mode=True, on_issue=...) requires a callback and skips malformed records with record_parse_error. Declared ASCII may fall back strictly to the configured charset, reporting ascii_decode_fallback. Invalid UTF-8 and conflicting addresses are skipped. Duplicate message numbers report duplicate_message_number with action stopped before any messages are returned. Callback exceptions propagate. Issues identify the source without including message contents. Archive mode never enables editing or repairs original bytes.

07

Locks, rollback and compatibility

Keep GoldED closed. concurrent=True is rejected on every platform: original GoldED MSG locks are empty functions, and the Python sidecar cannot coordinate GoldED readers or writers. Standalone reading also requires stable files.

Each session operation takes the sidecar lock, rereads the area and checks the raw-byte revision under the lock. The default monotonic timeout is five seconds. Python sessions share a lock manager within one process. Callers must avoid direct base-file access during operations.

Append serializes a complete temporary file, then publishes without replacing an existing destination: a hard link on POSIX, a non-replacing rename on Windows. Update replaces the complete file; delete unlinks it. Ordinary I/O failure restores the affected file and numbering sidecar where possible. Failed rollback raises RollbackError and poisons the session. Completed earlier operations survive. Process death and power loss have no transactional guarantee.

Runtime checks in this checkout cover macOS. Linux and Windows execution and GoldED build interoperability, caching and refresh remain unverified. Source layout checks are not live compatibility tests.

08

Implementation references