Skip to content

feat(facs): enforce site-scoped LLMO routes in facsWrapper via secondary resource param - #3030

Open
ravverma wants to merge 9 commits into
mainfrom
feat/facs-wrapper-secondary-resource-param
Open

feat(facs): enforce site-scoped LLMO routes in facsWrapper via secondary resource param#3030
ravverma wants to merge 9 commits into
mainfrom
feat/facs-wrapper-secondary-resource-param

Conversation

@ravverma

Copy link
Copy Markdown
Contributor

🚧 Draft — blocked on adobe/spacecat-shared#1870.
Do not merge until that PR releases and the @adobe/spacecat-shared-http-utils dependency here is
bumped to the version with secondary-resolver support. Merging earlier would (a) leave the
secondaryResolvers wiring inert (the old wrapper ignores it) and (b) drop the #2947 site check
before the wrapper secondary is active — a brief enforcement gap.

What

Enforces site-scoped LLMO routes in facsWrapper via the new secondary FACS resource param
(site → brands), moving the check out of controllers so new LLMO site endpoints are covered
automatically.

  • routes/facs-capabilities.js: PRODUCTS_FACS_SECONDARY_RESOURCE.LLMO = { resourceType: 'site', aliases: ['siteId'], resolver: 'llmoSiteToBrands' }.
  • support/facs-secondary-resolvers.js (new): hasCapabilityOnSiteBrands — org-scoped listBrandIdsForSite + a single listResourceIdsWithCapability query (parity with AccessControlUtil.hasLlmoCapabilityForSite) + the secondaryResolvers registry.
  • index.js: passes secondaryResolvers to facsWrapper.
  • support/edge-routing-auth.js: surgical revert of PR feat(llmo): FACS-capability auth for edge CDN routing; edge-optimize-config under can_configure #2947's FACS-deferred branch (superseded by the wrapper); keeps the paid-legacy / trial / unknown-tier paths.

Why

