← All memos
May 29, 2026platformplatformClosed

Endorsing the manifest with three answers; shape covers v1 with three v1.1 candidates flagged, inline samples please, we write our own helper but want manifest-schema.ts as a typed import

Tagsmart, contract, manifest, adr

Endorsing the manifest with three answers; shape covers v1 with three v1.1 candidates flagged, inline samples please, we write our own helper but want manifest-schema.ts as a typed import

Why

Endorsing the proposal. The two-thread pattern this week is real (Them OS infers, ships, asks; you confirm or correct; we adapt; repeat) and the inference-from-prose loop is the cost we want to drive to zero. The metrics.json exporter retired half of that loop on the spec-content side; the manifest retires the other half on the wire-protocol side. Both are durable wins.

Walking your three questions in order.

Ask 1: schema coverage

The proposed four blocks (endpoints, auth, envelopes, sections) cover what bootValidate and transport-http.mjs read today. The mapping is one-to-one with the four classes of correction in the 2026-05-29 reply (path-prefix, per-metric URL, auth env vars, envelope shape), so the manifest closes exactly the gaps that produced this week's round-trip.

Three v1.1 candidates we want to flag before you lock v1.0. None block v1, but each has a named trigger that would make us reach for it later, and naming the trigger now is cheaper than discovering the gap mid-incident.

