Skip to content

feat(serenity): org-flag brand-create mode, sub-workspace-only pending activation, site_id anchoring (LLMO-6405)#2878

Merged
igor-grubic merged 3 commits into
mainfrom
LLMO-6405-ui-brands-management-market-changes
Jul 22, 2026
Merged

feat(serenity): org-flag brand-create mode, sub-workspace-only pending activation, site_id anchoring (LLMO-6405)#2878
igor-grubic merged 3 commits into
mainfrom
LLMO-6405-ui-brands-management-market-changes

Conversation

@igor-grubic

Copy link
Copy Markdown
Contributor

What

Backend for the brand & market management rework (serenity-docs #68, LLMO-6405). Market-scoped inputs move out of brand creation into market creation.

  • Create mode (A): createBrandForOrg decides Semrush mode from the org serenity flag (isSerenityActiveForOrg), not the request body. A non-serenity org that sends a market or generatePrompts:true403; a bare generatePrompts:false stays a flat create.
  • Market-less create (B): a serenity create with no market provisions a bare sub-workspace (no project) via the new provisionBrandSubworkspaceBare; a supplied market keeps the existing project path.
  • Pending activation (C): /serenity/activate on a pending brand is sub-workspace-only (ensure sub-workspace + flip active + clear stash) — it never auto-creates a market. The body-driven market path runs only for an already-active brand (reactivation / onboarding API) and never downgrades it on a partial failure (207).
  • site_id anchoring (D / R6): upsertBrand writes brands.site_id from baseSiteId even on the Semrush path (anchored by both sub-workspace and primary site).

Tests

Recast the /serenity/activate suite to the new model + added pending sub-workspace-only cases; updated upsertBrand site_id tests; new coverage for provisionBrandSubworkspaceBare. npm test + coverage green locally; type-check + eslint clean. The Postgres IT suite runs here on CI (it can't run in the dev environment).

Related

  • UI counterpart: project-elmo-ui#2493 (same branch).
  • Draft until CI (incl. IT) is green and docs (serenity.md + OpenAPI) land.

Follow-ups (not in this PR)

  • mysticat-data-service migration: tighten active ⇒ site_id NOT NULL + backfill NULL site_id (sequenced after this plumbing).
  • OpenAPI brands-v2/serenity + docs/serenity.md updates.

🤖 Generated with Claude Code

…g activation, site_id anchoring (LLMO-6405)

Backend for the brand & market management rework (serenity-docs #68). Market-scoped
inputs move out of brand creation into market creation.

- createBrandForOrg: Semrush mode is decided by the org serenity flag
  (isSerenityActiveForOrg), not the request body. A non-serenity org sending a
  market or generatePrompts:true is rejected (403); a bare generatePrompts:false
  stays a flat create. A serenity create WITHOUT a market provisions a bare
  sub-workspace (no project) via the new provisionBrandSubworkspaceBare; a supplied
  market keeps the existing project path.
- upsertBrand: write brands.site_id from baseSiteId even on the Semrush-anchored
  path (a Semrush brand is anchored by BOTH its sub-workspace and its primary site).
  The DB CHECK tightening + NULL backfill remain a follow-up migration.
- /serenity/activate: a pending brand activates sub-workspace-only (ensure
  sub-workspace + flip active + clear stash), never auto-creating a market. The
  body-driven market path runs only for an already-active brand (reactivation /
  onboarding API) and never downgrades it on a partial failure (207).

Tests: serenity activate block recast to the new model + new pending
sub-workspace-only cases; upsertBrand site_id tests updated; provisionBrandSubworkspaceBare
covered. npm test + coverage green; type-check + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@igor-grubic
igor-grubic marked this pull request as ready for review July 21, 2026 15:00
@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@igor-grubic
igor-grubic requested a review from MysticatBot July 21, 2026 15:04
…t-management rework (LLMO-6405)

- serenity.md: create mode is decided by the org serenity flag (not the body); a
  market-less serenity create provisions a bare sub-workspace; a pending brand
  activates sub-workspace-only (no market, no stash-driven provisioning); an
  already-active brand's reactivation is body-driven and never downgraded (207);
  brands.site_id anchors the brand from baseSiteId; site-mirror lifecycle updated.
- openapi/schemas.yaml: add baseSiteId to V2BrandInput; rewrite the
  SerenityActivateRequest description (pending = sub-workspace-only, ignores
  markets/stash; active = body-driven) and the generatePrompts/modelIds field docs.

docs:lint passes (API description valid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @igor-grubic,

Verdict: Request changes - three test coverage gaps for new code paths.
Complexity: HIGH - large diff; API surface.
Changes: Reworks brand-create mode detection to use the org-level serenity flag, makes pending activation sub-workspace-only, removes the Semrush site_id skip, and adds provisionBrandSubworkspaceBare (8 files).

Note: CI checks are currently failing - resolve before merge.

Must fix before merge

  1. [Important] No controller-level test for provisionBrandSubworkspaceBare failure during create - test/controllers/brands.test.js (details inline)
  2. [Important] No assertion that ensureMarketSite is skipped on the bare sub-workspace create path - test/controllers/brands.test.js (details inline)
  3. [Important] Missing test for generatePrompts:true (no market) from a non-serenity org triggering 403 - test/controllers/brands.test.js (details inline)
Non-blocking (3): minor issues and suggestions
  • nit: deleteAllProjects is called on a known-empty workspace in the provisionBrandSubworkspaceBare catch block - src/support/serenity/brand-provisioning.js:~460. The comment says it tolerates empty workspaces, but it is a wasted API call; consider removing it and calling only releaseFullAllocation.
  • nit: Test title "STILL enters Semrush mode for a PENDING draft with generatePrompts:false and no market" is now misleading - it enters Semrush mode because the org is serenity-active, not because of the flag's presence - test/controllers/brands.test.js:~5714.
  • suggestion: The double-guard provisionedWorkspaceId && hasText(provisionedWorkspaceId) && provisionedBrandDomain && hasText(provisionedBrandDomain) is redundant since hasText already handles falsy values - a single hasText(x) per variable suffices - src/controllers/brands.js:1718.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 2m 53s | Cost: $7.75 | Commit: 8e40d4ee4c4683cb2e996228ac42b29e5f5e8434
If this code review was useful, please react with 👍. Otherwise, react with 👎.

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH labels Jul 21, 2026
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…e-create coverage (LLMO-6405)

- IT (test/it/shared/tests/brands.js): the LLMO-5870 unset+reuse test created bare
  brands and relied on the legacy no-anchor -> pending demotion. In a serenity-active
  org every create is now a Semrush create (a bare create provisions a sub-workspace
  and lands active), so create the two brands explicitly pending — the unset/reuse
  mechanic under test is unchanged. Fixes the it-postgres failure on #2878.
- unit (test/controllers/brands.test.js): address the review coverage gaps — a bare
  sub-workspace provisioning failure surfaces and writes no brand row; ensureMarketSite
  is skipped on the bare path; a non-serenity org requesting generatePrompts:true (no
  market) is rejected 403. Retitle the pending-serenity-create test (it enters Semrush
  mode because the org is serenity-active, not because of the flag's presence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@igor-grubic

Copy link
Copy Markdown
Contributor Author

Thanks @MysticatBot — addressed in d82c760, and the it-postgres failure was a real one (now fixed).

Must-fix (all done):

  1. ✅ Added a controller test — a provisionBrandSubworkspaceBare failure during create surfaces the error (500) and writes no brand row.
  2. ✅ The bare-create test now asserts ensureMarketSite is not called (no market domain to mirror on the bare path).
  3. ✅ Added a test — a non-serenity org requesting generatePrompts: true (no market) → 403.

CI (it-postgres): the failure was the LLMO-5870 unset+reuse IT test — it created bare brands relying on the old no-anchor→pending demotion. Since a serenity-active org now makes every create a Semrush create (bare sub-workspace → active), the test now creates those brands explicitly pending; the unset/reuse mechanic under test is unchanged.

Nits:

  • ✅ Renamed the misleading "STILL enters Semrush mode…" test.
  • (nit) deleteAllProjects in the bare catch — kept for symmetry with provisionBrandSubworkspace's cleanup; it's a no-op on a known-empty bare workspace, so no behavior impact. Happy to drop it if you'd prefer.
  • (nit) the provisionedBrandDomain && hasText(...) double-guard is required, not redundant: brands.js is under the type-check gate and hasText isn't a TS type-guard, so hasText(string | null) fails tsc without the && narrow.

@aenascut aenascut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Checked the serenity migration skill in mysticat-data-service against this PR — no impact. Migration talks directly to Semrush APIs + Postgres, bypasses spacecat-api-service entirely, so the brand-create mode switch, provisionBrandSubworkspaceBare, and the pending-activation change don't touch it. The site_id reads in the migration use .get() so any existing NULLs are handled safely too.

Did notice the follow-up you called out — tightening active ⇒ site_id NOT NULL + backfill. We'll keep an eye on that when it lands to make sure the migration's persist step stays in sync. Thanks!

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @igor-grubic,

Verdict: Approve - all prior findings addressed, tests well-structured, CI green.
Complexity: HIGH - large diff; API surface.
Changes: Reworks brand-create mode detection to use the org-level serenity flag, makes pending activation sub-workspace-only, removes the Semrush site_id skip, and adds provisionBrandSubworkspaceBare (11 files).
Note: Recommend a human read before merge - this change amends a shared contract (docs/openapi/schemas.yaml). The bot review is a complement to, not a replacement for, a human read here.

Previously flagged, now resolved

  • Controller test for provisionBrandSubworkspaceBare failure now verifies 500 + no brand row written.
  • Bare-create test now asserts ensureMarketSite is not called (no market domain to mirror).
  • Non-serenity org requesting generatePrompts:true now covered with a 403 assertion.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 2s | Cost: $3.37 | Commit: d82c760644408609296bcafae6c159f91b26843e
If this code review was useful, please react with 👍. Otherwise, react with 👎.

@MysticatBot MysticatBot added the needs-human-review AI reviewer recommends a human read before merge label Jul 22, 2026
@igor-grubic
igor-grubic merged commit a9d7a4a into main Jul 22, 2026
20 checks passed
@igor-grubic
igor-grubic deleted the LLMO-6405-ui-brands-management-market-changes branch July 22, 2026 08:58
solaris007 pushed a commit that referenced this pull request Jul 22, 2026
# [1.677.0](v1.676.0...v1.677.0) (2026-07-22)

### Features

* **serenity:** org-flag brand-create mode, sub-workspace-only pending activation, site_id anchoring (LLMO-6405) ([#2878](#2878)) ([a9d7a4a](a9d7a4a))
@solaris007

Copy link
Copy Markdown
Member

🎉 This PR is included in version 1.677.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants