product-knowledge-read-seamrun.md00_intake/stub.mdThe rich product profile lives in the blueprint document, but commit writes only name + description
into the thin product model. Nothing yet joins a committed product back to its rich blueprint data,
so the profile the conversation acquired isn't consumable after launch and the future BRD-agent upgrade
has no seam to read. commitRecord.items already maps blueprint item key → committed record id per
section, so the join is possible today without a new collection — the committed blueprint is the
interim product-knowledge store.
commitVerifiedBlueprint keeps committing products exactly as today (name + description into
the existing product model) — no model changes; existing product / service / skill / SLA /
settings / profile commits are unaffected.getProductKnowledge(tenantId, productId)) in
packages/services/src/db/services/onboarding-blueprint/ that resolves commitRecord.items back to
the blueprint's product item and returns the typed rich profile (the ProductProfileDataSchema
from stub 1). This is the seam the future BRD-agent upgrade reads; shipping it now makes the MVP's
data immediately consumable.commitVerifiedBlueprint behaves exactly as today — existing product / service / skill /
SLA / settings / profile commits are unaffected.getProductKnowledge(tenantId, productId) returns the typed rich profile for a
committed product.ProductProfileDataSchema from
product-conversation (reused, not redefined).brd-agent-upgrade).product-profile-review-panel (stub 3).Reuse the typed ProductProfileDataSchema from product-conversation as the read return type — do not
redefine the field list. Confirm the commitRecord.items shape (blueprint item key → record id, per
section) against commit.ts before designing the join, and pin down the empty/missing-item contract
(what getProductKnowledge returns when a product was committed before the typed profile existed, or
its blueprint item is gone). touches:
packages/services/src/db/services/onboarding-blueprint/ (commit.ts read-side + a new read service).
01_define/output/spec.mdProductProfileDataSchema from packages/services/src/ai/onboarding/contract.ts)The vendor onboarding conversation now acquires a rich, typed product profile (stub 1,
product-conversation), but that profile lives only inside the blueprint document. Commit still writes
just name + description into the thin product model, and nothing joins a committed product back to
its rich blueprint data. So the profile the conversation captured is not consumable after launch, and
the future BRD-agent upgrade has no seam to read it from. This blocks the payoff of the
vendor-onboarding-mvp epic (initiative Build the Bridge, objective Q2-2026 O1 — Establish
Product-Market Fit with Vendor Partners): the point of capturing richer product knowledge is that
downstream product/matching steps can consume it. The join is already possible today without a new
collection — commitRecord.items maps each committed record id back to its originating blueprint item
key per section, so the committed blueprint is the interim product-knowledge store.
commitVerifiedBlueprint keeps committing products exactly as today
(name + description into the existing product model). No model changes; existing product /
service / skill / SLA / settings / profile commits are untouched.getProductKnowledge(tenantId, productId) in
packages/services/src/db/services/onboarding-blueprint/ that resolves a committed product back to its
rich blueprint profile via the existing join:commitRecord.items, find the entry with section === "products" and
recordId === productId.key, look up the matching item in the blueprint's products section, and
return its data parsed through the typed ProductProfileDataSchema (reused from
product-conversation, not redefined).ProductProfileData | null:name / description
(the rich PRODUCT_PROFILE_FIELDS simply absent) — a valid, non-null result, not an error.null when no committed blueprint product item resolves to productId: no live blueprint, no
matching commitRecord.items entry, or the referenced blueprint item is gone. This is the
documented empty result — never a throw.This is the seam the future BRD-agent upgrade reads; shipping it now makes the MVP's captured product knowledge immediately consumable from a clean, typed source.
commitVerifiedBlueprint behaves exactly as today — existing product / service / skill /
SLA / settings / profile commits are unaffected (no commit-path or model change).getProductKnowledge(tenantId, productId) returns the typed rich
profile for a committed product, joined via commitRecord.items (section products, matching
recordId) back to the blueprint item's data.ProductProfileDataSchema /
ProductProfileData from product-conversation (reused, not redefined).productId with no resolvable committed blueprint item returns null (documented empty
result), not a throw — covering: no live blueprint, no matching commit item, and a missing
blueprint item.name / description, with the rich fields absent./server entry point.brd-agent-upgrade).product-profile-review-panel (stub 3).product_knowledge collection or migrating existing tenants — the committed blueprint
is the interim store; greenfield only.none — the join, the reused return type, and the empty/missing contract are all settled above and
confirmed against commit.ts and onboarding-blueprint.ts.
02_build/output/notes.mdfeat: product-knowledge-read-seam — getProductKnowledge read servicepackages/services/src/db/services/onboarding-blueprint/read.ts (new): getProductKnowledge(tenantId, productId).
Loads the tenant's live (non-deleted) blueprint, reverses the commit join
(commitRecord.items entry with section: "products" + matching recordId → blueprint item key →
the products-section item's data), and parses that data through the reused
ProductProfileDataSchema. Returns null for the three not-found cases.packages/services/src/ai/onboarding/contract.ts: added export type ProductProfileData = z.infer<typeof ProductProfileDataSchema>
— a purely additive type alias so the read seam reuses one canonical definition instead of re-inferring
the field list. No schema, commit-path, or model change.packages/services/src/db/services/onboarding-blueprint/index.ts: re-exports getProductKnowledge +
ProductProfileData, and adds a thin OnboardingBlueprintService.getProductKnowledge method delegating
to the standalone (mirrors how commitBlueprint wraps commitVerifiedBlueprint).packages/services/src/db/services/index.ts: surfaces getProductKnowledge + ProductProfileData from
the services barrel, so both reach the package's /server entry (server → db → db/services → barrel).commitVerifiedBlueprint behaves exactly as today — commit.ts and the models are untouched; only
a new read file plus additive exports/type were added.getProductKnowledge(tenantId, productId) returns the typed profile, joined via
commitRecord.items (section products, matching recordId) back to the blueprint item's data.tenantId + isDeleted: false); return type is ProductProfileData /
ProductProfileDataSchema reused from product-conversation (ai/onboarding/contract.ts), not redefined.null (no live blueprint / no commitRecord / no matching
commit item / missing blueprint item all short-circuit to null before any parse — never a throw).name/description
populated (its data parses cleanly; rich fields simply absent).getProductKnowledge + the class method) and reachable
via @sustentus/services/server.read.ts imports ProductProfileDataSchema from ai/onboarding/contract.ts. This
direction is already precedented (db/services/matching/score-expert-fit.ts imports from ai/index.js),
and there's no cycle: contract.ts's db deps (sla/index, tenant-setting/registry, the blueprint model)
don't import back into onboarding-blueprint or the ai contract..parse vs .safeParse. The three not-found cases are handled before parsing, so they return null.
A present item's data is parsed with .parse (throws on malformed data) rather than .safeParse — the
write side validates data against this same schema, so a stored product profile always conforms; failing
loudly on a genuinely malformed blob is preferable to silently masking it as an empty result. The spec's
"not a throw" contract is about the empty/missing cases, which never reach the parse.03_release/output/release.mdapps/docs/app/technical/packages/services/page.mdx — added the read-seam sentence to the onboarding-concierge entry (getProductKnowledge reads a committed product's rich profile back; the committed blueprint is the interim product-knowledge store)product-conversation already carried the vendor-onboarding investor story). Offered a delivery-velocity note to the user as an override.commitVerifiedBlueprint unchanged — commit.ts and models untouched; only a new read file + additive exports/type.getProductKnowledge returns the typed profile joined via commitRecord.items (section products, matching recordId) → blueprint item data.tenantId + isDeleted: false); returns ProductProfileData reused from ai/onboarding/contract.ts, not redefined.null (no blueprint / no commit item / missing item), never a throw.name/description.@sustentus/services/server.