catalogue-services-skills-alignmentrun.md00_intake/stub.mdThe services and skills sections can draft datapoints that don't exist on the real taxonomy models (the
demo reference carried fields like priceBand, duration, level, linkedServices that have no home).
Real service and skill are thin taxonomy models: service = {name, description, productKeys[]};
skill = {name, serviceKey, description}. Junk fields confuse the review panel and never commit.
Constrain the services and skills blueprint sections to their real taxonomy fields and cross-references
only: service data = name / description + productKeys (links to drafted product items); skill data
= name / description + serviceKey (exactly one drafted service). Align contract, prompt, review-panel
editable fields, and commit so no non-existent fields are drafted and the cross-refs resolve correctly.
productKeys cross-refs; no invented fields.serviceKey cross-ref; no invented fields.services / skills rows with the correct links.serviceKey → service; service.productKeys → product), so they
are aligned together as the catalogue tail; products stay as-is upstream.packages/services/src/ai/onboarding/{contract.ts,prompt.ts,analyse-source.ts},
packages/services/src/db/services/onboarding-blueprint/commit.ts (the services + skills branches,
including productKeys / serviceKey resolution), apps/web/components/onboarding/types.ts
(services / skills SECTION_META); reference the taxonomy models
packages/services/src/db/models/{service,skill,taxonomy}.ts.01_define/output/spec.mdThe AI onboarding concierge drafts a per-tenant blueprint, but the services and skills sections can
invent datapoints that have no home on the real taxonomy models. Those models are deliberately thin:
service = { name, description } with product links held many-to-many via product_service_link;
skill = { name, description, services[] } linking to at least one service. Today the extraction
contract's data is a free-form record (its description still name-drops "pricing, tiers, locations"),
and neither the analysis prompt nor the conversational prompt tells the model which fields to draft or
to populate the cross-references. So a draft can carry junk fields like priceBand / duration /
level / linkedServices that confuse the review panel and never commit, and it can omit the
productKeys / serviceKey cross-refs the commit needs — leaving services unlinked or aborting the
commit. This is the catalogue tail of the onboarding-blueprint-alignment epic (Build the Bridge /
Q2-2026 O1 — establish product-market fit with vendor partners); a blueprint that mirrors the real
platform is what lets a vendor onboard cleanly onto a paid tier.
Constrain the services and skills blueprint sections to their real taxonomy fields plus their cross-references only, and align every layer that touches them so no non-existent field is drafted and the cross-refs resolve at commit:
contract.ts) — validate the services and skills sections against a constrained item
shape so only real fields survive: service data = name, description, productKeys[] (slugs of
drafted product items); skill data = name, description, serviceKey (slug of exactly one
drafted service). Any other field is stripped/rejected at the contract boundary and never persisted.
The other sections keep their current free-form payload. Remove the stale "pricing, tiers, locations"
hint from the generic data description.analyse-source.ts system prompt, prompt.ts interview instructions) — tell the model
exactly which fields to draft for services and skills, and to populate the cross-refs by referencing
the key of a drafted product (for a service's productKeys) or a drafted service (for a skill's
serviceKey), so a normal draft yields resolvable references.commit.ts) — keep the existing product → service → skill resolution: a service's
productKeys resolve into product_service_link rows (many-to-many); a skill's serviceKey resolves
to a single service and writes serviceIds: [serviceId]. A missing or unresolvable required
cross-ref (a skill with no/absent service, or a productKey/serviceKey naming an item not being
committed) aborts the whole commit atomically with a clear, item-specific message — no partial writes.
Confirm the service/skill branches match the constrained field set (drop any reliance on invented
fields).apps/web/components/onboarding/types.ts SECTION_META) — services and skills
expose only name and description as editable fields; no invented field is surfaced or editable.
The structural cross-refs (productKeys / serviceKey) stay non-editable so editing can't break the
commit.Products stay untouched — they are the upstream parent these cross-refs point at.
name, description, and a productKeys[] cross-ref; any
other field the model emits (e.g. priceBand, duration, linkedServices) is dropped at
validation and never reaches the stored blueprint.name, description, and a single serviceKey cross-ref; any
other field (e.g. level) is dropped at validation and never reaches the stored blueprint.productKeys / serviceKey by referencing drafted item keys, so a
normal end-to-end draft produces cross-refs that resolve.productKeys resolve into product_service_link rows and a skill's
serviceKey resolves into serviceIds: [serviceId]; a missing or unresolvable required cross-ref
aborts the entire commit atomically with a clear, item-specific message and writes nothing.name and description as editable fields for services and skills,
and surfaces no invented field.service rows (linked to their products) and skill rows (each
linked to ≥1 service) that satisfy the real taxonomy model validation.service / skill taxonomy models themselves, or their admin CRUD.services[], but the blueprint draws
exactly one serviceKey; multi-service skills are a possible follow-up.None — the missing-cross-ref behaviour is settled as atomic abort with a clear per-item message.
02_build/output/notes.mdfeat: catalogue-services-skills-alignment — constrain services & skills to real taxonomy fieldspackages/services/src/ai/onboarding/contract.ts: added a single-source-of-truth whitelist
(CONSTRAINED_SECTION_FIELDS) and sanitiseSectionData(section, data) that drops any field outside a
section's real taxonomy shape. Services → name / description / productKeys; skills → name /
description / serviceKey. Removed the stale "pricing, tiers, locations" hint from the generic
data description. The extraction schema stays a plain object; the whitelist is enforced at the
persistence boundary on both write paths (see below), so the schema the model is generated against
needs no transform.packages/services/src/ai/onboarding/analyse-source.ts + blueprint-tools.ts: both write paths route
each item's data through sanitiseSectionData(section, …) before persisting — the analysis path in
toItemInput, the conversational draft_section tool inline — so a service/skill can never persist an
invented field, whichever path drafts it. One whitelist, applied uniformly.packages/services/src/ai/onboarding/prompt.ts: the interview instructions tell the concierge exactly
which fields to draft for services (name, description, productKeys) and skills (name, description,
serviceKey), and to set the cross-refs to the key values of the drafted products/services so links
resolve on commit.packages/services/src/ai/onboarding/analyse-source.ts: the source-analysis system prompt got the
same field- and cross-ref guidance for services and skills.name, description, productKeys — enforced on both write paths by
sanitiseSectionData("services", …) (analysis in toItemInput, interview in draft_section);
invented fields (priceBand, duration, linkedServices) are dropped before persistence.name, description, a single serviceKey — same enforcement; level and
any other field are dropped before persistence.productKeys / serviceKey from drafted item keys so cross-refs resolve.productKeys into product_service_link rows and a skill's
serviceKey into serviceIds: [serviceId]; a missing/unresolvable required ref aborts the whole
transaction with a clear, item-specific message and writes nothing — already implemented in
commit.ts (verified, unchanged), and now matches the constrained field set exactly.name + description as editable for services/skills, and no invented
field — already the case in apps/web/components/onboarding/types.ts SECTION_META (verified,
unchanged); cross-refs stay deliberately non-editable.service rows (linked to products via product_service_link)
and skill rows (serviceIds with ≥1 service) that satisfy the taxonomy model validation.commit.ts and types.ts were verified rather than edited.draft_section tool). The spec's touches under-listed blueprint-tools.ts — the conversational
path needed the same guard to satisfy "never reaches the stored blueprint", so it's included.commit.ts and types.ts are intentionally unchanged: they already read/expose exactly the real
fields, so editing them would be churn. Look closely if you'd prefer a redundant server-side guard in
onboarding-blueprint/upsertSection as belt-and-suspenders — I kept enforcement at the two AI entry
points per the spec's scope.services: ObjectId[]); the blueprint deliberately drafts
exactly one serviceKey (committed as serviceIds: [one]). Multi-service drafting is out of scope.03_release/output/changelog.mdWhen you set up a new workspace, the setup assistant now drafts your services and skills using only the details that actually apply — a name, a short description, and the links between them.
03_release/output/investor-update.mdWho it's for: Vendors onboarding, and the admins supporting them What shipped: The setup concierge now drafts services and skills using only real fields and links, dropping invented data. Why it matters: Cleaner, reliable vendor setup — advancing product-market fit with vendor partners (Build the Bridge).
Every drafted service and skill commits with correct links.
Dig deeper: <merged-PR URL> · <changelog entry URL>
03_release/output/release.mdtechnical/packages/services describes the onboarding concierge as "a shared Zod extraction contract"; still accurate (the contract now constrains services/skills, same architecture)/code-review medium on the branch diff (4 code files + changelog + run docs). No correctness
findings. One simplification landed on the branch before merge: enforcement was split (schema
transform for the analysis path, explicit call for the conversational path). Unified both to call
sanitiseSectionData at the persistence boundary and removed the schema transform — one whitelist
applied identically on both paths, and the extraction schema stays a plain object the model is
generated against (no reliance on nested-transform handling in generateObject).draft_section sanitiser