sdm-supply-taxonomyrun.md00_intake/stub.mdEvery row of the SDM dashboard keys on a platform (vendor in the mock — SAP, Oracle, Salesforce)
and a module (product in the mock — MM, Fusion, CRM): see the goLiveDemand,
quoteAndBidReadiness, goLiveRisks and expertStrength arrays in
apps/web/lib/mock/sdm.ts. The real models can't express this: the
product model is a bare { name } with no platform parent, the skill model hangs off a generic
category, and lead.vendor is a reference to the paying Vendor org, not a technology family. With
no platform→module taxonomy to group leads and experts by, no grid cell can be formed — demand,
supply and coverage all aggregate over this key.
platform (SAP, Oracle, Salesforce) → module
(MM, SD, FI, Fusion, HCM, CRM, CPQ…). Model it as the cleanest fit for the existing schema (e.g. a
parent category/platform on the product model, or a dedicated taxonomy model) so a product
resolves to (platform, module).productSkills (their skills) resolve to the same taxonomy, so demand and supply can be
matched on a shared key.(platform, module) pair; the taxonomy is tenant-scoped and indexed.lead.vendor org reference — the paying Vendor org is unchanged; this adds a separate
technology dimension.product with a platform parent (+ reuse category for module), or
a dedicated taxonomy model — pick the one that least duplicates category/skill. Confirm whether
skill.category already encodes module and can be reused for the supply side.touches: packages/services/src/db/models/{product.ts,skill.ts,category.ts} (taxonomy fields + index),
a seed/back-fill script for the demo tenant.01_define/output/spec.mdEvery cell of the SDM "go live readiness" dashboard keys on a platform (SAP, Oracle, Salesforce)
and a module (MM, SD, FI, Fusion, HCM, CRM, CPQ) — the mock encodes these as the vendor and
product fields in apps/web/lib/mock/sdm.ts across the
demand, bid-readiness, risk and expert-strength sections. The real models can't express this two-level
key: product is a flat thin taxonomy with no platform parent, and there is no platform entity at all
(lead.vendor is the paying Vendor org, not a technology family). Without a platform→module
taxonomy to group leads and experts by, no grid cell can be formed — every downstream SDM feature
(demand aggregation, supply coverage, coverage-risk, expert strength) aggregates over this key. This is
feature 1 of the sdm-dashboard-data epic and the prerequisite for all of it; it advances Build the
Bridge / Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow by standing up the
real per-tenant supply key the SDM cockpit groups on.
Introduce a two-level technology taxonomy in @sustentus/services — a new tenant-scoped
platform entity that product (the module) hangs off — so demand and supply resolve to the same
(platform, module) key. No dashboard UI and no admin CRUD this round: model + migration + seed only.
Schema / model changes
platform collection. Add packages/services/src/db/models/platform.ts built on the
shared createTaxonomySchema (the same factory service/product/industry use): tenantId,
normalised name (lowercase/trim/length), isActive default true, soft-delete + tenant plugins,
{tenantId, name} unique index. Add getPlatformModel, export it from models/index.ts, and load
it where product is referenced (so the product → platform populate resolves).product becomes the module. Add a platform: ObjectId ref (to platform) on the product
model. Because product reuses the shared ITaxonomy type, extend the product type/schema locally
rather than widening the shared taxonomy. Change the product uniqueness from {tenantId, name} to
{tenantId, platform, name} so the same module name can exist under different platforms, and add
an index on {tenantId, platform} for cell grouping.sdm.area.platforms (today ref: "product",
a documented placeholder) to ref: "platform", and update the ISDMArea comment/type accordingly.
The {tenantId, "area.platforms"} index is unchanged in shape.Demand & supply resolution (no per-row re-tagging)
lead.product) and supply (expert.products) already reference product, so once a
product carries its platform parent, both sides resolve to (platform, module) transitively —
no new fields on lead or expert, and no per-lead/per-expert re-tagging. This run only guarantees
that demo leads/experts reference products that have a platform set.Seed + back-fill (versioned, tenant-aware, idempotent — ts-migrate-mongoose)
platform rows, ensure
the module product rows exist, and set each module's platform parent.product.platform for existing module rows by mapping known module names to their
platform (per the seed map); products that don't map to a known platform are left unset and reported
(they form no cell). Back-fill sdm.area.platforms by replacing each existing product id with
its product's platform parent id (de-duplicated).lead.product / expert.products only
where missing — do not invent demand/supply beyond what the demo needs to form cells).up is a no-op) and follows the established one-way/
symmetric down convention in migrations/1782000000000-taxonomy-normalization.ts; existing rows'
isActive is left untouched.platform collection exists (built on the shared taxonomy factory), is tenant-scoped, name-
normalised, has a {tenantId, name} unique index, and is exported from models/index.ts with a
getPlatformModel.product carries a platform ref to platform; a product (module) resolves to a
(platform, module) pair, product uniqueness is {tenantId, platform, name}, and `{tenantId,platform}` is indexed.
platform + module cell by query (via lead.product → platform),
and an expert's products resolve to the same key — demand and supply share one taxonomy.sdm.area.platforms references the platform collection (not product), and existing area
assignments are back-filled to the corresponding platform ids.product.platform,
sdm.area.platforms, and demo lead/expert product tags per tenant for the demo tenant; it is
idempotent (a second up makes no further changes) and leaves existing isActive untouched.pnpm typecheck, pnpm lint, and pnpm build pass; no code treats sdm.area.platforms as a
product ref.apps/web or apps/dashboards changes.lead.vendor — the paying Vendor org reference is unchanged; this adds a separate
technology dimension.skill / skillCategory axis — module is product, not skillCategory; "skill level" in the
grid is expert seniority/proficiency, resolved by later features, not part of this taxonomy.platform/product into a single polymorphic collection — kept separate, consistent with
the taxonomy-normalization decision.platform collection + product.platform ref) and the
sdm.area.platforms re-point + back-fill were both confirmed during Define.02_build/output/notes.mdpackages/services/src/db/models/platform.ts (new): the platform parent collection (SAP,
Oracle, Salesforce), built on the shared createTaxonomySchema like service/industry —
tenant-scoped, name-normalised, {tenantId, name} unique. Exposes getPlatformModel.packages/services/src/db/models/index.ts: export the new platform model.packages/services/src/db/models/product.ts: product is now the module. Replaced the
factory-built schema with a local one (extending the taxonomy shape rather than widening the shared
ITaxonomy) that adds a platform ref. Uniqueness moved from {tenantId, name} to
{tenantId, platform, name}, plus a {tenantId, platform} index for cell grouping. IProduct is
now ITaxonomy & { platform?: ObjectId | null } — still satisfies TaxonomyService<T extends ITaxonomy>, and admin CRUD (platform defaults to null) still enforces a unique name per tenant.packages/services/src/db/models/user.ts: re-pointed sdm.area.platforms from ref: "product"
to ref: "platform" and updated the ISDMArea doc comment (the placeholder note is now resolved).packages/services/src/db/migrations/1782700000000-sdm-supply-taxonomy.ts (new): per-tenant,
idempotent migration that seeds the platform→module taxonomy, sets/back-fills product.platform,
re-points sdm.area.platforms (product id → its platform parent, keeping ids that already point at
a platform so a re-run is a no-op), tags synthetic demo+ experts/leads with platform-bearing
products so the grid forms cells, and swaps the products unique index. down is a documented
no-op per the taxonomy-normalization convention.platform collection exists (shared taxonomy factory), tenant-scoped, name-normalised,
{tenantId, name} unique, exported with getPlatformModel.product carries a platform ref; resolves to (platform, module); uniqueness is
{tenantId, platform, name} with {tenantId, platform} indexed.lead.product → platform, and expert.products resolve to the
same key — both reference product, which now carries platform, so the cell is transitive.sdm.area.platforms references the platform collection; existing assignments are back-filled
to platform ids by the migration.product.platform,
sdm.area.platforms and demo lead/expert product tags per tenant; idempotent; leaves isActive
untouched.pnpm typecheck, pnpm lint, pnpm build pass — verified by CI on this PR (no code treats
area.platforms as a product ref; checked apps/web + services usages).tenantId_1_name_1 unique
index on products is dropped and {tenantId, platform, name} (unique) + {tenantId, platform}
created, since Mongoose autoIndex only ever adds, never drops.demo+ users (experts by email, leads via
their demo+ vendor) and only fills a missing product link — real tenant data is never touched.demo-data generator (services/demo-data/index.ts) still
creates experts without products; a later change should have it assign platform-bearing products
so freshly generated demo batches keep a populated supply side. This migration fixes existing data
only.03_release/output/investor-update.mdWho it's for: Service delivery managers — our internal supply-readiness role. What shipped: A tenant-scoped platform → module technology taxonomy (e.g. SAP → MM) now keys every lead and expert in the data layer. Why it matters: It is the foundation the SDM go-live readiness dashboard groups on — advancing Build the Bridge, objective "Validate Technical Infrastructure & Payout Flow".
Dig deeper: <merged-PR URL> · <changelog entry URL>
03_release/output/release.mddb/services/sdm-area/index.ts (shipped by
sdm-area-foundation) filtered lead.product / expert.products directly against
area.platforms. Re-pointing area.platforms to platform ids made that $in compare platform
ids to product ids → empty "my area" scope, and it violated AC6 ("no code treats
sdm.area.platforms as a product ref"). Fixed: the service now resolves owned platform ids to the
module (product) ids under them and filters by those (transitive resolution the spec intended).{tenantId, name} unique
index was already non-collated and non-partial (so same-name dups, incl. soft-deleted, were already
impossible), names are lowercase on write, and each seed module maps to exactly one platform. The
Migrate preview database CI check passed on this migration.area.platforms re-point drops product ids that map to no platform: accepted — area.platforms
was a documented placeholder this feature owns converting; only demo data has an area assigned.demo-data generator still creates experts without
products — noted in build notes for a later change.platform collection on the shared taxonomy factory, tenant-scoped, name-normalised, {tenantId, name} unique, exported with getPlatformModel — models/platform.ts + models/index.ts.product carries a platform ref; resolves to (platform, module); uniqueness {tenantId, platform, name} + {tenantId, platform} index — models/product.ts (and migration index swap).lead.product → platform; expert.products resolve to the same key — both ref product, which now carries platform (transitive).sdm.area.platforms refs platform; existing assignments back-filled — models/user.ts + migration step 3.isActive untouched — migrations/1782700000000-sdm-supply-taxonomy.ts.area.platforms as a product ref — green CI on the build commit.