Files
fpc-msgbase/docs/attributes-registry.md
Ken Johnson e876d98b83 0.3.5: ma.kludge shared helper, INTL/FMPT/TOPT, area auto-pop, list helpers, PKT polish
Five consumer-feedback items, one milestone:

(1) Shared FTSC kludge plumbing in src/ma.kludge.pas

  ParseKludgeLine, SplitKludgeBlob, BuildKludgePrefix,
  BuildKludgeSuffix.  Single source of truth for kludge naming,
  INTL/FMPT/TOPT recognition, and the kludge.<lowername>
  forward-compat passthrough.  Eliminates the four near-identical
  parsers MSG/PKT/Squish were carrying; JAM's FTSKLUDGE subfield
  walking also routes through ParseKludgeLine so its unknown
  kludges land in the same `kludge.<name>` slot as the others.

  Bug fix folded in: the parser previously split kludge name from
  value at the first ':' it found, which broke INTL (the value
  contains an FTN address with ':' in it).  Now picks the earlier
  of space and colon, which handles both colon-form ("MSGID: foo")
  and space-form ("INTL <to> <from>") kludges correctly.

(2) INTL / FMPT / TOPT slots in attributes registry

  FSC-4008 cross-zone routing kludges every netmail tosser carries.
  Added to JAM/Squish/MSG/PKT capability lists, parsed natively,
  emitted on Write.  Round-trip covered by tests.

(3) Unified `kludge.*` namespace for unknown FTSC kludges

  Squish's `squish.kludge.<name>`, MSG's `msg.kludge.<name>`, and
  PKT's `pkt.kludge.<name>` all collapse to plain `kludge.<name>`.
  Consumers find passthrough kludges without switching on format.
  JAM's numeric `jam.subfield.<id>` stays — those are JAM-specific
  binary subfields, not FTSC-form kludges.

(4) `area` auto-populated from base.AreaTag on Read

  When the caller passes AAreaTag to MessageBaseOpen (or sets
  the AreaTag property post-construction), every successful
  ReadMessage fills msg.Attributes['area'] unless the adapter
  already populated it from on-disk data (e.g. PKT AREA kludge).
  Saves echomail consumers from copying AreaTag into every
  message attribute manually.

(5) TMsgAttributes multi-line helpers

  GetList / SetList / AppendListItem on TMsgAttributes for the
  multi-instance attributes (seen-by, path, via, trace) that
  store with #13 between entries.  Consumers don't have to roll
  their own split/join.

Plus two PKT polish items from the same feedback round:

(6) ma.fmt.pkt.uni.DoWriteMessage now raises EMessageBase
    explicitly with a pointer to the Native API instead of
    silently returning False.

(7) TPktFile.CreateFromStream / CreateNewToStream constructors
    accept any TStream (with optional ownership), so unit tests
    that round-trip via TMemoryStream don't have to tempfile-dance.
    FStream is now TStream; FOwnsStream gates Free in destructor.

TStringDynArray moved from ma.api.pas to ma.types.pas so both
the capabilities API and the new attribute helpers can share it.

Docs sweep:

- docs/attributes-registry.md: intl/fmpt/topt added; unknown-kludge
  convention documented; multi-line helper section added.
- docs/architecture.md: ma.kludge layer surfaced; .uni adapter
  registration gotcha called out loudly with the recommended
  uses clause; area auto-pop documented.
- docs/API.md: TUniMessage section rewritten for Body+Attributes
  model (was still pre-0.2); HWM API documented; PKT cheat-sheet
  notes Native + CreateFromStream; tests/programs list updated.
- README.md: Building section flags the .uni gotcha first
  thing; ma.kludge added to features.

tests/test_consumer_round1.pas: 7 new tests covering INTL/FMPT/
TOPT round-trip on JAM/Squish/MSG, area auto-pop, GetList/SetList/
AppendListItem, PKT raise, and TPktFile in-memory stream
round-trip.

Suite: 47/47 across 10 programs (test_consumer_round1 adds 7).
2026-04-18 09:14:33 -07:00

10 KiB

Attribute registry

This document is the source of truth for attribute key names used across all fpc-msgbase backends.

TUniMessage has only two areas:

TUniMessage = record
  Body:       AnsiString;       { only the message text }
  Attributes: TMsgAttributes;   { everything else, key/value }
end;

Every header field, kludge, control line, and per-format flag a backend understands lives in Attributes under one of the keys below. Backends drop keys they don't recognise on Write (RFC 822 X-header semantics). Callers can query base.SupportsAttribute(K) before setting a key to know up-front which backends carry it.

Naming convention: lowercase, dot-namespaced. Universal / FTSC-defined keys are unqualified (from, msgid). Format- specific keys are prefixed with the format name (jam.msgidcrc, squish.umsgid, pcb.confnum).

Tier 1 — Universal headers

Every Fido format sets these on Read; every backend reads them on Write. Equivalent to the always-present headers in any classic BBS message.

Key Type Meaning
msg.num int Backend-assigned message number / index
from string Author name
to string Recipient name
subject string Message subject
date.written date When author wrote the message
date.received date When local system received
addr.orig ftn Originating FTN address (zone:net/node[.point])
addr.dest ftn Destination FTN address
area string Echo area tag (echomail)
board int Conference / board number for multi-board formats
cost int Cost in cents (FTS-1 backends)

Tier 2 — Canonical attribute bits

Boolean flags derived from the FTS-1 attribute word, plus extensions some backends carry as separate bits. Always written via SetBool(K, true); absent (or false) when not set.

Key FTS-1 Meaning
attr.private 0x0001 Private message
attr.crash 0x0002 Crash priority
attr.received 0x0004 Recipient has read
attr.sent 0x0008 Sent to destination
attr.fileattach 0x0010 File attached
attr.intransit 0x0020 In transit
attr.orphan 0x0040 No matching destination
attr.killsent 0x0080 Kill after sending
attr.local 0x0100 Originated locally
attr.hold 0x0200 Hold for pickup
attr.filereq 0x0400 File request
attr.returnreceipt 0x0800 Return receipt requested
attr.isreceipt 0x1000 This message is a receipt
attr.auditreq 0x2000 Audit trail requested
attr.fileupdreq 0x4000 File update request
attr.deleted Tombstoned (per-base)
attr.read Marked-read (per-user)
attr.echo Echomail (vs netmail)
attr.direct Direct flavour
attr.immediate Immediate flavour
attr.locked Locked (no edit)
attr.netpending Pending netmail toss
attr.echopending Pending echomail toss
attr.nokill Protected from purge

Tier 3 — FTSC kludges

Standard FTSC kludge lines, named after the kludge name without the leading ^A. Multi-line attributes (SEEN-BY, PATH, Via) join their lines with #13.

Key Type Spec Meaning
msgid string FTS-9 Globally unique message ID
replyid string FTS-9 Reply linkage to a previous MSGID
pid string FRL-1004 Producer ID (creating tosser/editor)
tid string FSC-46 Tosser ID
flags string FRL-1005 Routing/handling flags
chrs string FTS-5003 Character set declaration
tzutc string FTS-4001 Time-zone offset from UTC
seen-by multi-string FTS-4 SEEN-BY lines (one node-list per line)
path multi-string FTS-4 PATH lines (one node-list per line)
via multi-string FTS-4009 Via lines (one per relay)
intl string FSC-4008 INTL kludge: <dest-zone:net/node> <orig-zone:net/node> for cross-zone netmail
fmpt string FSC-4008 FMPT (origin point number)
topt string FSC-4008 TOPT (destination point number)

Unknown FTSC kludges

Any ^A<NAME>: <value> line whose <NAME> is not in the table above is preserved as kludge.<lowername> regardless of which backend stored it. Example: ^aXFOO: barkludge.xfoo = 'bar'.

This is the universal forward-compat slot for FTSC-form kludges the library doesn't recognize natively. All four kludge-aware backends (JAM, Squish, MSG, PKT) use the same kludge.* namespace so a consumer can find passthrough kludges without switching on format.

JAM's numeric SubField IDs that have no FTSC analogue stay namespaced as jam.subfield.<id> (those aren't FTSC-form kludges; they're JAM-specific binary subfields).

Multi-line attribute helpers

Attributes that store multiple lines (seen-by, path, via, trace) join their lines with #13. Use the typed accessors on TMsgAttributes to avoid manual splitting:

list := msg.Attributes.GetList('seen-by');     // TStringDynArray
msg.Attributes.SetList('path', list);          // joins with #13
msg.Attributes.AppendListItem('seen-by', '3/777');

Tier 4 — Format-specific keys

These are namespaced and only meaningful to the format that produces them. Other backends ignore them on Write (silently dropped — fine).

JAM

Key Type Meaning
jam.msgidcrc int Index fast-path CRC of MSGID
jam.replycrc int Index fast-path CRC of ReplyID
jam.dateprocessed unix-int Tosser timestamp
jam.passwordcrc int Per-message password CRC
jam.cost int JAM-level cost (separate from cost)
jam.timesread int Times-read counter
jam.replyto int Parent in reply chain
jam.reply1st int First child in reply chain
jam.replynext int Next sibling in reply chain
jam.attribute2 int Reserved JAM attribute2 word
jam.subfield.<id> multi Passthrough for JAM-numeric subfields with no FTSC kludge analogue

(JAM's JAM_FTSKLUDGE subfields are parsed through the shared kludge dispatcher, so their content lands in the canonical slot — msgid, intl, etc., or kludge.<name> for unknowns — rather than a JAM-specific bag.)

Squish

Key Type Meaning
squish.umsgid int UMsgID (per-area unique number)
squish.utcofs int UTC offset in minutes
squish.replyto int Reply chain parent
squish.kludge.<name> multi Passthrough for unknown CtrlInfo kludges

Hudson / GoldBase

Key Type Meaning
hudson.prevreply int Previous message in reply chain
hudson.nextreply int Next message in reply chain
hudson.timesread int Times-read counter
goldbase.prevreply int (GoldBase variant)
goldbase.nextreply int (GoldBase variant)
goldbase.timesread int (GoldBase variant)

EzyCom

Key Type Meaning
ezy.extattr int EzyCom ExtAttr byte
ezy.prevreply int Reply chain prev
ezy.nextreply int Reply chain next

PCBoard

Key Type Meaning
pcb.refnum int RefNum field
pcb.status int Raw PCB status byte
pcb.active int Active flag
pcb.echo int Echo flag
pcb.export int Export flag
pcb.extra2 int Extra2 byte (incl. Allfix-sent bit 6)
pcb.extra3 int Extra3 byte
pcb.hastags int HasTags flag
pcb.origin int Origin flag
pcb.readnum int ReadNum word
pcb.extendedstatus int Extended status byte
pcb.password string Per-message password (max 12 chars)

Wildcat

Key Type Meaning
wildcat.confnum int Conference number this message lives in
wildcat.mflags int Raw mFlags word from TMsgHeader

PKT

Key Type Meaning
pkt.cost int Packet-level cost (separate from cost)
pkt.kludge.<name> multi Passthrough for unknown body kludges

MSG (FTS-1)

Key Type Meaning
msg.replyto int Reply chain parent
msg.nextreply int Reply chain next
msg.timesread int Times-read counter
msg.kludge.<name> multi Passthrough for unknown body kludges

Per-format support matrix

X = the backend's ClassSupportedAttributes lists the key. Blank = backend has no slot for it; setting the key has no effect on Write, and the key won't appear in Attributes after a Read.

Key JAM Squish MSG PKT Hudson GoldBase EzyCom PCB WC
msg.num X X X X X X X X X
from / to / subject X X X X X X X X X
addr.orig / addr.dest X X X X X X X X X
date.written X X X X X X X X X
date.received X X X X
area X X X X X X X X X
board X X X
cost X X X X X
attr.private X X X X X X X X X
attr.crash X X X X X X X
attr.received X X X X X X X X
attr.sent X X X X X X X X
attr.killsent X X X X X X X
attr.local X X X X X X X X
attr.hold X X X X X X
attr.fileattach X X X X X X X
attr.returnreceipt X X X X X X
attr.deleted X X X X X X X
msgid X X X X
replyid X X X X
pid X X X X
flags X X X X
seen-by X X X X
path X X X X
Format-specific (<fmt>.*) X X X X X X X X X

(Always check at runtime via SupportsAttribute(K) rather than relying on this table — it can drift. The capability list each backend ships in ClassSupportedAttributes is authoritative.)