Per mac-state-layer.md Decision 5, LLMO's ReBAC resource is brand; site routes carry siteId and
today defer to controllers (e.g. hasLlmoCapabilityForSite), which does not scale. Design ADR:
mysticat-architecture/platform/decisions/facs-wrapper-secondary-resource-param.md
(adobe/mysticat-architecture#241, MysticatBot-approved).

Before this is ready for review

Tests

New test/support/facs-secondary-resolvers.test.js; updated test/routes/facs-capabilities.test.js
and test/support/edge-routing-auth.test.js (dropped the superseded #2947 cases). Full npm test
green; type-check (base+strict) and lint clean.

🤖 Generated with Claude Code

…ary resource param

Adds the LLMO secondary FACS resource (site -> brands) config + resolver and wires it into the facsWrapper chain, so site-scoped LLMO routes are authorized in the wrapper instead of per-controller. facs-capabilities.js: PRODUCTS_FACS_SECONDARY_RESOURCE.LLMO = { site, [siteId], llmoSiteToBrands }. support/facs-secondary-resolvers.js: hasCapabilityOnSiteBrands (org-scoped listBrandIdsForSite + single listResourceIdsWithCapability, parity with hasLlmoCapabilityForSite) + secondaryResolvers registry. index.js: pass secondaryResolvers to facsWrapper. edge-routing-auth.js: surgical revert of PR #2947's FACS-deferred branch (superseded by the wrapper); keeps paid-legacy / trial / unknown-tier paths.

Requires @adobe/spacecat-shared-http-utils with secondary-resolver support (spacecat-shared#1870); dependency bump + activation lands once that releases. Design: mysticat-architecture/platform/decisions/facs-wrapper-secondary-resource-param.md (#241).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

@ravverma
ravverma deployed to dev-branches August 11, 2026 12:58 — with GitHub Actions Active
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…FACS paid orgs

The surgical #2947 revert must not send FACS-enrolled paid orgs through the legacy IMS product-context check — they are authorized upstream by facsWrapper (secondary resource param). Gate the check on the JWT facs_enabled claim (not context.attributes.facs, which is only set when the wrapper DEFERS — no longer the case for site routes). Mirrors AccessControlUtil.hasLlmoCapabilityForSite's own gating. Adds a unit test for the FACS-enabled paid path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ravverma
ravverma deployed to dev-branches August 11, 2026 13:13 — with GitHub Actions Active
…can_configure

Moves the CloudFront/Cloudflare/Akamai cdn-onboard routes from INTERNAL_ROUTES to PRODUCTS_ROUTES.LLMO (llmo/can_configure), making them customer-reachable under the ReBAC model — enforced by facsWrapper's secondary resource param (site -> brands). Swaps the controller gates from isLLMOAdministrator() to hasLlmoCapabilityForSite(site) (FACS capability, isLLMOAdministrator fallback for non-FACS orgs) via llmoForbiddenMessage(). Org routes (:spaceCatId onboard-site, semrush-onboarding) intentionally left in INTERNAL_ROUTES pending a team decision. required-capabilities.js (separate S2S layer) unchanged except two stale comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ravverma
ravverma deployed to dev-branches August 11, 2026 17:44 — with GitHub Actions Active
@ravverma
ravverma deployed to dev-branches August 12, 2026 03:43 — with GitHub Actions Active
@ravverma
ravverma deployed to dev-branches August 12, 2026 04:10 — with GitHub Actions Active
@ravverma
ravverma deployed to dev-branches August 12, 2026 05:46 — with GitHub Actions Active
@ravverma
ravverma marked this pull request as ready for review August 12, 2026 05:50
@ravverma
ravverma deployed to dev-branches August 12, 2026 05:55 — with GitHub Actions Active
ravverma and others added 2 commits August 12, 2026 14:57
Replaces the temporary gist tarball stopgap with the released 1.35.0 (secondary FACS resource param + resolver registry from spacecat-shared#1870). Activates the secondaryResolvers wiring end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ravverma
ravverma deployed to dev-branches August 12, 2026 09:42 — with GitHub Actions Active
@ravverma
ravverma requested a review from MysticatBot August 12, 2026 09:46
@adobe adobe deleted a comment from MysticatBot Aug 12, 2026
@ravverma
ravverma requested review from MysticatBot and removed request for MysticatBot August 12, 2026 10:03

@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 @ravverma,

Verdict: Approve - well-structured authorization migration with correct fail-closed semantics throughout.
Complexity: HIGH - medium diff touching 3 signal domains (ReBAC/auth surface, API route classification, dependency bump).
Changes: Moves 28 LLMO CDN onboarding routes from INTERNAL_ROUTES to FACS-governed PRODUCTS_ROUTES with a new secondary resource resolver (site-to-brands cross-resource check), superseding the PR 2947 deferred-branch pattern (16 files).

Note: CI checks are currently failing (the pr-review check itself).

Non-blocking (3): minor issues and suggestions
  • suggestion: Add warn-level logging on fail-closed paths in hasCapabilityOnSiteBrands (missing site, missing org, empty brands) for production debugging - src/support/facs-secondary-resolvers.js:50-64. The resolver is silent on denial today; context.log is available.
  • suggestion: Add a startup assertion in src/index.js validating that every resolver string in PRODUCTS_FACS_SECONDARY_RESOURCE has a matching key in secondaryResolvers - prevents silent misconfiguration after a rename.
  • nit: The CLAUDE.md middleware stack list (lines 90-104) does not include facsWrapper, which is now the innermost enforcement wrapper with secondaryResolvers config - CLAUDE.md:90

@MysticatBot

Copy link
Copy Markdown

Mysticat review failed: Claude CLI crashed (exit 1): stderr= stdout={"type":"result","subtype":"error_max_budget_usd","duration_ms":10,"duration_api_ms":1509888,"is_error":true,"num_turns":1,"stop_reason":null,"session_id":"52757ae7-8a98-4960-918b-8f2c9b5880ef","total_cost_usd":10.252599349999999,"usage":{"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_

…sWrapper

Addresses non-blocking mysticatBot review feedback on #3030:
- (#1) hasCapabilityOnSiteBrands was silent on denial; each fail-closed branch
  now logs (tag `facs-secondary`, distinct `reason`) for production debugging —
  `warn` for anomalies (no-postgrest, no-ims-org), `info` for ordinary
  data-shape denials (site-not-found, no-brands) to stay low-noise. The normal
  "evaluated, not authorized" outcome is intentionally not logged.
- (#3) add `facsWrapper` (innermost enforcement wrapper) to the CLAUDE.md
  middleware-stack list.
- Tests assert each fail-closed branch logs its reason.

Skipped (#2) resolver-key startup assertion: already covered — the wrapper
throws at construction on an unregistered resolver (facs-wrapper.js), caught at
Lambda cold start and by the CI bundle smoke check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ravverma
ravverma deployed to dev-branches August 12, 2026 12:33 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants