Home / Projects / Orchestrata Ultimate / Decisions / ADR index / ADR-0038
ADR-0038: The CXP envelope schema supersedes the pre-CXP Postman schema
| field | value |
|---|---|
| Status | Accepted — Accepted (de facto, by commit 2883703a, 2026-07-06) |
| Date | 2026-07-06 |
| Register | factory (docs/adr, 4-digit ids) |
| Source | docs/adr/0038-cxp-envelope-schema-supersedes-postman-types.md — permalink at 6562c5d08 (unchanged on master since that commit) |
| Wiki register entry | ADR-0038 (wiki status: Accepted) |
The record
- Status: Accepted (de facto, by commit
2883703a, 2026-07-06) - Date: 2026-07-06 (the decision, as executed) · first written 2026-08-19, corrected same day
- Spec: SPEC-002 (Postman); SPEC-004 (CXP)
- Relates to: commit
2883703ae5ffa84c4c9794e1a6c94d67111aa3f5; an in-flight, uncommitted restoration of three fields, present in the working tree as of this writing (see Consequences)
This is a reconstructed ADR — and its own first draft got the history wrong. It was not written when the decision was made; it was written six weeks later, on 2026-08-19, to back-fill a record that never existed — the same failure mode this log's own README describes as the reason it exists at all: "the rationale... existed nowhere on disk." That first draft asserted
sender/recipient/timestampwere RENAMED fields of the canonicalpostman.types.MessageEnvelope, thatcontent_typeMOVED off it, and thatcorrelation_id/compressed/size_byteswere DROPPED from it and absent for six weeks. None of that happened.git show 2883703a^:src/orchestrata_ultimate/postman/types.py | grep -cE 'correlation_id|compressed|size_bytes'returns0— those fields never existed on the canonical class, before or after the commit — andgit show 2883703a --statshowspostman/types.pyis not even in that commit's diff. What actually happened, and why the mistake is itself informative, is below. Every claim in this corrected version cites a commit sha or afile:line. Where the historical record is genuinely silent — specifically, on why the port produced two differently-shaped classes with the same name in the first place — this document says so directly instead of supplying a plausible-sounding reason. If a genuinely contemporaneous record of that decision surfaces later, it supersedes the relevant section here.
Context
src/orchestrata_ultimate/postman/ carried two same-named MessageEnvelope classes with two
different schemas, in two different files:
postman/postman_types.py(848 lines, deleted 2026-07-06) — the pre-CXP schema:MessageEnvelopehadsender,recipient,timestamp, a nativecorrelation_id, and its owncontent_typefield living directly on the envelope; a pairedMessagePayloadtypeddata: byteswith its own (second, redundant)content_typepluscompressed: boolandsize_bytes: intbookkeeping fields, docstring "Supports compression and size tracking" (pre-deletion file,MessageEnvelopeat lines 128–145,MessagePayloadat lines 220–232, retrievable viagit show 2883703a^:src/orchestrata_ultimate/postman/postman_types.py).postman/types.py— the CXP-native schema:to,from_,thread_id,subject,created_at, frozen "per CXP protocol requirements." Nosender/recipient/timestamp, nocorrelation_id, nocontent_typeon the envelope at all —MessageEnvelopehas never had one. This shape matches SPEC-004's own packet definition —message_id,from_agent,to_agent,thread_id,subject,payload(specs/004-cxp/spec.md:44-50) — and its immutability requirement, "Prevent modification of immutable headers (message_id, from_agent, to_agent)" (specs/004-cxp/spec.md:72).
This shape is not new to 2026-07-06 and was never renamed from anything. git log --follow
on postman/types.py reaches back to the initial porting-phase commits (70b68322, 5322ba6d),
predating this repo's own history in any granularity this investigation can resolve.
git show 70b68322:src/orchestrata_ultimate/postman/types.py shows MessageEnvelope already
using to/from_/thread_id/subject/created_at at that very first commit, with no
correlation_id field yet, and MessagePayload already carrying content_type (never
MessageEnvelope) with no compressed/size_bytes yet. By 2026-07-06 postman/types.py had 19
production importers to postman_types.py's zero.
Commit 2883703a's diff never touches postman/types.py at all. git show 2883703a --stat:
src/orchestrata_ultimate/postman/postman_types.py | 848 ----------------------
src/orchestrata_ultimate/types.py | 2 +-
tests/postman/test_coverage_envelope.py | 20 +-
tests/postman/test_postman_types_coverage.py | 777 --------------------
Four files change: the twin is deleted outright (848 lines), an unrelated top-level
src/orchestrata_ultimate/types.py gets a 2-line edit, and two test files are updated/deleted.
postman/types.py — the class this ADR is about — is not one of them. The decision this ADR
records is not the original design of the CXP shape (that predates it by many commits) and it is
not a rename or a field drop performed on the canonical class (the diff never touches that file)
— it is commit 2883703a's act of deleting a same-named, differently-shaped, zero-importer twin
class in a different file. Its message (quoted in full, git show 2883703a):
refactor(postman): delete dead postman_types twin; fix envelope name-collision test (W3.3 item 1)
postman_types.py had 0 production importers vs types.py's 19 — and the twinMessageEnvelope classes had DIFFERENT schemas (sender/recipient/timestamp vsto/from_/created_at): a name collision, not a copy. test_coverage_envelope.pyrepointed to the canonical class EnvelopeHandler actually constructs.
That message is accurate and is the anchor for the corrected history above — "a name collision,
not a copy" is the load-bearing phrase this ADR's first draft failed to take literally. The
reasoning given is reachability (0 production importers) plus the observation that the two
same-named classes' schemas differed. It is not a field-by-field capability comparison of the two
schemas. That absence — not "silent drops from the canonical class," which never happened — is
the central fact this ADR exists to record: three fields that existed only on the deleted twin
(correlation_id, compressed, size_bytes) had already been marked satisfied in the
requirements corpus, and stayed marked satisfied after the twin implementing them was deleted,
because nothing checked whether the surviving, actually-imported class also had them.
specs/002-postman/spec.md:244-257 (the Postman spec's own MessageEnvelope code block) still
documents sender/recipient/timestamp/content_type on the envelope — the twin's shape, not
the canonical one — and was never updated to match the class that has been canonical since the
initial port. The requirements checklist (specs/002-postman/checklists/requirements.md:424-432,
FR-001 "Envelope Fields") is in the same state: every item, including
REQ-002-FR1-007: correlation_id field (for request-response) (line 430) and
REQ-002-FR2-004: Compressed payloads (line 457), is checked [x] under field names that match
the twin, not the class that was actually shipped.
A rubber-stamp finding, not just a stale-doc one. specs/002-postman/review-2026-03-25.md,
lines 347–365, list all 19 FR-001 sub-requirements (REQ-002-FR1-001 through REQ-002-FR1-019,
including REQ-002-FR1-007 correlation_id and REQ-002-FR1-009 content_type) as PASS, and
every single row cites the identical code location, envelope.py:35, as evidence. Nineteen
different requirements citing one line is not nineteen independent verifications; it is one
citation copy-pasted nineteen times. That review passed REQ-002-FR1-007 months before the twin
carrying correlation_id was even deleted, on evidence that could not have distinguished the twin
from the canonical class.
Why the port produced two differently-shaped classes with the same name is not recorded, and
this document does not invent an answer. plans/port-from-orchestrata.md:244 lists
envelope.py (671) and plans/port-from-orchestrata.md:251 lists
types.py (672) + postman_types.py (829) side by side as port inputs, with no note on why two
type-definition files were both ported into the same package with a colliding class name, nor on
which one the port intended to be canonical. That plan is a file-size manifest, not a design
rationale. No other contemporaneous source found in this investigation addresses it either. This
is left explicitly unrecorded, not reconstructed.
Out of scope: cxp.CXPPacket (src/orchestrata_ultimate/cxp/packet.py, re-exporting
cxp._packet_impl.CXPPacket) is a distinct class at the CXP transport layer with its own
from_agent/to_agent header fields and its own payload-compression tracking
(PayloadManager.get_payload_size). Postman's MessageEnvelope/MessagePayload are a
different layer that now mirrors CXP's envelope shape; this ADR does not touch CXPPacket
itself, and postman/types.py's own docstring is careful to say so.
Decision
postman.types.MessageEnvelope's CXP-native shape (to/from_/thread_id/subject/
created_at) is canonical, and always has been — it is not a decision this ADR is recording
the making-of; it predates commit 2883703a by many commits. What commit 2883703a decided was
narrower: the deleted twin, postman_types.MessageEnvelope/MessagePayload, contributes
nothing further — not because every one of its fields was redundant, but because it had zero
production importers. Three of its fields (correlation_id, compressed, size_bytes) turned
out not to be redundant with anything on the canonical class; they are added to the canonical
class for the first time by the in-flight restoration described under Consequences, as direct
spec conformance, not as recovery of something the canonical class used to have.
Twin field (postman_types, deleted 2026-07-06) |
Canonical field/property (postman.types) |
Relationship |
|---|---|---|
MessageEnvelope.sender |
MessageEnvelope.from_ (native since 70b68322) + read-only sender property (types.py:213-220) |
Not a rename. The canonical class always used from_. The property maps the twin's field name onto it, for code/docs written against the twin. |
MessageEnvelope.recipient |
MessageEnvelope.to (native) + read-only recipient property (types.py:222-229) |
Not a rename — same basis as sender above. |
MessageEnvelope.timestamp |
MessageEnvelope.created_at (native) + read-only timestamp property (types.py:231-238) |
Not a rename — same basis as sender above. |
MessageEnvelope.content_type (twin envelope's own copy) |
MessagePayload.content_type (native since 70b68322); MessageEnvelope.content_type property raises (types.py:240-262) |
Not a move. The canonical MessageEnvelope has never carried content_type at any point in its history — it has always lived solely on MessagePayload. The deleted twin, unusually, carried content_type on both its MessageEnvelope and its MessagePayload — a redundancy internal to the twin, not evidence of a relocation in the canonical class's lineage. |
MessagePayload.data: bytes |
MessagePayload.content: str |
Different representation by design since the initial port, not an in-place retype of a shared field's history. |
MessageEnvelope.correlation_id (twin, native) |
MessageEnvelope.correlation_id |
First-time addition to the canonical class (this restoration), required by REQ-002-FR1-007 (specs/002-postman/spec.md:253; checklist specs/002-postman/checklists/requirements.md:430, marked [x]). Never a canonical field before; never "dropped" from it. |
MessagePayload.compressed (twin, native) |
MessagePayload.compressed |
First-time addition, required by REQ-002-FR2-004 (specs/002-postman/checklists/requirements.md:457, marked [x]). Same basis as correlation_id. |
MessagePayload.size_bytes (twin, native) |
MessagePayload.size_bytes |
First-time addition, same basis — no requirement names the field literally (REQ-002-FR2-004 describes the compression capability, not a field), but the twin's own design paired it with compressed as bookkeeping for the same capability. |
The false-confidence finding
This is the finding this ADR exists to record, replacing the "silent drop" narrative its first draft told.
The requirements were marked satisfied because the traced implementation lived on a class
nobody imported. REQ-002-FR1-007 (correlation_id) and REQ-002-FR2-004 (compressed
payloads) were checked [x] in specs/002-postman/checklists/requirements.md and cited PASS
in specs/002-postman/review-2026-03-25.md:353 — both citing the same envelope.py:35 that
lines 347–365 cite for every other FR-001 requirement, PASS or not. The actual field these
requirements described existed only on postman_types.MessageEnvelope/MessagePayload, the twin
with zero production importers. EnvelopeHandler.create() — the function that constructs every
MessageEnvelope the running system ever produces — never imported that twin and never
constructed one. So the requirement was satisfied on paper, by a class that never ran in
production, while the class that did run in production never carried the field. That is a
false-confidence failure: a requirement ticked, a review passing it, an implementation on
dead code — not a regression, because there was never a moment when the canonical, running
class had the field and then lost it.
The distinction between the three fields is in how findable the gap was, not in whether it
mattered. correlation_id had a field-named anchor in the requirements checklist
(REQ-002-FR1-007: correlation_id field, line 430) — a check comparing that literal string against
the canonical class's actual field surface could, in principle, have caught the mismatch.
compressed/size_bytes did not: REQ-002-FR2-004 ("Compressed payloads," line 457) names a
capability, not a field, so no literal-string check could have anchored it to a specific
dataclass attribute at all. Prior to today's restoration, EnvelopeHandler.create()
(postman/envelope.py, pre-restoration HEAD) accepted a correlation_id parameter, documented it
("For request-response correlation," line 80), and then silently discarded it — its own body
carried a comment claiming # Note: content_type and correlation_id belong to MessagePayload, not MessageEnvelope (line 136), which was false: MessagePayload never had correlation_id either.
Today's restoration threads the parameter through (correlation_id=correlation_id) and corrects
that comment (envelope.py:137-142).
The lesson: the commit's stated justification — reachability (0 production importers) — is
a true and sufficient reason to delete a class nobody imports. It is not, on its own, sufficient
evidence that a requirement checklist referencing that class's field names was ever re-verified
against the surviving class. When two same-named classes have genuinely diverged — the commit
message itself said so — a reachability justification for deleting one answers "is this code
dead," not "does the requirement it was cited for still hold." Those are different questions, and
this incident is what happens when a review answers only the first one nineteen times in a row
and calls it nineteen verifications.
Consequences
- Three requirements —
REQ-002-FR1-007(correlation_id), and the field-level portions ofREQ-002-FR2-004(compressed / size_bytes bookkeeping) — were marked satisfied inspecs/002-postman/checklists/requirements.mdandPASSinspecs/002-postman/review-2026-03-25.mdon the strength of a class,postman_types.*, that never ran in production and was deleted on 2026-07-06. The canonical, actually-constructedMessageEnvelope/MessagePayloadnever carried these fields at any point before this ADR's restoration — there is no window in which the canonical class "had" them and then lost them. - Only
correlation_id's gap was structurally findable by a check comparing requirement field names against the canonical class's actual attributes, because the checklist happened to name the field literally.compressed/size_byteswere invisible to any such check by construction, becauseREQ-002-FR2-004names a capability, not a field — no literal-string anchor exists for it to check against. - A restoration is in flight, uncommitted, as of this writing (
git diff HEAD --stat, 2026-08-19): 4 source files (postman/types.py,postman/envelope.py,postman/payload.py,postman/message_store.py) and 8 test files, roughly 390 insertions / 29 deletions. It adds all three fields toMessageEnvelope/MessagePayloadfor the first time, threadscorrelation_idthroughEnvelopeHandler.create(), threadscompressedthroughPayloadHandler.create_payload(), and adds the Redis cache round-trip for bothcompressedandsize_bytesinMessageStore. It does not add SQLite schema columns forcompressed/size_bytes— the SQLite path inferscompressedfromcontent_typeinstead (message_store.py, restoration diff) — so that gap is reduced, not fully closed, by this restoration. This ADR does not claim the restoration is complete, tested, or merged; that is separate, ongoing work this record does not speak for. - The lesson, as a consequence rather than a rule: when a requirement checklist is verified
against a class, and that class is later deleted as unreachable, the checklist's
[x]marks need to be re-verified against whatever class actually survived — reachability answers "is this dead code," not "does the class that lives on still satisfy what this was checked against." A rubber-stamp review that cites one line for nineteen different requirements cannot catch that gap even in principle, because it never looks at the requirement-specific evidence at all. - A second consequence, about this ADR itself: this document's own first draft (2026-08-19)
made exactly the mistake described above one level up — it inferred a "rename" and a "silent
drop from the canonical class for six weeks" from the fact that a requirement checklist named
fields that matched the twin, without checking
git show 2883703a --statto confirm which file the commit actually touched. The correction here was itself only caught by that same check.
Provenance
Reconstructed 2026-08-19, corrected same day, from: commit 2883703ae5ffa84c4c9794e1a6c94d67111aa3f5
(2026-07-06, Mohamed) and its parent's deleted postman_types.py
(git show 2883703a^:src/orchestrata_ultimate/postman/postman_types.py); git show 2883703a --stat (confirms postman/types.py is not in that commit's diff); git log --follow and
git show 70b68322:src/orchestrata_ultimate/postman/types.py (confirms the canonical class's
shape predates the commit); the current postman/types.py twin-compatibility-mapping docstring;
specs/002-postman/spec.md, specs/002-postman/checklists/requirements.md, and
specs/002-postman/review-2026-03-25.md (lines 347-365, the rubber-stamp citation pattern);
specs/004-cxp/spec.md; plans/port-from-orchestrata.md:244,251; and a diff of
postman/envelope.py, postman/payload.py, and postman/message_store.py at HEAD against the
in-flight, uncommitted working-tree restoration. No contemporaneous record of why the port
produced two differently-shaped same-named classes was found in the commit, the spec, the
requirements corpus, or the port plan; per the standard this log holds itself to, none is invented
here.
Related ADRs
No other ADR is named in this record.
Rulings cited
None.