source-analysis-pipelinerun.md00_intake/stub.mdThe concierge's "wow" is that the tenant's catalogue drafts itself from something the vendor already has — a website URL, an uploaded price list, or a free-text description. Issue #588 Q2: there is no source-analysis capability today; the demo fakes it with scripted data.
An extraction pipeline in packages/services/src/ai (on the stub-2 foundation): fetch + parse the source (website scrape, uploaded file text, or raw description), then structured LLM extraction into a typed draft blueprint — the 7 sections with per-item confidence — written into the persisted blueprint from stub 1. A clear extraction contract (Zod schema shared with the blueprint model) is the deliverable alongside the pipeline. Graceful degradation when a source can't be analysed (unreachable site, thin content): return a partial or empty draft with the reason, so the concierge (stub 5) falls back to interviewing rather than erroring.
Price-list upload can piggyback on Vercel Blob (already in the stack); Define should decide whether file parsing (PDF/CSV) is in this cut or URL+text only, flagging file support as a fast-follow if it inflates the PR. Website fetch must respect the outbound-proxy constraints of the deploy environment. touches: packages/services/src/ai/ (new onboarding/extraction or similar).
01_define/output/spec.mdThe onboarding concierge's "wow" is that a tenant's catalogue drafts itself from something the
vendor already has — a website URL or a free-text description of their business. Today there is no
source-analysis capability (issue #588); the apps/demo concierge fakes it with scripted data. The
persisted blueprint (onboarding-blueprint-model) and the reusable agent core
(ai-agent-foundation) now exist, but nothing populates a draft from a real source. This blocks the
concierge agent (stub 5) and the wizard UI (stub 6), and it directly advances Build the Bridge /
Q2-2026 O1 — establish product-market fit with vendor partners (KR: onboard 8+ vendors onto paid
tiers) by making first-run setup feel effortless rather than like a data-entry chore.
Add an extraction pipeline under packages/services/src/ai/onboarding that turns a source into a
typed draft blueprint and writes it into the persisted blueprint for a tenant. Functionally:
analyseSource({ tenantId, source }).profile, categories,
products, services, skills, sla, settings), each item carrying a 0–100 confidence score.
The schema is the deliverable alongside the pipeline and is the type the LLM is generated against
(via the foundation's generateStructured) so malformed model output can never reach the document.OnboardingBlueprintService.upsertSection (provenance ai_drafted, review state pending) — the
pipeline creates no new model and no new collection.unreachable / thin_content /
extraction_failed) rather than throwing, so the concierge (stub 5) can fall back to interviewing.The pipeline is a service module only — no conversational logic, no commit-to-real-services, no UI.
analyseSource produces a draft whose items carry 0–100
confidence scores and are persisted into that tenant's blueprint sections (provenance
ai_drafted, review state pending), readable back via OnboardingBlueprintService.blueprint-commit-service — already built).02_build/output/notes.mdfeat: source-analysis-pipeline — extract a draft blueprint from a URL or descriptionNew server-only module packages/services/src/ai/onboarding/, on the existing AI foundation
(generateStructured) and the persisted blueprint from stubs 1/3:
contract.ts — the canonical extraction contract. DraftBlueprintSchema = the seven
onboarding sections, each an array of confidence-scored DraftBlueprintItemSchema items
(key / label / data / confidence 0–100). satisfies Record<OnboardingSectionKey, …> keeps the
shape in lockstep with the model's section vocabulary; the item shape mirrors BlueprintItemInput
so a validated draft item persists unchanged. Designed so a file source plugs in later unchanged.fetch-website.ts — fetchWebsiteText(url): standard fetch (so the runtime's outbound proxy
governs egress), an AbortSignal.timeout (8 s) and a streamed 2 MB byte cap, protocol +
content-type guards, dependency-free HTML→text. Any network/HTTP/size/content problem returns
{ ok: false, reason: "unreachable" } — it never throws or hangs.analyse-source.ts — the entry point analyseSource({ tenantId, source }). Resolves the source to
text (URL or free text), runs structured extraction against the contract, and persists each
non-empty section via onboardingBlueprintService.upsertSection (items land as ai_drafted /
pending). Every failure degrades to an explicit result with a machine-readable
AnalysisFailureReason (empty_source / unreachable / thin_content / extraction_failed) —
status analysed (full), partial (thin source, some items) or empty.index.ts — module barrel.server/index.ts — re-exports analyseSource, the contract, and the types from @sustentus/services/server (server-only, alongside the catalogueTool precedent — network + DB access).ai_drafted/pending, readable back via
OnboardingBlueprintService — analyseSource → upsertSection (defaults provenance/reviewState).kind: "text"
branch of resolveSourceText, identical downstream.resolveSourceText and generateStructured failures map to
reasons.generateStructured (generateObject) validates against DraftBlueprintSchema and throws on
mismatch → extraction_failed, nothing persisted.AbortSignal.timeout + readCapped (2 MB) over standard fetch.lib includes DOM +
DOM.Iterable and types: ["node"], so fetch / ReadableStream / AbortSignal.timeout / Buffer
all resolve; undici was intentionally not added (not resolvable in the workspace).fetch uses the runtime's default egress, so
the deploy proxy governs it — writing a custom socket/dispatcher would bypass that. The real safety
guarantees (timeout + size cap) are explicit in code.data payload is z.record(z.string(), z.unknown()) — intentionally loose, since each section's
payload shape varies (pricing, tiers, locations). The model is steered by the system prompt's
per-section guidance rather than a rigid per-section schema, to keep the contract stable.MAX_ITEMS_PER_SECTION at persist time (not via a
schema .max), so an over-eager model degrades gracefully instead of failing validation.03_release/output/investor-update.mdWho it's for: Vendors and the admins onboarding them. What shipped: The onboarding concierge turns a vendor's website URL or business description into a confidence-scored draft catalogue. Why it matters: Removes the blank-page setup barrier — progress toward our Q2 objective, establish product-market fit with vendor partners.
Unusable or thin sources degrade to a clear reason, so onboarding never dead-ends.
Dig deeper: <merged-PR URL>
03_release/output/release.mdapps/docs/app/technical/packages/services/page.mdx (AI section — new
ai/onboarding/ source-analysis capability + /server export) in this PRget_review_comments → 0 threads). Diff is small, additive, self-contained.type not interface, braces on every block — nothing to fix on the branch.AnalysisFailureReason; the only throw (generateObject on schema mismatch) is caught and mapped,
so nothing malformed reaches the blueprint document.ai_drafted/pending), readable via
OnboardingBlueprintService — analyseSource → upsertSection.kind: "text"
branch, identical downstream.generateStructured/generateObject validates against DraftBlueprintSchema; throw → extraction_failed.AbortSignal.timeout(8s) + readCapped(2MB) over standard fetch.