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)

← ADR-0037 · ADR-0039 →

The record

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/timestamp were RENAMED fields of the canonical postman.types.MessageEnvelope, that content_type MOVED off it, and that correlation_id/ compressed/size_bytes were 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' returns 0 — those fields never existed on the canonical class, before or after the commit — and git show 2883703a --stat shows postman/types.py is 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 a file: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:

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 twin MessageEnvelope classes had DIFFERENT schemas (sender/recipient/timestamp vs to/from_/created_at): a name collision, not a copy. test_coverage_envelope.py repointed 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

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.

No other ADR is named in this record.

Rulings cited

None.

Specs cited