feat : integrate mac facs capabilities#2433
Draft
ravverma wants to merge 9 commits into
Draft
Conversation
Implements api-service side of Phase 1 of the MAC state layer design
(mysticat-architecture/platform/decisions/mac-state-layer.md).
src/routes/facs-capabilities.js — new file. Top-level shape:
{ INTERNAL_ROUTES: [...], PRODUCTS_ROUTES: { LLMO, ASO, ACO } }
Each product owns full FACS permission strings (<product>/<action>) per
route — decoupled from the original flat route → action map so each
product MAC policy can name roles independently. Coverage invariant:
routes(product) ∪ INTERNAL_ROUTES = all routes in src/routes/index.js,
with routes(product) ∩ INTERNAL_ROUTES = ∅.
LLMO populated with 383 routes:
- llmo/can_view × 288
- llmo/can_configure × 76
- llmo/can_onboard × 10
- llmo/can_deploy × 9
INTERNAL_ROUTES × 55 — admin/S2S/restricted/infra surfaces excluded
from FACS enforcement (each entry annotated with its gating mechanism
inline). ASO/ACO sub-maps stubbed empty pending MAC policy.
src/index.js — import facsWrapper from @adobe/spacecat-shared-http-utils
and add .with(facsWrapper, { routeFacsCapabilities }) as the innermost
wrapper (runs last, after readOnlyAdminWrapper).
test/routes/facs-capabilities.test.js — pins the shape contract and the
coverage invariant: top-level shape, INTERNAL_ROUTES uniqueness, product
keys uppercase, permission strings prefixed with their product, no stale
routes (every entry must exist in src/routes/index.js), and the
union/disjointness invariant for every populated product.
package.json — temporarily pinned spacecat-shared-http-utils to a gist
tarball containing the facsWrapper implementation; will revert to a
released version once the package publishes facsWrapper.
Co-Authored-By: Claude Sonnet 4.7 <noreply@anthropic.com>
Cross-checked LLMO POST routes against the S2S source-of-truth in src/routes/required-capabilities.js. Two corrections in each direction: POSTs marked :read in S2S (confirmed query operations) — moved to can_view: - POST /sites/:siteId/autofix-checks (was can_deploy) - POST /sites/:siteId/llmo/sheet-data/:dataSource (was can_configure) - POST /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource (was can_configure) - POST /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource(was can_configure) POSTs marked :write in S2S (mutating, not body-based queries) — moved to can_configure: - POST /llmo/agentic-traffic/global (S2S: report:write; was can_view) - POST /sites/:siteId/traffic/predominant-type (S2S: site:write; was can_view) - POST /sites/:siteId/traffic/predominant-type/:channel (S2S: site:write; was can_view) Counts after the rebalance: - llmo/can_view: 288 -> 289 - llmo/can_configure: 76 -> 76 (out 3 sheet-data, in 3 traffic writes) - llmo/can_onboard: 10 -> 10 - llmo/can_deploy: 9 -> 8 (autofix-checks moved out) - LLMO total: 383 -> 383 Shape contract + coverage invariant tests still pass (14/14). Lint clean. Co-Authored-By: Claude Sonnet 4.7 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the api-service side of Phase 1 of the MAC State Layer design (
platform/decisions/mac-state-layer.md) — externalising LLMO access decisions to FACS while keeping all existing auth paths working unchanged.Three things ship here:
src/routes/facs-capabilities.js— new file. The route → permission map thatfacsWrapperenforces against the JWT'sfacs_permissionsclaim.src/index.js— wiresfacsWrapperfrom@adobe/spacecat-shared-http-utilsas the innermost wrapper in the chain (runs afterreadOnlyAdminWrapper).test/routes/facs-capabilities.test.js— pins the shape contract and the coverage invariant againstsrc/routes/index.js.Capability model
The map departs from the original flat
route → actionproposal in favour of a per-product, full-permission-string shape. Each product (LLMO, ASO, ACO) authors its MAC policy independently with its own role and action vocabulary, so a runtime<product>/<action>composition would force every product into LLMO's vocabulary or duplicate maps anyway. Top-level shape:INTERNAL_ROUTES(55 routes) — admin-only / S2S-only / restricted / pure infrastructure surfaces.facsWrapperdoes NOT act on this list; it's here for the coverage invariant test. Internal endpoints are already covered by the identity bypass in the wrapper.PRODUCTS_ROUTES[<PRODUCT>]— the customer-facing route → permission map. Values are fully-qualified strings used verbatim byfacsWrapper(no runtime composition).LLMO permission set (agreed with the MAC team)
llmo/can_viewllmo/can_configurellmo/can_onboardllmo/can_deployllmo/can_manage_userPOST classification cross-checked against
src/routes/required-capabilities.js(the S2S source of truth).:readPOSTs map tocan_view,:writePOSTs tocan_configure(with onboard/deploy exceptions).Coverage invariant
For any populated product P:
For LLMO: 383 + 55 = 438 = total routes. Enforced by
test/routes/facs-capabilities.test.js— adding a new route tosrc/routes/index.jswithout categorising it as either product-scoped or internal will fail the test.Wrapper chain
facsWrapperis permissive-by-default — bypasses for OPTIONS preflight, internal identities (is_admin/is_s2s_admin/is_s2s_consumer/is_read_only_admin), Adobe internal IMS orgs, requests withoutx-product, products with no sub-map, and disabled per-product LD flags. Deny-by-default fires only inside an enrolled product when the route is unmapped or the caller lacks the required FACS permission.Dependency note (must be unwound before merge)
package.jsontemporarily pins@adobe/spacecat-shared-http-utilsto a gist tarball that includes thefacsWrapperimplementation. The corresponding change is on its way to the package via the parallel PR inadobe/spacecat-shared. Once that releases, the dependency line reverts to a normal semver pin andpackage-lock.jsonupdates withnpm install.Test plan
test/routes/facs-capabilities.test.js— 14 contract + invariant tests pass.routes(LLMO) ∪ INTERNAL_ROUTES = 438(full route surface).src/routes/index.js.test/index.test.js— 15/15 (wrapper wiring exercised; OPTIONS bypass; bypasses for legacy api-key callers).feat/mac-facs-capabilities.Related
mysticat-architecture/platform/decisions/mac-state-layer.md— updated to reflect the per-product capability model and to add theis_llmo_administrator→ FACS RBAC migration plan.adobe/spacecat-sharedaddingfacsWrapperto@adobe/spacecat-shared-http-utils.facs_permissionsclaim (already merged).Out of scope
is_llmo_administratorretirement and controller-side dual checks — see the "Migration" section of the design doc; tracked as a separate per-org rollout exercise.facs_access_mappingstable,/facs/access-mappings/*endpoints, resource-level enforcement) — explicitly deferred.🤖 Generated with Claude Code