Skip to Content

← All archived runs

Run: product-knowledge-read-seam

run.md

Run: product-knowledge-read-seam

  • branch: claude/product-knowledge-read-seam-imnydw
  • pr: #632

00_intake/stub.md

Stub: Product knowledge read seam

  • feature-slug: product-knowledge-read-seam
  • epic: vendor-onboarding-mvp
  • personas: Vendor
  • initiative: Build the Bridge / objective: Q2-2026 O1 — Establish Product-Market Fit with Vendor Partners
  • depends-on: product-conversation
  • sequence: 2 of 3

Problem

The 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.

Proposed change

  • 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.
  • Add a read service (e.g. 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.

Acceptance criteria (rough)

  • commitVerifiedBlueprint behaves exactly as today — existing product / service / skill / SLA / settings / profile commits are unaffected.
  • After commit, getProductKnowledge(tenantId, productId) returns the typed rich profile for a committed product.
  • The read is tenant-scoped and its return type is the ProductProfileDataSchema from product-conversation (reused, not redefined).
  • A product id with no committed blueprint item resolves cleanly (documented empty result, not a throw).

Out of scope (this feature)

  • Consuming the profile in the BRD agent — deferred (epic stub 9, brd-agent-upgrade).
  • Any commit-path behaviour change or model change.
  • The review-panel UI — product-profile-review-panel (stub 3).

Notes for Define

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.md

Spec: Product knowledge read seam

  • slug: product-knowledge-read-seam
  • personas: Vendor
  • touches: packages/services/src/db/services/onboarding-blueprint (new read service; reuses ProductProfileDataSchema from packages/services/src/ai/onboarding/contract.ts)
  • complexity: standard

Problem

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.

Proposed change

  • Commit path is unchanged. 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.
  • Add a tenant-scoped read service 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:
    1. Load the tenant's live blueprint (tenant-scoped, non-deleted — the model already enforces one live blueprint per tenant).
    2. In its commitRecord.items, find the entry with section === "products" and recordId === productId.
    3. Take that entry's 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).
  • Return contractProductProfileData | null:
    • Resolves to a profile when a committed blueprint product item is found. A product committed before the typed profile existed parses cleanly to a profile carrying only 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.

Acceptance criteria

  • commitVerifiedBlueprint behaves exactly as today — existing product / service / skill / SLA / settings / profile commits are unaffected (no commit-path or model change).
  • After a blueprint commit, 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.
  • The read is tenant-scoped, and its return value is typed as the ProductProfileDataSchema / ProductProfileData from product-conversation (reused, not redefined).
  • A 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.
  • A product committed before the typed profile existed resolves to a non-null profile carrying only name / description, with the rich fields absent.
  • The read service is exported from the onboarding-blueprint service barrel and reachable via the package's /server entry point.

Out of scope

  • Consuming the profile in the BRD agent — deferred (epic stub 9, brd-agent-upgrade).
  • Any commit-path behaviour change or model change (target is zero schema change for the MVP).
  • The review-panel UI — product-profile-review-panel (stub 3).
  • Introducing a product_knowledge collection or migrating existing tenants — the committed blueprint is the interim store; greenfield only.
  • Exposing item-level provenance / confidence / reviewState through the seam — the return is the typed profile only for this run.

Open questions

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.md

Build notes: product-knowledge-read-seam

  • commits: feat: product-knowledge-read-seam — getProductKnowledge read service

What changed

  • packages/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).

Acceptance criteria status

  • commitVerifiedBlueprint behaves exactly as today — commit.ts and the models are untouched; only a new read file plus additive exports/type were added.
  • After commit, getProductKnowledge(tenantId, productId) returns the typed profile, joined via commitRecord.items (section products, matching recordId) back to the blueprint item's data.
  • Tenant-scoped (tenantId + isDeleted: false); return type is ProductProfileData / ProductProfileDataSchema reused from product-conversation (ai/onboarding/contract.ts), not redefined.
  • No resolvable committed blueprint item → null (no live blueprint / no commitRecord / no matching commit item / missing blueprint item all short-circuit to null before any parse — never a throw).
  • A product committed before the typed profile existed → non-null profile with only name/description populated (its data parses cleanly; rich fields simply absent).
  • Exported from the onboarding-blueprint barrel (getProductKnowledge + the class method) and reachable via @sustentus/services/server.

Verify result

  • mechanical checks (format · lint · typecheck · build) run in CI + the Vercel preview, not here. No check is expected to fail.

Notes for review

  • db → ai import. 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.
  • No new tests were added (the package has no existing unit-test harness for these services); the seam is exercised by the acceptance walkthrough on the preview.

03_release/output/release.md

Release: product-knowledge-read-seam

  • pr: #632 (https://github.com/sustentus/sustentus/pull/632) · merged: <pending — squash on ticked gate>
  • CI: <pending merge — green required>
  • technical docs: apps/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)
  • business docs: no business docs impact — no user-facing behaviour change; the onboarding flow, UI, and personas are unchanged, and the seam is not yet consumed (BRD-agent consumption is deferred to epic stub 9)
  • release notes: none — internal change. No end-user changelog (nothing a user can now do differently); no investor email (the consumability this unblocks is not realized until the BRD agent reads it, and stub 1 product-conversation already carried the vendor-onboarding investor story). Offered a delivery-velocity note to the user as an override.
  • deploy: <pending — poll web,docs,help after merge>
  • sent: none — internal change (no investor recipients)

Review summary

  • <pending /code-review medium — complexity: standard>

Acceptance check (vs spec)

  • 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.
  • Tenant-scoped (tenantId + isDeleted: false); returns ProductProfileData reused from ai/onboarding/contract.ts, not redefined.
  • No resolvable committed item → null (no blueprint / no commit item / missing item), never a throw.
  • Product committed before the typed profile existed → non-null profile with only name/description.
  • Exported from the onboarding-blueprint barrel + class method; reachable via @sustentus/services/server.