feat(mktg-os): brand kit one-page intake form and generation flow - #1348
feat(mktg-os): brand kit one-page intake form and generation flow#1348joanreyero wants to merge 4 commits into
Conversation
…LFXV2-2059) Agent-invocation UI without persistence (persistence deferred per Joan's ruling; salvaged session-driver + envelope-extraction from the closed PR #1317 branch, storage parts dropped): - One-page intake form (all 7 of Paul's questions verbatim, open-ended, per dec-brand-kit-intake-form) on the dark-launched mktg-os-agents surface (feature flag unchanged). - POST /api/mktg-agents/brand-kit/generate renders the batch-intake message and starts a one-shot Guild form-mode session vs linux-foundation~brand-kit; POST .../result polls, extracts the finalize_brand_kit tool-result envelope from raw events, validates (schema + 12-heading gate + server-side sha256 recompute), and returns the document for display. - Client renders the validated markdown with a download option; owner-token creator binding on the result endpoint. NO PutObject, NO object storage. - Shared brand-kit contract types/constants/utils reused from the salvage branch plus intake-form validation + batch-message renderer; 20-test vitest spec. Signed-off-by: Joan Reyero <joan@reyero.io>
- Generation-epoch token discards stale in-flight poll responses so a cancelled/reset generation can't resurrect (reviewer Important). - Tolerate up to 3 consecutive transient poll failures instead of failing the multi-minute generation on one blip (reviewer Important). - Split the onSubmit guards so generating() no longer marks the form touched. - role=status + aria-live=polite on the generating state. - path context on the brand_kit_result AuthorizationError; reword stale persistence-era comments (persistence deferred). Signed-off-by: Joan Reyero <joan@reyero.io>
PR SummaryMedium Risk Overview The UI submits answers, shows a generating state, then polls the BFF for a validated document, renders markdown, supports BFF: Shared brand-kit-output/v1 constants, interfaces, and validation/extraction utils back both layers; Reviewed by Cursor Bugbot for commit 00c9151. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
There was a problem hiding this comment.
Pull request overview
Adds a display-only Brand Kit generation flow to the dark-launched Marketing OS marketplace.
Changes:
- Adds the seven-question intake and generated Markdown result UI.
- Adds authenticated Guild generation and owner-gated polling endpoints.
- Adds shared Brand Kit contracts, validation, extraction, and tests.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
packages/shared/src/utils/index.ts |
Exports Brand Kit utilities. |
packages/shared/src/utils/brand-kit.utils.ts |
Validates, extracts, and renders Brand Kit data. |
packages/shared/src/utils/brand-kit.utils.spec.ts |
Tests shared Brand Kit utilities. |
packages/shared/src/interfaces/mktg-agent.interface.ts |
Adds agent surface selection. |
packages/shared/src/interfaces/index.ts |
Exports Brand Kit interfaces. |
packages/shared/src/interfaces/brand-kit.interface.ts |
Defines Brand Kit API and envelope contracts. |
packages/shared/src/constants/mktg-os-agents.constants.ts |
Registers the Brand Kit agent. |
packages/shared/src/constants/index.ts |
Exports Brand Kit constants. |
packages/shared/src/constants/brand-kit.constants.ts |
Defines intake and validation constants. |
apps/lfx-one/src/server/services/guild.service.ts |
Fetches raw Guild session events. |
apps/lfx-one/src/server/services/brand-kit.service.ts |
Drives generation and resolves results. |
apps/lfx-one/src/server/routes/mktg-agents.route.ts |
Registers Brand Kit endpoints. |
apps/lfx-one/src/server/controllers/mktg-agents.controller.ts |
Validates and handles Brand Kit requests. |
apps/lfx-one/src/app/shared/services/brand-kit.service.ts |
Adds the Angular API client. |
apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.ts |
Selects the Brand Kit form surface. |
apps/lfx-one/src/app/modules/mktg-os-agents/mktg-os-agents/mktg-os-agents.component.html |
Renders the selected surface. |
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.ts |
Implements form, polling, and download behavior. |
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html |
Renders intake, progress, errors, and results. |
Suppressed comments (1)
apps/lfx-one/src/server/controllers/mktg-agents.controller.ts:220
- A request with no JSON body or a literal
nullbody throws here before thesessionIdtype gate runs, producing a server error rather than the intended validation response. Normalize the body before destructuring.
const { sessionId, ownerToken } = req.body as BrandKitResultRequest;
|
@joanreyero you have AI feedback and conflicts to resolve. |
- SECURITY: exclude user-originated events (user_message/trigger_message) from the raw-event scan so a schema-valid envelope planted in an intake answer can never be selected as agent output. - Free-text scanner unwraps the envelope_json tool-result wrapper (walks outward brace by brace), closing the documented A3 path in non-JSON stream bodies. - Extractor returns unknown[] candidates; BrandKitEnvelope typing now flows only through validateBrandKitEnvelope (no lying casts). - Null-body normalization on both new endpoints (controlled 400 instead of a destructuring throw). - Poll error tolerance off-by-one: three consecutive failures are now tolerated, the fourth aborts. - trimmedRequired() on all seven intake controls (client/server validation parity for whitespace-only answers). - a11y: aria-describedby + aria-invalid wired through lfx-textarea to the per-question error message. - New brand-kit.service.spec.ts (6 tests): batch-message session start, pending, ready via envelope_json, sha-mismatch suppression, version preference, abridged-output rejection. Shared spec extended (21 tests). Signed-off-by: Joan Reyero <joan@reyero.io>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 62bd856. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (4)
packages/shared/src/utils/brand-kit.utils.ts:47
project_nameis never runtime-validated even though this validator later permits the candidate to be cast toBrandKitEnvelope. A schema-valid-looking envelope withproject_name: { ... }therefore reachesBrandKitService.getResult()and is returned asprojectName, violating the response contract and rendering[object Object]in the result header. Validate the optional field's type before accepting the envelope.
if (typeof envelope.project !== 'string' || !BRAND_KIT_PROJECT_SLUG_REGEX.test(envelope.project)) {
errors.push('project must be a lowercase kebab-case slug');
}
apps/lfx-one/src/server/services/guild.service.ts:175
- The truncation check counts events after filtering out
user_messageandtrigger_message. A full 500-event API page containing either excluded type is therefore reported as fewer than 500 events, suppressing the only warning that the terminal envelope may be missing. Check and log the raw page length instead.
if (items.length >= GUILD_RAW_EVENTS_LIMIT) {
logger.warning(req, 'guild_get_raw_events', 'Raw event page is full — session may be truncated', { count: items.length, limit: GUILD_RAW_EVENTS_LIMIT });
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html:32
- The document is retained in the Guild session event stream—the result endpoint polls that stored stream—so telling users it is “not stored anywhere else” is inaccurate. Clarify that this iteration does not persist it to LFX document/object storage rather than implying no external retention.
<span class="text-xs text-gray-500">Review the document below. Download it to keep a copy — it is not stored anywhere else yet.</span>
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.ts:125
- This calls browser-only
URLanddocumentAPIs without a browser guard. The repository's SSR rule requires these references to be gated (.claude/rules/ssr-safety.md:14-28); add anisPlatformBrowsercheck before constructing the download.
const blob = new Blob([current.documentMarkdown], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
Signed-off-by: Joan Reyero <joan@reyero.io>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/shared/src/utils/brand-kit.utils.ts:47
project_nameis returned to Angular as a string, but this validator never checks its runtime type. A malformed envelope can therefore pass validation with an object or array here and violateBrandKitResultResponse.projectName; reject a present non-string value before casting the candidate toBrandKitEnvelope.
if (typeof envelope.project !== 'string' || !BRAND_KIT_PROJECT_SLUG_REGEX.test(envelope.project)) {
errors.push('project must be a lowercase kebab-case slug');
}
apps/lfx-one/src/app/modules/mktg-os-agents/brand-kit-form/brand-kit-form.component.html:32
- The document necessarily remains in Guild session events—the BFF polls those events to retrieve it—so “not stored anywhere else” gives users an inaccurate data-retention assurance. Clarify that this iteration does not save it to LFX Brand Kit storage rather than implying no external copy exists.
<span class="text-xs text-gray-500">Review the document below. Download it to keep a copy — it is not stored anywhere else yet.</span>

Summary
Adds the Brand Kit agent-invocation flow to the dark-launched Marketing OS surface (LFXV2-2059, WorkItem
wi-brand-kit-lfx-selfserve) — display-only, no persistence (the dec-brand-kit-storage-v1 write path is deferred; the session-driver and envelope-validation code is salvaged from the closed #1317).What it does
/…/mktg-os-agentspage. Newbrand-kitcatalog tile opens the form surface instead of the chat stub (surface: 'brand-kit-form'discriminator).POST /api/mktg-agents/brand-kit/generate: validates the 7 answers server-side (all keys, non-empty, no extras), renders the agent's documented batch-intake message, and starts a one-shot Guild form-mode session againstlinux-foundation~brand-kit(handle from the server-side catalog, never the client). Returns{sessionId, ownerToken}(HMAC creator binding).POST /api/mktg-agents/brand-kit/result: owner-token-gated poll. Scans raw session events for thefinalize_brand_kittool-result envelope (live-smoke A3 verdict — the__submit__text is never trusted), validates it (contract schema, 12-heading order gate, server-side sha256 recompute; mismatch → stillpending), and returns the document.lfx-markdown-renderer) +.mddownload + start-over. Poll: 10 s cadence, ~5 min cap, tolerates 3 consecutive transient failures, generation-epoch token discards stale in-flight responses after cancel/reset.Review process
Post-commit reviewer trio + full-branch sweep run per repo work cycle: 0 Critical; both Important findings (stale-poll race, transient-failure abort) fixed in the
fix(review)commit; sweep verified the epoch fix closes the race.Documented trade-offs
server/services/*.spec.ts); non-trivial logic lives in@lfx-one/sharedutils with a 20-test vitest spec.External references
marketing-os-agents/docs/contracts/brand-kit-output.{md,schema.json}(normative; shared interface is its transcription)linux-foundation~brand-kitv1.0.14 (Guild, PUBLISHED — verified live)