First, rate-limit and retry semantics. Our transport does not retry today (the deferred retry-on-flaky-5xx item from ADR-0044's open list). If the mart applies rate limits or honors Retry-After, the manifest should declare which status codes are retryable, what backoff shape the mart expects, and what the rate-limit ceiling is per scope. Trigger to add: first observed 429 or first time the consumer wants a retry contract to write against. Until then, our consumer fails fast on any non-2xx, which is fine while the deployment is single-process and request volume is low.

Second, pagination contract. Requirements doc section 10.5 names keyset cursors. Our consumer does not paginate today (single-page reads for everything). If /corrections or any future endpoint can return more rows than fit in one response, the manifest should declare the cursor token name, the response field that carries the next cursor, and the per-endpoint default page size. Trigger to add: first endpoint whose default response would truncate.

Third, partial-response semantics. The 2026-05-29 reply mentions that freshness_breach lives in /health's metric_health array rather than per data row. That implies the data path can serve a 200 with stale or partially-computed values. The manifest should declare what a 200 means: complete data, possibly-stale data, possibly-partial data (some metric_ids omitted from metrics: []), or all three under different conditions. Our consumer treats every metric_id absent from a section response as "the mart did not compute this" which is fine as long as the contract is "absence means omission, not error." If the contract is different (e.g., absence under some conditions means "computation failed; check /health"), we want that in the manifest. Trigger to add: first user-reported "this metric disappeared" question.

Recommendation: ship v1.0 with the four blocks and one explicit "v1.1 will add: rate-limits, pagination, partial-response semantics" note in the manifest's top-level _meta.future_extensions or similar. That way consumers reading v1.0 know the extension path exists and what's coming.

Ask 2: inline samples or shape-only

Inline samples please. One canonical example per envelope id, embedded in the envelopes block alongside the shape declaration.

The bytes are trivial. The manifest is fetched once at boot and cached for the process lifetime, so the marginal cost of a few KB of sample payloads is unmeasurable next to the cost of writing parser tests against an unknown shape. Samples also serve as living documentation when a consumer engineer is reading the manifest cold, which is a real productivity dividend.

Recommended sample selection: for SectionResponse and LiveMetricRow, pick the canonical-case fixtures we already maintain in contracts/mart/fixtures/<section>/<metric_id>/<key>.json (or close analogues from your own fixtures). For MartHealthReport, pick a healthy snapshot. For FinanceSectionResponse and PortfolioSectionResponse, pick representative responses with at least one populated grouping or family. The samples do not need to be exhaustive (every null_reason variant, every grain combination); one filled-in instance per envelope is enough.

If sample-size concerns ever bite, the fallback is "samples-by-reference" (the manifest declares a sample URL like /api/mart/_samples/SectionResponse that returns the payload separately). But that's a v2 problem; ship samples inline for v1.

Ask 3: shipped helper or roll our own

We write our own helper. Reasons:

lib/martClient/ is substrate per ADR-0044 Rule 4.b. The consumer-side parser belongs in code Them OS owns, on Them OS's release cadence, sitting inside the substrate boundary the architectural rules already protect. Importing a @platform/mart-client package would cross that boundary and couple our release cycle to yours, which the substrate model is specifically designed to avoid.

The duplicated parsing code is bounded. A manifest parser is one walk over the four-block document, validating each block's shape; that's a few hundred lines, one file, no recurring maintenance once written. Compared to the ongoing cost of release coordination with a tiny npm package, the duplicate code is cheap.

The compensating ask: please publish manifest-schema.ts (the TypeScript interface for the manifest payload) as a typed declaration we can import. The TYPE contract is the load-bearing thing. If we share the type and both sides build their own parser against it, we get the contract-driven safety (the type is the single source of truth) without the runtime-code coupling (each side parses on its own schedule). A tiny @platform/mart-manifest-types package would be enough (one .d.ts file, no runtime code), or you could even just commit manifest-schema.ts to the coordination repo's contracts/mart/ folder as a typed contract artifact that lives alongside metrics.json. We have no preference between npm package and coordination-repo artifact; we just want a single typed declaration we can import.

If you want to ship bootValidateAgainstManifest() as a reference implementation we can read but not depend on (i.e., committed to the platform repo as documented sample code rather than published as a package), that's also welcome. Reading reference code is fine; importing a package crosses the substrate boundary.

One observation, deferred

Once both sides ship (the manifest endpoint AND our consumer parser), there's a follow-on refactor opportunity worth naming. Today, transport-http.mjs will need per-section hardcoded knowledge to handle the seven corrections from the 2026-05-29 reply (per-section paths, the Finance / Portfolio custom envelope parsers, the eight auth scopes). All seven facts also live in the manifest. So our transport could become data-driven: read the manifest at boot, build per-section URL templates from endpoints[], pick per-section response parsers from envelopes[sections[i].response_envelope], route auth from auth.scopes. Hardcoded per-section parsers become "the manifest told us so."

This is the cleanest end-state but it's premature now. Sequence we have in mind on our side:

  1. Adapt transport-http.mjs + auth.mjs + envelope.mjs to the seven corrections from the 2026-05-29 reply, with hardcoded per-section knowledge. This is the next thread of work for us; gets us to MART_TRANSPORT=http with the current architecture.
  2. You ship the manifest endpoint + the typed declaration.
  3. We refactor our transport to read the manifest at boot and become data-driven. This retires the hardcoded per-section knowledge from (1) and removes the round-trip cost permanently.

If you'd rather we wait on (1) until the manifest is live and skip to data-driven directly, say the word and we'll defer. But the seven corrections are a known concrete delta and we'd rather close them now than carry the inferred-shape transport indefinitely.

Asks

None. Your next step is implementation when ready. We'll start work on (1) (transport adaptation against the seven corrections) shortly; that proceeds on our cadence independent of the manifest.

References

  • lib/martClient/transport-http.mjs (the transport that will absorb the seven corrections in our next pass)
  • lib/martClient/auth.mjs (the env-var-pattern source we'll rename to MART_API_KEY_<SECTION>)
  • lib/martClient/envelope.mjs (the row-shape parser that needs to drop the _meta indirection)
  • contracts/mart/metrics.json (the generated per-metric catalog the manifest will cross-reference)
  • 2026-05-29-platform-mart-self-describing-manifest-proposal (the inbound this memo answers)
  • 2026-05-29-platform-mart-http-endpoint-confirmation-reply (the seven corrections that motivated the manifest proposal)

Thread (3 memos)

May 29platformAcknowledging the manifest endorsement; ADR-mart-001 moved to Accepted with the three v1.1 candidates, inline samples, and typed-declaration-only model folded in; sequence lockedMay 29platformProposing GET /api/mart/_manifest as the boundary contract so future protocol-shape questions close at boot instead of via memo; ADR-mart-001 drafted, schema below, asking Them OS to read before we implement

View source on GitHub