Zmail mail-file format semantics
Reconstruction claim
This document is the normative storage-format companion to the Zmail and mail composition reimplementation specification. It closes the text-format boundary for these two profiles:
C303-STORAGE: the maintained LM-3 System 303 implementation in public source; andG85-STORAGE-SRC: the separately preserved System 452.1 / Zmail 442.0 source profile used to specify Genera 8.5 behavior.
The two profiles are not aliases. In particular, C303 and G85 disagree about Text input, Unix boundaries, malformed Tenex input, Babyl error containment, status properties, format discovery, and save architecture. An implementation selects one profile per buffer and MUST NOT average those differences.
The compatibility boundary is the observable storage behavior defined here for those exact source profiles. It excludes package/API compatibility, interactive Zmail behavior outside mail-file I/O, and claims about untested preserved worlds.
This is a semantic and logical-character-I/O specification. It defines Lisp-machine character records, message boundaries, property mappings, state transitions, ordering, and failures. It does not publish licensed Genera source or recovered mail, and it does not claim that the logical characters specified here have one release-independent host-octet encoding. Exact G85 KBIN words and their byte serialization remain a local licensed oracle.
Normative language and evidence classes
MUST, MUST NOT, SHOULD, and MAY are normative. Evidence labels mean:
| Label | Meaning |
|---|---|
C303-SRC |
direct observation of the public maintained LM-3 source at the pinned Fossil check-in |
G85-SRC |
direct local observation of the hashed licensed source profile; only original prose and non-secret identities appear here |
MANUAL |
public manual or release-note comparison, not a substitute for executable source |
INFERENCE |
consequence derived from source-visible control flow and stated as such |
TODO-RUNTIME |
exact preserved-world or byte observation still required |
An implementation claiming a strict row MUST implement the source behavior even when a safer extension would be preferable. A safer extension MUST be selectable and reported separately.
Exact release and artifact profile
Public C303 evidence
The public source is maintained LM-3, not pristine 1983 media. The selected Fossil check-in is 4df393c68d7f083ce42d5c377039d26043cc18a9031ace28258dc97f4137eb91.
| Logical path | Bytes | SHA-256 | Role |
|---|---|---|---|
zmail/mfiles.lisp |
68,281 | 663346b2927fbd9988b9fd754747ed472430d8a25ed90732e2a60bad3a6456e4 |
common buffer, read loop, write-only Text, load/save state |
zmail/mfhost.lisp |
48,590 | 4f388328fb4554d6f6d9979ba72981d2ac93bd14efa52d490cbf39c1cca8fce0 |
ITS/Rmail/Babyl/Tenex/Unix/VMS formats and host selection |
zmail/defs.lisp |
identity inherited from the D08 source ledger | identity inherited from the D08 source ledger | status-property order and Text separator option |
zwei/primit.lisp |
44,003 | c75513e9dbe055368c868dd26addf42acba2d8c47d83dbb612b13c1c6d7d84db |
buffer-pointer construction and the forced-Text null-start failure boundary |
The canonical public links are in Sources.
Licensed G85 source evidence
These suffixes identify local evidence without publishing its machine-specific root. The complete 47-file selection and its content digests are in the declared-build source manifest.
| Portable suffix | Bytes | SHA-256 | Role |
|---|---|---|---|
sys.sct/zmail/mail-files.lisp.~1566~ |
205,514 | 1ade0babfa463a4c2780165f64f59ce4d25191d93af7770f7bf571d440ef3648 |
shared loader/writer, Text, retry and format verification |
sys.sct/zmail/foreign-mail-file-formats.lisp.~1520~ |
57,759 | 6eabd4f8ce57fa85b48542a8c415528c331f78d37101b2e89db23642e91f32ee |
ITS/Rmail/Babyl/Tenex/Unix |
sys.sct/zmail/directory-mail.lisp.~1505~ |
11,544 | d5b62077554e07496313aea9a864181d2db04cb60246def44ad7740e1e36debe |
Directory index and per-message files |
sys.sct/zmail/mailbox-pathnames.lisp.~1507~ |
25,415 | 86aba9f0cc4b2f2366ceddfbe5c272875f2d51460c9a170289ca0406c3cb9fb7 |
host-specific possible/default/actual format rules |
sys.sct/zmail/kbin/buffer.lisp.~1511~ |
14,113 | 07c45298489141b5c75492b733c826c5468a2a0efbbad6db926058592319efc6 |
KBIN buffer integration |
sys.sct/zmail/kbin/defs.lisp.~1512~ |
local manifest | 544dd752725bf141c024212be979a69e13d9a6ea13bea2d500d536fc051e6f3f |
KBIN constants and representation definitions |
sys.sct/zmail/kbin/dump-defs.lisp.~1505~ |
local manifest | 00fc429415ef537e38f52ab661fd5133b3cf9672c4530554dbb2b54e6ac66ee9 |
KBIN writer definitions |
sys.sct/zmail/kbin/dump.lisp.~1519~ |
local manifest | bd45fd83efd76c919da23da4a4ef2e90c4e6e01644f081769a0b0524c75e4ff5 |
KBIN writer |
sys.sct/zmail/kbin/level-1-defs.lisp.~1501~ |
local manifest | eed22620f1afb98928f703dd3dce2d415aa25767c315a58082aeb9985aa6710f |
KBIN level-1 definitions |
sys.sct/zmail/kbin/load-defs.lisp.~1501~ |
local manifest | 519fe69efdb04d82cab903bc68363ff148111bb742203af1ef1f4b0c77e42d91 |
KBIN loader definitions |
sys.sct/zmail/kbin/load.lisp.~1521~ |
local manifest | f6f08e2fc45afb6d3356b92992476fc27610c3d00d1b11ddc85eb18b09f4b50b |
KBIN loader |
sys.sct/zmail/kbin/trace.lisp.~1501~ |
local manifest | e4ef5888046569985a927af769eef9a5bc9142c9dcef63ef5e4c77beec5600e0 |
KBIN tracing support |
The G85 source profile is not automatically identical to the preserved Genera-8-5.vlod world. Any function-level world claim needs a separate oracle record.
Compatibility levels and selectable overlays
| Claim | Required behavior |
|---|---|
FMT-L1 |
message and option data model; deterministic adapter dispatch; no external file mutation |
FMT-L2-C303 |
exact C303 Rmail, Babyl, Tenex, Unix and VMS semantics, the source-visible unsupported Text-load path, host selection, and the public logical-fixture obligations below |
FMT-L2-G85-TEXT |
exact G85 Babyl, Rmail, Tenex, Unix, Directory and Text semantics; locally verified G85 serializer hashes are allowed but not published |
FMT-L2-G85-KBIN |
all G85 text semantics plus exact KBIN recognition, decoding, encoding, upgrade and failure behavior against local licensed fixtures |
FMT-L3-RUNTIME |
selected FMT-L2 plus preserved-world comparison and timing-visible background I/O behavior |
FMT-L2-G85-TEXT may register KBIN as unavailable. It MUST NOT expose an in-memory stub under the historical name. FMT-L2-G85-KBIN remains blocked by TODO-RUNTIME-ZM-G85-KBIN-BYTES until exact licensed byte fixtures can be exercised locally without entering the repository.
Common abstract data model
Buffer state
Each mail-file buffer owns at least:
| Field | Contract |
|---|---|
| profile | immutable C303-STORAGE or G85-STORAGE-SRC selector |
| adapter | exact format flavor and registration identity |
| pathname | translated pathname plus original host-family classification |
| stream | absent, input-open, output-open, failed, or closed-with-result |
| file identity | source-visible stream/file information used for conflict detection |
| header | ordered raw file-header records plus parsed property list |
| messages | ordered message identities; each has real wrapper interval, visible interval, status and ticks |
| append policy | source profile's option, not an adapter-independent boolean default |
| status | exact profile state; generic loading/saving labels may not erase release-specific states |
| partial I/O | last complete record/message, source position, stream result and pending external work |
The real message interval contains format wrapper material. The visible interval excludes status/separator material and may exclude original headers. Moving a message between incompatible adapters rewrites the wrapper and may reformat headers; a clone MUST NOT treat every message as one undifferentiated byte string.
Line model
Source line objects do not store a trailing newline as part of their active character length. The loader receives a line, its active length and an EOF flag. Specifications below use these tokens:
| Token | Meaning |
|---|---|
<CR> |
Lisp-machine Return line separator in source-visible character output |
<LF> |
linefeed in a foreign-file count where explicitly stated |
<CRLF> |
two PDP-10-counted characters produced for one Return |
<US> |
ASCII Unit Separator, code 31, the ITS/Babyl out-of-band delimiter |
<FF> |
formfeed/page, code 12 |
<TAB> |
horizontal tab |
<EOF> |
end-of-stream indication, not a stored character |
Fixture notation is descriptive; tracked documentation does not contain recovered licensed mail bytes.
Reader result algebra
The reader's boundary callback returns:
| Result | Meaning |
|---|---|
nil |
current line does not end the message |
true |
current line belongs to and ends the current message |
integer n |
first n characters belong to this message; optional remainder seeds the next |
start-next |
current line is excluded from this message and retained as the first line of the next |
Format state is reset to nil at each new message. The common reader sets its start line only after a nonblank line is seen, but it still invokes the adapter callback with a null start on blank input. If that callback reports an end, later buffer- pointer construction can fault on the null start; a whitespace-only file therefore cannot be assigned a generic no-message result without the adapter-specific branch.
G85 third boundary value
G85 adds an explicit new-line remainder value for a numeric split. It truncates the current line to the included prefix and stores new-line as the next message seed. C303's Tenex implementation instead mutates the line graph when recovering a mid-line boundary. These are not equivalent failure traces.
Recognition and construction transactions
The exact existing/new-file precedence is incorporated from the parent D08 specification. This section defines the format probes that transaction invokes.
C303 host computation
Every content probe reads the first line and resets the stream pointer to zero.
| Host family | No-stream default | Existing content choice | Append result | Possible interactive formats |
|---|---|---|---|---|
| ITS | Rmail | exact first line Babyl Options: selects Babyl; otherwise Rmail |
true only for Rmail first line *APPEND* |
Rmail, Babyl |
| Tenex family | Babyl | exact Babyl marker selects Babyl; otherwise Tenex | always true | Tenex, Babyl |
| Unix | Babyl | exact Babyl marker selects Babyl; otherwise Unix | always true | Babyl, Unix |
| VMS | Babyl | first line exactly one formfeed selects VMS; otherwise Babyl | always true | Babyl, VMS |
Rmail's *APPEND* test is reached only in the non-Babyl ITS branch. The C303 source contains no generic magic-priority pipeline.
G85 host computation
G85 separates four generics: native choices, pathname-derived default, host fallback, and content-derived actual format. Text is appended to a host's native choice list if the native method omitted it.
| Host | Native/possible formats in order | Pathname default | Fallback | Existing-content priority |
|---|---|---|---|---|
| LMFS/native | Babyl, Rmail, Directory, KBIN, Text | canonical Babyl/KBIN/Rmail type | Babyl | KBIN, Babyl, Directory marker, else character-compatible Rmail |
| ITS | Rmail, Babyl, KBIN, Text | canonical Babyl/Rmail/KBIN type | Rmail | KBIN, Babyl, else character-compatible Rmail |
| Tenex family | Tenex, Babyl, KBIN, Text | canonical Babyl/KBIN type | Babyl | KBIN, Babyl, else character-compatible Tenex |
| Unix | Unix, Babyl, KBIN, Text | raw RMAIL without type means Babyl; otherwise canonical Babyl/KBIN type |
Unix | KBIN, Babyl, else character-compatible Unix |
| Macintosh | Babyl, Rmail, KBIN, Text | canonical Babyl/KBIN/Rmail type | Babyl | KBIN, Babyl, else character-compatible Rmail |
Tenex and Unix host option methods define Append as true for every format. Comments that LMFS/Mac defaults should become KBIN are non-executable and MUST NOT change the strict table.
G85 probe order and pointer invariant
KBIN recognition precedes Babyl recognition. KBIN accepts either its native element type or a four-character narrow stream. The latter constructs 16-bit opcode/version pairs in normal and byte-swapped order, accepts only the exact format opcode and a version within the source bounds, and resets the pointer. Babyl then checks the exact first line Babyl Options: and resets the pointer. Directory checks the exact first line *Mail file directory* and resets the pointer. Host fallback runs last.
Every normal return and every reported apparent-format path MUST leave the stream at offset zero. A short four-character read, fat character, unsupported element type or failed bound check is a KBIN nonmatch, not an exception-driven KBIN selection.
C303 Rmail contract
Grammar and boundary
rmail-file = [ append-line CR ] *rmail-message
append-line = "*APPEND*"
rmail-message = message-text delimiter-line CR
delimiter-line = *prefix-char US *delimiter-tail
delimiter-tail = *(SP / TAB / FF)
US may occur at any index in the delimiter line. The boundary is valid only when every later character is space, tab or formfeed. The visible message ends immediately before US; the real wrapper consumes the whole delimiter line and starts the next message on the following line. At EOF after message text, the loader supplies a canonical one-character US line.
Header and options
The exact first line *APPEND* is a file header and sets Append true. It is removed from the message stream and retained in the buffer header. Any other first line is message input and leaves Append false. Updating options inserts or removes precisely that header line.
Serialization
Before each appended message, the target normalizes the previous message end to a line containing US. A message moved into Rmail receives no leading wrapper and one Return as its real trailer. Header parsing is the ITS header parser. Rmail persists no per-message status record beyond message header/body text.
C303 Babyl contract
File envelope
babyl-file = option-section US [ FF ] CR *babyl-message
option-section = 1*option-line
babyl-message = *blank-line status-line CR original-area eooh-line CR display-and-body
delimiter-line CR
eooh-line = "*** EOOH ***"
delimiter-line = US [ FF ]
The option reader appends each input line to the header interval and stops at the first line containing US; it does not require US to be the only character. The first-message locator searches the header interval for that line. If it contains only US, C303 appends FF before returning the following line as the first message.
For a newly constructed Babyl file, Version defaults to 5 and the header ends in US. A newly inserted message receives a leading blank line, *** EOOH ***, a following blank line, and a trailing Return. When normalizing an existing message, the final delimiter is US FF except for the final non-append normalization path, where bare US is allowed. The in-memory message end is before the delimiter.
Option-line grammar
An option line is split at its first colon. Its name is uppercased and interned as an option key. With no colon, the value is Boolean true. With a colon, spaces and tabs immediately following it are skipped. The remainder is decoded as follows:
| First value character | Result |
|---|---|
(, quote, slash, or decimal digit |
one Lisp object read with keyword package and decimal print/read base |
| anything else | remaining characters as an untrimmed string |
| no remaining characters after a colon | an array-index failure occurs before a value can be produced |
Known option-specific parsers override the generic rule. Unknown option keys and values are retained in the property list. When updating the file, C303 edits a known line in place, removes it when all properties represented by that line became false, and appends nonfalse known properties not previously represented. Babyl Options: is forced to be the first logical property.
The empty-tail failure is a strict C303 edge, not an invitation to substitute NIL or an empty string: after whitespace skipping leaves the index at active length, the generic branch indexes that position while testing the special-character set. An unknown option whose value is an empty string can fail again during generic printing, which likewise inspects character zero. A safety-corrected overlay MAY preserve an empty string, but MUST report the divergence. G85 adds an explicit empty-tail parse case, although its generic printer retains the empty-string character-zero defect.
C303 Babyl option map
| Serialized name | Internal result | Parse/print rule |
|---|---|---|
Babyl Options: |
BABYL-P = true |
presence marker; printed first |
Version: |
numeric Version | decimal number; values outside inclusive 4–5 signal a continuable error and retain the parsed value/result path |
No Reformation |
Boolean | no value means true |
Gmsgs-Host: |
string or false | generic string handling |
Owner: |
string or false | generic string handling |
Mail: |
ordered pathname list | comma-separated, whitespace skipped before each item, merged against Zmail pathname defaults |
Append: |
Append and Reverse-New-Mail | absent value means Append true; otherwise octal bit 0 is Append and bit 1 is Reverse; printer emits one octal digit/mask |
Summary-Window-Format: |
Lisp object | one Lisp object in the selected reader environment |
Keywords: or Labels: |
keyword registry and original spelling string | comma-separated entries; an optional prefix through = is discarded when determining the keyword name |
Sort: |
sort selector | case-insensitive menu name or true when valueless |
Delete-Expired: |
yes/no/ask selector | case-insensitive menu name or true when valueless |
The settable list is Append, Reverse-New-Mail, Version, Mail, Owner, Sort, Delete-Expired, No Reformation, Summary-Window-Format and Gmsgs-Host. Babyl-P and the keyword registry fields are not ordinary UI-settable options.
Version 5 status grammar
status-v5 = reformatted "," basic-labels "," user-labels
reformatted = "0" / nonzero-text
basic-labels = *(SP basic-name ",")
user-labels = *(SP user-name ",")
The parser is comma-driven. 0 means not reformatted; any other first field means reformatted. Basic names are case-insensitively resolved through this ordered C303 table: last, unseen, deleted, bad-header, answered, forwarded, redistributed, filed, recent. badHeader is first normalized to bad-header. An unknown basic name signals a Zmail error. User names reuse a case-insensitive keyword registry entry or create an interned uppercase symbol while retaining the first observed display spelling.
The serializer emits basic labels in exactly the table order, then user keywords in message order. Every emitted label is preceded by one space and followed by a comma.
Version 4 status grammar
A v4 status line begins with optional D for Deleted, followed by an octal integer, followed by zero or more space-plus-{keyword} items. The five low bit positions map, in order, to Reformatted, Unseen, Losing-Headers, Answered and Filed; bit 1 is XORed with octal 2 so the stored sense represents Seen rather than Unseen. Serialization starts with octal 10000, adds those property bits, applies the Seen inversion, appends each keyword in braces, and overwrites the first character with D when Deleted.
Header split and malformed input
C303 skips blank lines to find the status line, parses it, then searches up to the message end for *** EOOH ***. When found, lines after the status and before the sentinel are the original header area, and the visible message starts on the line after the sentinel. If the sentinel is absent, the selected C303 method does not raise the explicit contained BAD-BABYL-MSG condition introduced in G85; it leaves the status line as the split reference and starts visible text on its following line. Strict compatibility preserves this malformed-input result.
The blank-line skip has no end-of-message guard. A wrapper containing only blank lines can therefore walk beyond the real interval and fail; C303 does not convert that case into G85's contained Null-Message condition.
C303 Tenex contract
Status record
tenex-status = received-date "," decimal-byte-count ";" octal-status
The date prefix ends immediately before the comma. The so-called byte count is the decimal field between comma and semicolon. At this layer it is a PDP-10 logical character count: an ordinary stored character contributes one and each Lisp-machine Return contributes two for the foreign CRLF representation. The status field is octal. Low bits, after XOR with one, map in order to Unseen, Deleted, Always-Show and Answered.
Boundary recognition and later status parsing are deliberately different. The boundary method recognizes only comma, later semicolon, and a decimal count between them; it does not validate the date or octal status. The before-parse method accepts any nonempty first line with comma and later semicolon, stores a false Received-Date when date parsing fails, and may signal while parsing the octal flags. Whether or not those delimiters exist, it then moves the visible message start to the following line. A strict clone MUST preserve this unconditional first-line hiding.
The byte count covers the visible message region and counts every Return as two PDP-10 characters. On rewrite, the received date falls back from Received-Date to Date and then the current time; the status field is exactly 12 octal digits; and the timezone text, date, clock and decimal byte count are regenerated.
Boundary state machine
Let remaining be the current Tenex state.
- The first line is probed for comma, semicolon and a decimal count. A recognizable line installs that count without subtracting the status line. An unrecognizable one enters recovery state
1. - If
remaining >= line-length, subtractline-length + 2; the message ends after the line when the result is nonpositive. - Otherwise the expected boundary is inside or before the line. Search from the expected offset for a comma and later semicolon whose intervening field is a decimal count. This test still does not validate a date or status field.
- If such a probable next status exists and the expected offset was nonzero, insert a Return at that offset, add two to the previous message's stored byte count, end the current message, and let the common reader consume the newly split suffix on its next iteration. The newly parsed count is returned as callback state but is discarded when the common reader resets state for the next message.
- If no probable status is found, set state to
1, do not end the message, and scan each later line until one resembles a status line.
Two strict defects follow from those rules. Recovery state 1 splits a later candidate at character index one, so the first character remains attached to the malformed message and the suffix begins one character into the candidate. A declared count of zero is also not treated as end-of-message on the status line: on a later nonempty candidate, entry state zero suppresses the split but returns true, causing that whole candidate line to remain in the preceding real interval rather than seed the next message. These are source-profile results, not recommended repair policy.
The nonzero-offset repair mutates the in-memory source text and, when the original status count can still be parsed, its earlier status count. It is not strict rejection and it is not transactional. A safe parser MAY offer rejection as a separate mode.
C303 Unix contract
Boundary recognition
The first nonblank line starts a message. A later line starts the next message only when all these tests pass:
- it starts exactly with
From; - it contains another space after the beginning of the sender portion;
- after that position, one of the configured weekday names occurs; and
- at least one decimal digit occurs after the weekday occurrence.
At EOF the final active line length terminates the message. The boundary line is retained as start-next. Unlike G85, a bare From prefix is insufficient.
Envelope parse and synthesized headers
The envelope parser examines a consecutive run of lines beginning From or >From and selects the last line in that run as its parse candidate; if no movement was possible, it uses the original first line. Starting after the five-character marker, it grows the sender field word by word until the next word matches a weekday abbreviation. Address and date errors are stored as report strings rather than re-signaled by this helper.
The fixed start offset remains five even when the selected candidate starts with the six-character >From prefix. Thus a selected escaped candidate is not generically unescaped: its leading > changes the field alignment and can yield an empty or malformed sender parse. Only the first real line is later hidden, so any additional candidate lines remain in the visible interval. A clone MUST NOT turn this narrow, source-visible scan into mboxrd unescaping.
Only the first real line is removed from the visible interval. Parsed envelope sender and date are stored as UNIX-FROM-HEADER; the date supplies Received-Date and, if missing, Date; the sender supplies From if missing. The selected source does not define a general mboxrd body unescaper. Consecutive >From handling above is an envelope-parse heuristic, not permission to claim complete >From round-trip support.
Serialization
The C303 rewrite test compares current and saved From address entries pairwise for equal Name and Host fields but does not compare list lengths. Because the parallel loop stops with the shorter list, equal common prefixes—including two empty lists—can suppress a needed rewrite. Otherwise it rewrites or inserts a From line, prints the current address list, then an optional ctime-shaped date using decoded Received-Date. The ordinary message headers follow. The source has no adapter method that escapes arbitrary body lines beginning From ; strict conformance MUST test both the prefix-length defect and the absence of body escaping rather than silently claiming modern mbox compatibility.
C303 VMS contract
Detection and boundaries
VMS is selected only on a VMS pathname when the first line equals one formfeed. Each message begins with a formfeed line; a later formfeed line is retained as the next message start. EOF ends the current message. The formfeed wrapper is excluded from visible text. A new message receives formfeed-plus-Return before its content and one Return after it.
Header variants
The first visible line is parsed as a fixed VMS From record. The sender occupies the 12-character field after the FROM:<TAB> prefix, trimmed of spaces; the following date portion is parsed if possible, with an error report string retained on failure.
If that sender is exactly CHAOSMAIL, the remaining message is parsed as ordinary headers. Otherwise the parser conditionally consumes, in order:
- a
TO:<TAB>line, split at commas into nonempty space-trimmed names on the pathname host; then - a
SUBJ:<TAB>line, whose value is the substring after the tab.
The fixed sender/date always supplies Received-Date and missing Date/From properties. On reformat, the writer emits a CHAOSMAIL-style From line derived from Received-Date or Date, prints known ordinary headers, inserts a blank line, and removes the old visible header region. Multics is not registered in this profile.
C303 Text contract
Text is write-only by declared capability. It leaves headers visually unchanged and replaces each message's final line with the configured Text separator. The ordinary arbitrary-format command excludes it because MAIL-FILE-REPARSABLE-P is false.
The source comment says an attempted read should yield no messages, but the selected method body and common reader do not implement that comment. The boundary callback returns true for every line. For a non-space/tab line the common reader sets START and publishes that one-line message. If the first line, or any line after a completed one-line message, is blank or contains only spaces/tabs, START remains null but the callback still returns true; the reader then calls buffer-pointer construction with that null line and reaches a fault before another message can be published. Thus a forced low-level load over consecutive nonblank lines yields one message per line until EOF or the first blank logical line. The exact runtime condition object and cleanup after the blank-line failure remain TODO-RUNTIME-ZM-C303-TEXT-FORCED. This is executable control-flow analysis (C303-SRC), not a System 303 band observation.
A port MAY reject reopening a Text buffer, matching the supported UI boundary, or label it as lossy. Such behavior is a safety/policy overlay. A port that exposes the forced low-level path under strict source semantics MUST reproduce both its one- message-per-consecutive-nonblank-line result and its blank-line failure boundary, and MUST NOT report the source comment as observed behavior.
G85 Rmail contract
G85 Rmail uses the same exact *APPEND* header and ITS US delimiter grammar as C303, with these release-visible differences:
- the ITS boundary callback also returns true at EOF, so a final unterminated message is complete without fabricating a canonical delimiter line;
- verification first applies the G85 default rejection of wrong element type, KBIN and Babyl, then consumes
*APPEND*into the header interval if present; - each serialized message begins on a fresh line; and
- after each serialized message the ITS mixin writes a fresh line followed by
US.
Rmail's only settable file option is Append. Updating it replaces the entire header interval with either *APPEND* plus Return or an empty interval and marks the sequence hard-modified.
G85 Babyl contract
Envelope and version
G85 requires the first line to equal Babyl Options:. It reads option lines into the header interval until EOF or a line containing US, but the US line itself is not inserted as an option line. Versions 4 and 5 are accepted. Version is initialized to the format default, 5, before header parsing, so an absent Version line retains 5; only an explicitly parsed absent-number or out-of-range value signals the selected source condition. This is defaulting, not an on-disk upgrade transaction.
Saving writes the updated option header, a fresh line and US; each message then writes FF CR, its wrapper/header/body representation, a fresh line and the ITS US terminator. A new v5 message leader is exactly a nonempty status line 0,,, then *** EOOH ***, then a blank line. A new v4 leader uses 0 instead.
Generic and specific option decoding
The first-colon split and generic value classes match C303, except that option keys are keywords and an empty post-colon tail is explicitly the empty string. Lisp objects are read in a standard I/O environment. Unknown lines are preserved; update logic changes only options in the buffer's declared possible set, and retains the relative order of existing lines. Babyl-P is forced first.
The explicit empty-string parse does not imply an exact empty-string round trip for an unknown option. The generic printer tests character zero before choosing display syntax and can fail on an empty string. Known options with their own printers follow those printers instead. Conformance therefore records this failure rather than normalizing it away.
| Serialized option | G85 result/delta |
|---|---|
Babyl Options: |
Babyl-P true, forced first |
Version: |
inclusive 4–5 version |
Owner: |
string or false |
Mail: |
ordered pathnames parsed/printed with the heuristic maybe-hosted pathname routines |
Gmsgs-Host: |
parser/printer is registered, but this key is not in the Babyl buffer's declared Possible or Settable lists |
Append: |
same octal Append/Reverse bits as C303 |
Reformat-Headers-P: |
true, false, Save-Both or source menu value; this new option takes precedence over legacy controls |
No Reformation |
legacy compatibility input; not settable through the G85 Babyl option list |
No Original: |
true, false or Save-Original legacy input; not settable through the G85 Babyl option list |
Sort: / Delete-Expired: |
selected menu value or true when valueless |
Summary-Window-Format: |
selected G85 summary format, defaulting to :DEFAULT |
Reformat-Template: |
Lisp object read in the user package |
Keywords: / Labels: |
keyword spellings and registry, with Labels printed for v5 and Keywords for v4 |
The declared possible list has 15 keys: Append, Babyl-P, Reformat-Headers-P, both legacy reformatting keys, Reverse-New-Mail, Version, Mail, Owner, Sort, Delete-Expired, Keywords, Keywords-String, Summary-Window-Format and Reformat-Template. The settable subset has 10: Append, Reformat-Headers-P, Reverse-New-Mail, Version, Mail, Owner, Sort, Delete-Expired, Summary-Window-Format and Reformat-Template. Sticky options are Append, Babyl-P and Version.
Reformatting-option normalization
If Reformat-Headers-P is explicitly present, it wins:
| Reformat-Headers-P | No Reformation after normalization | No Original after normalization |
|---|---|---|
:SAVE-BOTH |
false | false |
| false | true | false |
| true | false | false |
If it is absent, No Reformation true produces Reformat-Headers-P false. Otherwise No Original :SAVE-ORIGINAL produces true, false produces :SAVE-BOTH, and true also produces true. These legacy pairs are not one-to-one; retaining the explicit new property is therefore observable.
Status mapping
The v4 bit/keyword grammar remains the C303 grammar. G85 v5 uses the same three-field shape but has these exact deltas:
- nonzero Reformatted is stored as
:IN-FILE, not Boolean true; - basic labels parse against this order: Last, Last-Undeleted, Unseen, Deleted, Losing-Headers, Unparseable, Answered, Forwarded, Redistributed, Redirected, Filed, Sent, Recent;
- Losing-Headers, Unparseable and Redirected are parseable but flagged not to be serialized as basic status labels;
- two distinct
bad-headerentries map to Losing-Headers and Unparseable; the first case-insensitive match controls parsing; - label fields without a leading space are accepted for Unix RMAIL compatibility; and
- user labels are interned in the keyword package.
The writer emits only non-ignored basic properties in table order and user keywords in message order. Recent is persistable only at version 5 or later.
Contained malformed-message behavior
A blank-only message receives a Null-Message condition in Unparseable. A status parse failure is wrapped as a Bad-Babyl-Message condition and stored in Unparseable. A missing *** EOOH *** before the visible message end is handled the same way. These cases return from the format's before-parse method without moving the visible start past a valid sentinel. They do not abort the whole mail-file load by default.
When valid, lines after the status and before *** EOOH *** become an Original-Headers-Interval; lines through the sentinel are reassigned to the real wrapper; and visible text starts after the sentinel. Parsing prefers the original header interval and normalizes a parsed Re-family subject key to Subject before later daemons observe it.
G85 Tenex contract
Probable status predicate
A line is a probable status line only when it contains a comma, a later semicolon, and all three fields parse without error as an RFC822 date-time, a base-10 integer byte count with no junk, and an unsigned base-8 integer status with no junk. The predicate returns the parsed date, count and flags together; a failure returns four false values. As in C303, the source-named byte count drives logical foreign-file character positions here; it is not a count of bytes in a modern host encoding.
Boundary state machine
- On the first line, a probable status installs its byte count; count zero ends the message on that line. A non-status line enters
:NO-STATUS-LINErecovery. - EOF with an empty line ends a partial message.
- In
:NO-STATUS-LINE, non-status lines remain in the current malformed message. The next probable status line is retained asstart-nextand ends it. - For numeric state smaller than the current active line length, the first
statecharacters end this message and a newly allocated copy of the remaining characters seeds the next message. - Otherwise subtract
line-length + 2, and end after this line when the new value is nonpositive.
Unlike C303, G85 does not insert a Return into the current line or rewrite the prior status count during a short-count repair. Its numeric split and explicit remainder are observable.
The before-parse method marks a message with an invalid leading status line Unparseable, but a valid one stores Received-Date, maps the XOR-one low flags to Unseen, Deleted, Always-Show and Answered, and moves visible text past the status.
Serialization
The status writer chooses time from Received-Date, then Date (using the first numeric member where necessary), then current time. It produces a two-digit day, short month, year, time, timezone forced to a leading hyphen where needed, decimal byte count and 12-digit octal flags. The count is computed by running the actual message—including epsilon encoding and original-header choice—through a counting stream where Return counts as two characters. The real message end is first normalized to a line boundary.
G85 Unix contract
Strict boundary delta
After the first line, every line whose first five characters are exactly From starts the next message. G85 performs no C303 weekday/digit plausibility test. EOF ends the current message at the active line length. Therefore body text beginning From splits a message unless an external producer escaped it; the selected writer does not provide a general body-line escaping pass.
Envelope and visible interval
The first real line is treated as the Unix envelope and removed from the visible interval when nonempty. Its sender ends at the first space after the marker; the rest is parsed as a universal time. Address or time failures become an error object stored as Unix-From-Header and later Losing-Headers rather than immediately aborting the file.
Trailing blank lines are normalized by deleting from the last nonblank endpoint to the real end, inserting exactly two Returns, and placing the visible end before the final blank-line wrapper. Parsed envelope values fill missing Received-Date, Date and From. When it synthesizes Date or From headers, it inserts them at the visible start, adds a blank line, records the new Headers-End pointer, fills missing hosts in To/Cc/ From from the pathname host, and refreshes the summary when those values changed.
Serialization
The selected source contains two strict serializer defects that a compatibility profile MUST expose rather than repair silently:
- A list-valued Date is reduced to its minimum. A list-valued Received-Date is also reduced, but the result is mistakenly assigned to the local Date variable while the original list remains in Received-Date. The later precedence expression still selects that list and can pass it to time decoding.
- When a parsed envelope is an error object, the method constructs a fallback saved envelope property using the user's local address and Received-Date, Date or current time. If it rewrites the physical line, however, it prints the current From header value rather than that fallback address. With no current From value, the stored property can name the local user while the emitted envelope sender is empty.
Outside those defects, the rewrite predicate requires equal From-list lengths, an existing From marker, and pairwise-equal Name and Host fields; otherwise it rewrites or inserts the record and then re-normalizes the two-Return trailer. The emitted date is ctime-shaped local date text. A safety-corrected overlay MAY use the minimum Received-Date and the fallback address consistently, but MUST identify both changes.
G85 Directory contract
Index grammar
directory-index = marker CR *option-line CR CR *message-path-line CR
marker = "*Mail file directory*"
The marker must be the exact first line. Options use the Babyl option parser and stop at EOF or the first empty line. Directory-Mail-File true is forced as the first property and Version defaults to the highest Babyl version. The declared Possible list is Directory-Mail-File, Version, Append, Reverse-New-Mail, Mail, Owner, Sort, Delete-Expired, Keywords, Keywords-String, Summary-Window-Format and Reformat-Template. The settable subset omits Directory-Mail-File, Version, Keywords and Keywords-String.
Every later line is parsed as a pathname against Zmail pathname defaults; a nonempty final line is accepted even when its read also reports EOF. Structural load creates an empty message with a File-ID tuple containing the member pathname, no file info and the file tick; it does not open the member. The same pathname object is pushed onto the buffer's remembered member list, so that list is in reverse index-read order.
Lazy member parse
On first parse, a member with absent info is opened and copied into the message's real interval. Only after copy does the implementation record stream Info and member file tick. The first member line is always parsed as a v5 Babyl status line and excluded from visible text, regardless of the Directory Version option. Failure opening a member leaves the index message unchanged. Failure during copy can leave partially inserted real-interval text, while the File-ID's Info remains false and its old tick remains; there is no rollback before a retry.
Save and allocation order
Before this method runs, the common save predicate updates modified status lines and the Directory option header. For each message in sequence order, Directory then:
- allocate a File-ID if absent;
- rewrite the member only when its real-interval tick exceeds the member file tick;
- close the member, then record Info and a new tick; and
- write its pathname to the index using the Dired string representation.
Allocation uses an in-memory [next, reserved-upper) cache. When the cache is absent, it reads the first line of UID.LISP or starts at 1, then installs [lower, 0) in the buffer. When absent or exhausted, it computes upper = lower + 200, stores that upper in the in-memory cache before opening/writing UID.LISP, and only after the write returns does it return and increment the prior lower value. A UID-write failure thus leaves [lower, upper) looking usable in memory even though the durable reservation did not complete; a retry in the same buffer can allocate lower without rewriting the UID file. This source-visible partial failure MUST be preserved by strict mode or corrected only in a named safety overlay. A member pathname has decimal name, type MSG, and newest version.
The common finish method closes the index stream, records its Info, clears the stream, deletes inserted inboxes, advances the buffer tick, and sets status to :IDLE. Directory's after method then enumerates current member pathnames, deletes each previously known pathname object not MEMQ-present in that list while ignoring File-Not-Found, and finally replaces its remembered list with sequence order. Thus member writes and UID reservation precede the index commit, while stale-member deletion follows the commit and even the transition to :IDLE; no encompassing rollback exists. A non-File-Not-Found deletion error escapes with the committed index and idle status but before the remembered list is replaced.
G85 Text contract
Text is non-reparsable and is excluded from arbitrary-format choices. Its boundary callback ends only at EOF, so strict G85 input creates one large message for a nonblank file. Serialization always chooses reformatted rather than original headers, writes a fresh line after each message, emits the configured Text separator, and finishes on a fresh line.
This one-message input result differs from C303's forced source path, which terminates and publishes at every nonblank logical line. Both releases nevertheless declare Text non-reparsable, so neither result should be advertised as a supported interchange format.
G85 KBIN boundary
KBIN is a 16-bit preparsed representation with its own element type, saving state, opcode/version signature, loader, writer and upgrade behavior. The exact eight-file implementation is part of the declared historical build closure, but licensed byte sequences and recovered message content cannot be committed here.
A strict KBIN claim therefore requires a local oracle record containing:
- hashes and sizes of synthetic, locally generated fixtures rather than purchased mailbox content;
- format version, opcode and byte-order recognition result;
- parsed message/property digest and insertion order;
- writer output hash, round-trip digest and exact failure point;
- old-version upgrade and Reparse-All result; and
- source-manifest content and selection digests.
The repository report MUST include only those identities and results. Until TODO-RUNTIME-ZM-G85-KBIN-BYTES is closed, the conformance report MUST mark KBIN unavailable; it may still claim FMT-L2-G85-TEXT.
Property preservation matrix
| Format/profile | Persisted outside ordinary message headers | Not safely round-tripped by the adapter |
|---|---|---|
| C303 Rmail | Append file header | internal status, keyword registry |
| C303 Babyl v4 | five bit properties, Deleted marker, user keywords; file options | properties outside v4 bit set; malformed/unknown basic status |
| C303 Babyl v5 | ordered saved-basic set, user keywords; file options | nonlisted internal properties |
| C303 Tenex | Received-Date and four status bits | user keywords and other internal status |
| C303 Unix | envelope sender/date | arbitrary mbox escaping; unrelated internal status |
| C303 VMS | fixed/CHAOSMAIL sender/date plus parsed header values | general internal status |
| C303 Text | each written message's text and configured separator; read is unsupported | original grouping and all internal status; forced source path treats consecutive nonblank logical lines as one-line messages and faults when an end is reported with a null start on blank input |
| G85 Rmail | Append file header | internal status and keyword registry |
| G85 Babyl v4 | v4 bits and keywords; normalized file options | newer status properties |
| G85 Babyl v5 | non-ignored saved-basic properties, user keywords, original/reformatted-header relationship; file options | ignored Bad-Header/Redirected labels as basic storage labels |
| G85 Tenex | Received-Date and four status bits | user keywords and other internal status |
| G85 Unix | envelope sender/date | body From escaping and unrelated internal status |
| G85 Directory | v5 status leader, ordinary message content, per-message file identity and index order | atomicity across UID, members, index and deletion |
| G85 Text | reformatted headers/body and separator | original headers, boundaries and internal status |
| G85 KBIN | implementation-defined preparsed property graph | not claimable without the local byte oracle |
Ordinary mail header parsing may derive many additional properties in memory. That does not make them format metadata.
Failure and abort matrix
| ID | Trigger | C303 strict result | G85 strict result |
|---|---|---|---|
FMT-F01 |
wrong explicit format | parser/host-specific error; no automatic universal switch | wrong-format condition; explicit choice disables first automatic apparent-format switch |
FMT-F02 |
first automatic format mismatch | host computer normally selected before construction | abort current load, change same buffer flavor only when apparent format exists, retry once |
FMT-F03 |
explicit unsupported Babyl version | continuable error from option parse | signaled option error under load/retry machinery; an omitted Version retains default 5 |
FMT-F04 |
missing Babyl EOOH | no dedicated contained error; visible start follows status line | attach contained Bad-Babyl condition to Unparseable and stop format before-parse work |
FMT-F05 |
malformed Babyl status | Zmail error may escape parsing | contained Bad-Babyl condition in Unparseable |
FMT-F06 |
malformed Tenex first status | boundary state becomes 1; later candidate is split at index one, and before-parse always hides the first real line | malformed prefix forms one message until next probable status and is marked Unparseable |
FMT-F07 |
nonzero Tenex count ends mid-line | split line graph at the remaining-count index and add two to the prior count when parseable | truncate current line at numeric index and carry a copied remainder; prior count untouched |
FMT-F08 |
Unix body begins From |
only becomes boundary if weekday/digit heuristic passes | unconditional boundary after first line |
FMT-F09 |
Unix envelope parse error | report strings may populate sender/date slots | error object becomes Unix-From-Header and Losing-Headers |
FMT-F10 |
Text reopened through forced low-level path | unsupported by the normal chooser; consecutive nonblank lines yield one message each, but an initial or intervening blank/space-tab-only line reaches null-start pointer construction and faults; adjacent comment instead predicts no messages | one large message for nonblank input |
FMT-F11 |
Directory member missing | format absent | index message remains; lazy open fails when parsed |
FMT-F12 |
Directory UID write fails | format absent | allocation stops before member/index output, but the advanced cache upper remains and a same-buffer retry can allocate without durable reservation |
FMT-F13 |
Directory member write fails | format absent | earlier UID reservation/member writes remain; index sequence not globally rolled back |
FMT-F14 |
Directory stale-member delete fails | format absent | File-Not-Found ignored; another file error escapes after index commit and :IDLE, before remembered member-list replacement |
FMT-F15 |
message write n fails |
prior stream/file output and dirty state exposed | prior messages already written; Current-Message identifies n; retry/abort is explicit |
FMT-F16 |
output close/version failure | exact host stream state retained | buffer must not become clean merely because message loop finished |
FMT-F17 |
KBIN wrong element type/version | format absent | typed wrong-format/nonmatch behavior; exact byte result reserved locally |
No row implies a whole-file transaction. A conforming host abstraction records whether its file service created a version, superseded in place, or left a partial stream.
Exact logical-character fixtures
Fixture codec
The fixtures below are generated from ASCII text plus the token table in Line model. CR terminates each logical line. A test runner MUST record both its token stream and its host octet encoding; conformance at this layer is against tokens. This avoids pretending that Lisp-machine characters, PDP-10 file characters and modern POSIX bytes are one encoding.
For each fixture, the test oracle records:
profile, adapter, host-family, option inputs,
token-stream-before, structural-message-count,
per-message real-token-span, visible-token-span, ordered status properties,
token-stream-after-normalize, condition/restart trace, final buffer/file state
Public C303 fixture set
| Fixture | Logical input | Exact required observation |
|---|---|---|
C303-RM-01 |
*APPEND* CR + one RFC-style message + CR US CR |
Rmail selected on ITS, Append true, header excluded, one message, visible end before US |
C303-RM-02 |
message text ending abc US SP TAB FF CR |
delimiter valid at index 3; abc belongs to visible message |
C303-RM-03 |
message text ending US "x" CR then EOF |
that line is content, not a delimiter; canonical EOF delimiter closes one message |
C303-B5-01 |
Babyl Options: CR Version: 5 CR Labels: work CR US FF CR 0, unseen,, work, CR + original headers + *** EOOH *** CR + display/body + US CR |
Babyl selected; Version 5; Unseen true; one Work keyword; original interval and display interval distinct |
C303-B5-02 |
same envelope with badHeader basic label |
alias resolves to Losing-Headers |
C303-B5-03 |
v5 status with unknown basic label | Zmail status-line error at that label; no invented keyword conversion |
C303-B4-01 |
Version 4 and status D10000 {work} |
Deleted true; bit map and Work keyword decoded with Seen inversion |
C303-BAD-EOOH-01 |
valid status followed directly by ordinary header/body and delimiter | no G85-style contained Bad-Babyl condition; visible start is line after status |
C303-BAD-EMPTY-OPTION-01 |
unknown option line ending immediately after : |
array-index failure at active length; no invented empty-string value |
C303-BAD-VERSION-01 |
Version: 3 |
continuable unsupported-version condition records parsed value and selected continue/abort path |
C303-TX-00 |
valid Tenex status whose decimal count is zero, followed by a nonempty line and EOF | zero does not terminate on the status line; following text remains in that message and the visible interval begins after status |
C303-TX-REPAIR-01 |
declared count ending inside a later line that also contains a probable next status | Return inserted at expected offset, previous count increased by two, completed prefix retained, next status reused |
C303-TX-SCAN-01 |
malformed status followed by text then a probable status line | recovery state scans linewise; candidate splits at index one, leaving its first character in the malformed message and reusing only its suffix |
C303-UX-01 |
two conventional mbox messages with weekday and digits in second envelope | two messages; second envelope retained as start-next |
C303-UX-FALSE-01 |
body line From prose without a calendar token |
one message; line remains body content |
C303-UX-ESC-01 |
consecutive initial From and >From lines |
last candidate is parsed at fixed offset five despite its six-character marker; only the first real line is hidden and no generic unescape occurs |
C303-UX-PREFIX-01 |
current and saved From lists with equal first entry but unequal lengths | prefix-only comparison suppresses rewrite |
C303-VMS-01 |
FF CR FROM:<TAB> fixed sender/date, optional To then Subject, body, next FF |
VMS selected on VMS host; fixed fields consumed in exact order; next formfeed retained |
C303-VMS-CHAOS-01 |
FF CR FROM:<TAB>CHAOSMAIL... plus ordinary headers |
ordinary header parser used; fixed fallback still supplies absent date/from |
C303-TEXT-01 |
force the low-level Text reader over two consecutive nonblank lines, then separately over initial and intervening blank/space-tab-only lines | two one-line messages in the first case; null-start pointer-construction failure before publication at each blank case; normal arbitrary-format UI does not offer Text; exact runtime condition/cleanup remain the named oracle |
The public fixture generator MAY materialize these token streams as tracked test data once it records the chosen character encoding and source revision. It MUST retain the token oracle above so a POSIX newline conversion cannot silently change the claim.
G85 local semantic fixture set
The following fixture descriptions are trackable; generated bytes and mail content remain local if produced by the licensed implementation.
| Fixture | Action | Required semantic result |
|---|---|---|
G85-RM-EOF-01 |
load one Rmail message without final US |
EOF closes the message without a synthesized delimiter line |
G85-B5-01 |
parse every serializable and ignored v5 basic status property | serializable order exact; Losing-Headers, Unparseable and Redirected not emitted as basic labels |
G85-B5-NOSPACE-01 |
basic/user labels without leading spaces | accepted under Unix-RMAIL compatibility branch |
G85-BAD-EOOH-01 |
omit sentinel | contained Bad-Babyl condition stored in Unparseable; whole file remains structurally loaded |
G85-BAD-EMPTY-OPTION-01 |
parse then save an unknown option with an empty post-colon tail | parse produces empty string; generic printing reaches its character-zero defect |
G85-REFORMAT-01 |
exhaust explicit new and legacy option combinations | normalization table exact; explicit new property wins |
G85-TX-SPLIT-01 |
byte count ends inside line | exact prefix visible in first message and copied suffix seeds next; prior status-record characters unchanged |
G85-TX-NOSTATUS-01 |
malformed region then valid status | malformed region is one Unparseable message; valid status starts next |
G85-UX-FALSE-01 |
body line beginning From without a date |
it starts the next message, unlike C303 |
G85-UX-TRAILER-01 |
zero, one and many trailing blank lines | every parsed message normalizes to the exact two-Return real trailer relationship |
G85-DIR-LAZY-01 |
index three members, parse only second | structural count three; only second member opened/content-loaded |
G85-DIR-UID-01 |
allocate 201 new members | first reservation advances UID upper by 200; exhaustion reserves next 200 before returning member 201 |
G85-UX-MULTIDATE-01 |
save with list-valued Received-Date | minimum is assigned to Date but original Received-Date list still wins later precedence; record resulting time-decode failure/result |
G85-UX-FALLBACK-01 |
save an error-valued envelope with no current From header | saved envelope property receives local-address fallback while rewritten physical sender is empty |
G85-DIR-FAIL-01 |
inject failure at UID write, each member write, index line, stale delete | source-order partial files, File-IDs, ticks, cache bounds, status and remembered-path list match the failure matrix |
G85-TEXT-01 |
read a nonblank multi-line file | exactly one message; capability remains non-reparsable |
G85-KBIN-01 |
local synthetic fixtures in normal/swapped byte order and every version boundary | local-only recognition/parse/write/upgrade hashes; report contains no bytes |
Conformance procedure
Adapter enumeration
For each selected profile, enumerate the registry and compare exact name, flavor, registration order, host applicability, settable/possible/sticky options, reparsability and element type. Required denominators are:
| Profile | Registered selectable formats |
|---|---|
| C303 | 6: Rmail, Babyl, Tenex, Unix, VMS, Text |
| G85 | 7: Rmail, Babyl, KBIN, Tenex, Unix, Directory, Text |
Directory is G85-only; VMS is C303-only. Text counts even though its chooser/read capability is restricted.
Parser-state exhaustiveness
Tests MUST cover every boundary return class and every state tag:
- ITS delimiter at index zero and nonzero, permitted and forbidden suffixes, EOF;
- Tenex first-line numeric, zero, positive, malformed, mid-line split, exact-line end, EOF partial, entry-state-zero candidate loss and state-one scan recovery;
- Unix first line, later valid/false boundary, consecutive
>Fromcandidates, unequal From-list lengths, empty EOF and envelope parse error; - VMS first/later formfeed and EOF; and
- C303/G85 Text's opposite callbacks, including C303 initial/intervening blank input with a null start.
For every failure point, compare complete message count, retained next-line state, real/visible interval spans and any mutation of source text.
Option round-trip
For Babyl v4 and v5, feed every declared option as valueless, empty, string, Lisp object and invalid input where applicable. Empty generic values are failure cases in the strict profiles described above, not unconditional round-trip cases. Verify:
- first-colon and whitespace rules;
- known parser dispatch;
- unknown option retention;
- ordered update-in-place versus append/delete behavior;
- keyword spelling/order preservation;
- version transition rewriting of every message status line; and
- G85 reformatting normalization; and
- C303 empty-tail parse failure and both profiles' generic empty-string print failure.
A round trip compares the normalized logical token stream and ordered property graph; it does not require preservation of source text which the selected writer explicitly normalizes.
Save fault injection
Inject failure before open, after header character n, before/after every message, during epsilon encode, during close/info/version update, and during each Directory phase. The report MUST distinguish:
- bytes/characters accepted by the external stream;
- last complete logical record and message;
- current adapter saving state;
- buffer status and dirty ticks;
- external file/version/member identities; and
- retry, skip, abort and cleanup results.
Cross-profile differential tests
At minimum, run the same synthetic corpus through both profiles and assert these differences rather than merely allowing them:
| ID | Differential |
|---|---|
DIFF-01 |
forced Text input: C303 one message per consecutive nonblank logical line and a null-start failure on blank input, G85 one message for the whole nonblank file; both advertise the format as non-reparsable |
DIFF-02 |
Unix false From line: C303 retains when date heuristic fails, G85 splits |
DIFF-03 |
Tenex mid-line count repair: C303 mutates/increments, G85 copies suffix |
DIFF-04 |
Missing Babyl EOOH: C303 no contained-format condition, G85 Unparseable condition |
DIFF-05 |
v5 Reformatted: C303 Boolean true, G85 :IN-FILE |
DIFF-06 |
status property tables and ignored serialization entries differ |
DIFF-07 |
format set: C303 VMS versus G85 Directory/KBIN |
Observable diagnostics
A conforming implementation exposes a structured trace record for each operation:
profile, adapter, host-family, pathname-class,
selection-source, probe-order, stream-position-before/after,
buffer-state-before/after, message-index, boundary-input/result/state,
option/status parse result, mutation order,
condition, restart, partial-output, external identity
Diagnostics MUST use logical fixture identities or hashes rather than including private mail content. An unrecognized format is distinct from a recognized format whose message is malformed.
Security and rights boundary
- Never commit licensed source, purchased mail files, recovered mail, KBIN bytes or local absolute paths.
- G85 fixtures SHOULD be synthetic and created inside a disposable private harness session. Only sizes, hashes, semantic digests and original prose may enter docs.
- Public C303 logical fixtures may be tracked with the pinned public source and its license, but must contain no private addresses or imported third-party mail.
- A runtime screenshot is neither useful nor required for this nonvisual format contract. Any future UI screenshot about format selection follows the repository's separate curated screenshot policy.
- A file's presence in a public Fossil browser does not establish the rights to a separate message payload used for testing.
Known unknowns and nonclaims
TODO-RUNTIME-ZM-G85-KBIN-BYTES: exact licensed KBIN opcode stream, version upgrade, property representation and error boundaries need local synthetic-oracle closure.TODO-RUNTIME-ZM-G85-FORMATS: source behavior has not yet been exhaustively compared with each resident format method in the preserved Genera 8.5 world.TODO-RUNTIME-ZM-C303-FORMATS: the available System 303 band does not presently reach a working Zmail frame, so source-defined format behavior lacks a runtime band oracle.TODO-RUNTIME-ZM-C303-TEXT-FORCED: invoke the Text flavor's low-level reader directly on disposable consecutive-nonblank, initial-blank and intervening-blank files in a runnable matching source environment. Confirm the two one-line messages for the first fixture and record the exact condition, partial array and cleanup after the null-start pointer-construction failures. Also establish whether an uninspected wrapper ever enforces the adjacent zero-message comment. This oracle does not change Text's unsupported normal-chooser status.- This document does not specify filesystem wire encodings, QFASL layout, VLOD representation, SMTP/Chaos transport, Mailer queues, or arbitrary RFC conformance.
- “Unix” here means the selected historical adapters. Neither profile is claimed as a complete modern mbox, mboxo, mboxrd, mboxcl or mboxcl2 implementation.
Sources and verification
- LM-3 maintained System 303,
zmail/mfiles.lisp, common reader/writer and Text; verified 2026-07-19. - LM-3 maintained System 303,
zmail/mfhost.lisp, Rmail, Babyl, Tenex, Unix and VMS; verified 2026-07-19. - LM-3 maintained System 303,
zmail/defs.lisp, status-property order and Text separator; verified 2026-07-19. - LM-3 maintained System 303,
zwei/primit.lisp, buffer-pointer construction used by the forced-Text null-start analysis; verified 2026-07-19. - Symbolics, Editing and Mail, Genera 8, user-visible format comparison; verified 2026-07-19.
- Licensed local System 452.1 / Zmail 442.0 source suffixes and hashes in the evidence table and declared-build manifest, inspected 2026-07-19. No source text or recovered message payload is reproduced.
- Zmail and mail composition reimplementation specification, normative parent for lifecycle, selection, inbox, background and UI behavior.