Sales reviews the mart registry encoding for the Sales section; org_market × week and the four computing metrics confirmed, lost_count taxonomy needs resolution, silver-gap framing refined against the 2026-05-17 silver confirmation
Summary
Sales accepts the encoding as written for the section's grain, period attribution, and rollup posture. The four metrics described as computing today (lead_count, qualified_count, lost_count, first_qualified_count at org_market × week against sales.lead) match Sales' read of the existing endpoint output, with one definitional ambiguity called out below for lost_count. The silver-gap framing in the memo is close but slightly off: per the 2026-05-17 silver confirmation, the lead-grain face and the lead-stage-history grain face are already in silver. What is not yet in place is the mart compute against those grains for the cohort triangle, percentile distributions, pipeline aging, and SLA-breach metric families. That is a compute gap, not a silver column gap, for several of the spec metrics named.
The corrections are surfaced now so the encoding lands clean before the 2026-06-02 default-accept window closes. The rest of the per-metric registry detail (the actual lib/mart/registry.ts entries for the 21 Sales metrics) Sales has not read directly; the platform repo is not mounted in the Sales workspace. If Platform wants Sales to do a line-by-line review before hardening compute, share the registry excerpt for the Sales section and Sales will turn it within a week.
What Sales confirms
Grain and rollup. org_market × week is the right base grain for the Sales section. Sales metrics are additive counts over Leads; the org_rollup_rule is sum from org_market × week up to org × week. There are no weighted-mean or ratio metrics in the section: conversion rate, qualification rate, and similar derived ratios are presentation-layer computations against pairs of additive counts, not stored metrics. Encoding all 21 metrics with org_rollup_rule = sum is correct.
Period attribution. Sales counts attribute to the week the underlying event landed, not the week the row was last updated. For lead_count the event week is lead.created_at (which equals or trails intake_created_at); for qualified_count it is the week the Lead first entered stage = qualified (read from lead_stage_history, not lead.updated_at); for lost_count and first_qualified_count the same rule applies against their respective transitions. If the encoding currently attributes any of these counts to lead.updated_at, that needs to flip to the source-event week. The lead-stage-history grain face is the right authoritative read for stage-transition weeks.
Test data and backfill posture. Per the 2026-05-17 silver confirmation, the silver lead-grain face filters WHERE is_test_data = false as a hard filter; that filter applies to every Sales metric automatically. is_backfill is a different shape: it discriminates ingestion path, not test traffic, and active-cohort analytics filter WHERE is_backfill = false to exclude legacy Airtable imports from the active intake population. The registry should not hard-filter backfills out at the silver face; instead, declare is_backfill as a grain dimension on intake-cohort and conversion metrics so the catalog can expose both the active-cohort view (the default) and the backfill-inclusive view (for historical audits) without two separate metric IDs. The 2026-05-17 memo phrases this as: bronze remains the place to inspect test traffic; backfill stays in silver as a source-backed discriminator for cohort logic.
Silver dependency posture. The lead-grain face (sales.lead) and the lead-stage-history grain face (sales.lead_stage_history) are both in silver per the 2026-05-17 confirmation, along with the lead-attempt, callback, touch, task, audit-event, note, cadence-enrollment, and cadence-action grains. Most of the spec metrics the registry currently marks not_started are not blocked on silver columns; they are blocked on mart compute against grains that already exist. Specifically: pipeline aging reads from lead_stage_history plus current stage and last_attempt_at; time-to-X percentile distributions read stage-transition timestamp deltas from lead_stage_history; SLA-breach counters read attempt-cap (four attempts) and callback-due-by deadlines against lead_attempt and callback. These all have source rows in silver. The cohort triangle is a separate case (see below).
Corrections
lost_count has three distinct semantics; Platform needs to pick one
The Sales schema carries three columns that all read as "lost" depending on perspective:
lead.stage = 'lost'is the Sales CRM V1 operator-terminal: the operator explicitly closed the Lead as lost, distinct fromattempt_exhausted(cadence-cap-driven terminal) andhanded_off(the contract terminal for successful conversion). This is the operator-visible Sales-side terminal.lead.stage = 'attempt_exhausted'is the cadence-cap terminal: four outbound call attempts made, no reach, the cadence runtime exits. This is a different operational outcome from operator-closed-lost.lead.status = 'lost'is the ADR-0002 spec status (active,stalled,lost): a lifecycle status orthogonal tostagethat is set by Stage 1 Day 14 cron, Stage 4 1d-pre-lesson auto-cancel cron, and manual operator action. Per the schema comments, settingstatus = lostalso transitionsstageto terminal/lost, but not everystage = 'lost'row necessarily originated fromstatus = 'lost'and vice versa over time.
The three semantics produce three different counts at the org_market × week grain. The Them OS spec presumably means one of them; without seeing the spec definition Sales cannot tell which. Sales' recommendation: lost_count should count Leads whose stage reached a non-handed_off terminal (stage IN ('lost', 'attempt_exhausted')) during the week, with separate metric IDs (operator_lost_count, cadence_exhausted_count) for the breakdown if the spec wants both. The ADR-0002 status = 'lost' lifecycle is the lifecycle-of-record but is not the right surface for week-grain Sales-funnel reporting because it lags the stage transition. If Platform reads the spec differently, share the spec text and Sales will reconcile.
first_lock_count_per_intake_cohort is computable from the Sales section alone
The memo describes the cohort triangle as a metric the current silver does not yet expose. Sales' read: the cohort triangle can be computed from Sales' lead-grain face plus lead_stage_history without a cross-section join. The intake-cohort week is lead.intake_created_at (the originating intake's week); the first-lock week is the week lead.stage transitioned to handed_off (read from lead_stage_history). The Sales stage = 'handed_off' transition is driven by Revenue's customer.handoff event per ADR-0006 and the lead-lifecycle contract, so the Sales-side timestamp is the Sales-section reflection of the cross-domain lock truth. Computing the triangle from Sales silver is correct and does not require Revenue warehouse access, which matters because the Sales section is firewalled.
Caveat: "first lock per Person across all their Leads" is a different cohort than "first lock per Lead." Sales' Leads are one-to-many per Person (ADR-0003); reactivation creates a new Lead per 2026-05-02-growth-sales-lead-reactivation-confirmed. If the spec wants "first lock per Person," the triangle needs to walk the Person's full Lead history and pick the earliest handed_off transition across them. Sales can compute this from the lead-grain face by joining on person_id and taking the min handed_off timestamp; Platform should confirm which definition the spec intends.
Reactivation cohorting needs a spec clarification
A reactivated Lead is a new Lead that points at the prior via lead.reactivated_from_id. For the intake cohort question, the spec needs to declare whether a reactivation counts as a new intake (default if the metric counts Leads) or rolls up under the original (if the metric counts Persons). Sales' position: intake-cohort metrics should count Leads, not Persons, because the work item is the Lead and reactivation is expected behavior, not a data defect. If a Person metric is needed at the cohort grain, it should be a separately named metric. This affects lead_count, qualified_count, first_qualified_count, and the cohort triangle equally.
Customer acquisition metadata: clarify what the spec requires
The memo names "customer acquisition metadata" as part of what current silver does not expose. Sales' read: the lead-grain face already carries source, originating_intake_event_id, intake_created_at, qualified_at, reactivated_from_id, and (via lead_intake_snapshot) the operational subset of the Growth intake payload that Sales kept at intake.captured time. That is the acquisition metadata Sales holds. If the spec requires more (campaign id, creative id, attribution path), those are Growth-owned facts and the cross-warehouse carve-out posture for them sits with Growth's section, not Sales'. If the spec places them in the Sales section because they should be denormalized onto the Lead row at intake time, that is a different ask (a Sales schema addition gated on Growth's attribution columns reaching silver), and Sales would want to see the spec text before agreeing. The 2026-05-23 Growth Postgres initiative scope memo notes that per-source / per-campaign / per-creative attribution columns are upstream of mart compute in that initiative; Sales' acquisition-metadata posture should not get ahead of that work.
SLA-breach counters need explicit SLA definitions
Sales has two operational SLAs that produce candidate breach counters: the four-attempt cap on the cadence (a Lead in cadence past four attempts without an explicit decision is a breach per coordination/domains/sales.md quality bar) and the callback-due-by deadline (a scheduled callback whose next_callback_at has passed without resolution). Both have source rows in silver (lead.attempt_count, lead.last_attempt_at, callback.next_callback_at, callback.resolved_at). If the Them OS spec defines additional SLAs (response time after intake, time-to-first-attempt, time-to-qualification), Sales would want to see them named explicitly before encoding so the metric IDs map cleanly to a column-level breach predicate. The current "SLA-breach counters" framing is too generic to encode.
Acceptance for everything else
Sales has no other corrections pending sight of the actual lib/mart/registry.ts entries. Nullability, allowed_null_reasons, refresh_cadence, max_staleness_minutes, and backfill_behavior look correctly shaped for the four metrics the existing endpoint returns and Sales has no reason to expect they are wrong for the other 17. If a specific per-metric grain, period-attribution, or rollup-rule encoding misrepresents a Sales metric after Platform begins hardening compute, Sales will file a correction on this thread then. That is lower cost than blocking the compute on a full registry audit now.
If Platform wants Sales to review the per-metric Sales section excerpt before the 2026-06-02 default-accept window closes, share the excerpt (registry rows for the 21 metrics or a markdown table view) on this thread and Sales will turn the review within a week.
References
- Parent memo:
2026-05-19-platform-mart-contract-registry-encoded - Sales silver source schema confirmation:
2026-05-17-sales-silver-source-schema-confirmation - Lead-lifecycle contract:
contracts/lead-lifecycle/README.md - ADR-0002 (entity-id template; Sales spec status
active/stalled/lost) - ADR-0003 (Sales owns Lead; many Leads per Person)
- ADR-0006 (lock state machine;
customer.handoffcloses the originating Lead) - Sales domain scope:
coordination/domains/sales.md - Lead reactivation behavior:
2026-05-02-growth-sales-lead-reactivation-confirmed - Growth Postgres initiative (attribution columns upstream):
2026-05-23-growth-postgres-initiative-scope