---
id: ATS-PFM-SEC-015
scope: CAP-PFM-SEC-015
product_area: platform
module: security
revision: 3
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
scenarios:
  - id: ATS-PFM-SEC-015-01
    title: Reject unregistered sign-in origins
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-01
    preconditions: >-
      Prepare tokens from unknown issuers, inactive pools and unregistered clients, plus a request
      without a token.
    steps: Authorize each request.
    expected: Return 401 before identity or grant reads.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a request
        with no token at all
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown issuer: 401, and no pool, identity or grant is read
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a pool
        that is not active
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a client
        the pool does not register
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a request
      with no token at all
    category: validation
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a request
        with no token at all
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown issuer: 401, and no pool, identity or grant is read
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a pool
        that is not active
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a client
        the pool does not register
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a request
        with no token at all
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a request
        with no token at all
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-02
    title: Require an existing active identity
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-02
      - INV-DOM-PFM-SEC-003-02
      - INV-DOM-PFM-SEC-003-05
    preconditions: 'Prepare an unknown identity, revoked identity and suspended user.'
    steps: Attempt authorization and inspect writes.
    expected: Reject the requests without creating or updating identities.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown identity with 401 and writes nothing: the authorizer never creates one
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a revoked
        identity
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a
        suspended user with 401, and an invited or revoked one as inactive
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorizer-source.spec.ts#never
        inserts or updates anything in Postgres: every port it has is a read
      - TST-PFM-SEC-195
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an unknown
      identity with 401 and writes nothing: the authorizer never creates one
    category: validation
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown identity with 401 and writes nothing: the authorizer never creates one
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a revoked
        identity
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a
        suspended user with 401, and an invited or revoked one as inactive
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorizer-source.spec.ts#never
        inserts or updates anything in Postgres: every port it has is a read
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown identity with 401 and writes nothing: the authorizer never creates one
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an
        unknown identity with 401 and writes nothing: the authorizer never creates one
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-03
    title: 'Separate portal, console and customer reach'
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-03
    preconditions: Prepare portal and ordinary console callers.
    steps: >-
      Use portal credentials on console, system and another workspace’s routes; use console
      credentials across customers.
    expected: Reject every boundary crossing.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a console route before any identity is read
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a system route the same way
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        user of another tenant than the pool’s
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#never lists a
        tenant of another customer for a console user, and does not lock the route for its own
        tenants
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
      token on a console route before any identity is read
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a console route before any identity is read
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a system route the same way
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        user of another tenant than the pool’s
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#never lists a
        tenant of another customer for a console user, and does not lock the route for its own
        tenants
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a console route before any identity is read
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a portal
        token on a console route before any identity is read
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-04
    title: Limit staff grants to HQ
    level: api
    proves:
      - BR-CAP-PFM-SEC-015-04
    preconditions: Load the declared operations and callers for customer Administrator and the three staff roles.
    steps: 'Exercise console and portal routes with staff authority, then HQ routes with each caller.'
    expected: >-
      Staff grants do not open console or portal routes. HQ admits authorized staff, rejects
      customer authority as non-staff, and enforces each staff role’s keys.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses the widest
        grant Rhythm holds, every one of them
      - TST-PFM-SEC-067
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#admits the Rhythm
        Administrator, who is answered by the operation rather than refused
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#tells that
        Administrator they are not staff, rather than that a permission is missing
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses a Support
        Representative every staff route their policy does not carry
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses the widest
      grant Rhythm holds, every one of them
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses the widest
        grant Rhythm holds, every one of them
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#admits the Rhythm
        Administrator, who is answered by the operation rather than refused
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#tells that
        Administrator they are not staff, rather than that a permission is missing
      - >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses a Support
        Representative every staff route their policy does not carry
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses the widest
        grant Rhythm holds, every one of them
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/staff-separation.e2e.spec.js#refuses the widest
        grant Rhythm holds, every one of them
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-05
    title: Reject inconsistent route declarations
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-05
    preconditions: Construct manifests with incorrect staff keys and empty protected permissions.
    steps: Validate the manifests together.
    expected: >-
      Report all declaration problems, including ordinary keys on system routes and staff keys
      elsewhere.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        staffOnly key below a system route
      - TST-PFM-SEC-196
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        system route required by an ordinary key, which a customer administrator could hold
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        protected route that requires nothing, and a public route that requires something
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#collects
        every problem at once, so a deploy is told all of them
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a route
        the registry does not declare: a Deny the gateway may cache
      - TST-PFM-SEC-194
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
      staffOnly key below a system route
    category: validation
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        staffOnly key below a system route
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        system route required by an ordinary key, which a customer administrator could hold
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        protected route that requires nothing, and a public route that requires something
      - >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#collects
        every problem at once, so a deploy is told all of them
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a route
        the registry does not declare: a Deny the gateway may cache
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        staffOnly key below a system route
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/registry-manifest.spec.ts#refuses a
        staffOnly key below a system route
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-06
    title: Restrict authorization to grant reach
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-06
    preconditions: Give a caller grants for selected workspaces.
    steps: Authorize reached and unreached paths and an operation no grant covers.
    expected: >-
      The grant slice lists only reachable workspaces; inaccessible paths and uncovered operations
      are denied.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#lists only the
        tenants a grant could reach, not every tenant of the customer
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#returns a Deny
        for the whole route when no grant could apply anywhere
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a system
        route without a platform-scope assignment
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#lists only the
      tenants a grant could reach, not every tenant of the customer
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#lists only the
        tenants a grant could reach, not every tenant of the customer
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#returns a Deny
        for the whole route when no grant could apply anywhere
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a system
        route without a platform-scope assignment
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#lists only the
        tenants a grant could reach, not every tenant of the customer
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#lists only the
        tenants a grant could reach, not every tenant of the customer
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-07
    title: Sign bounded authority and fail closed
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-07
    preconditions: 'Prepare a valid request, an oversized slice and registry, grant-store and signing failures.'
    steps: Authorize each case and inspect the signed context.
    expected: >-
      The context contains no address or raw token and expires at cache lifetime plus thirty
      seconds. Oversize and unavailable inputs prevent an Allow.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context
        expires at iat + the gateway TTL + 30 s and carries no IP address
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a grant
        slice over 6 KB rather than truncating it
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#fails closed when
        the registry cannot be read at all
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#fails closed when
        the grants cannot be read
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#a KMS failure is
        context_unavailable: there is no Allow without a context
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context it
        signs is the only thing that carries authority: the raw token never reaches the backend
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context expires
      at iat + the gateway TTL + 30 s and carries no IP address
    category: invariant
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context
        expires at iat + the gateway TTL + 30 s and carries no IP address
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a grant
        slice over 6 KB rather than truncating it
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#fails closed when
        the registry cannot be read at all
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#fails closed when
        the grants cannot be read
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#a KMS failure is
        context_unavailable: there is no Allow without a context
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context it
        signs is the only thing that carries authority: the raw token never reaches the backend
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context
        expires at iat + the gateway TTL + 30 s and carries no IP address
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the context
        expires at iat + the gateway TTL + 30 s and carries no IP address
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-08
    title: Describe refusals without identity data
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-08
    preconditions: 'Arrange missing-permission, workspace-boundary and system-access refusals.'
    steps: Inspect their metadata.
    expected: Return the relevant cause and required keys without caller identity data.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the keys
        the route requires and the cause permission on a route Deny for a missing grant
      - TST-PFM-SEC-194
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the cause
        access, never permission, when holding the key would not help (a system route without staff
        authority)
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the refusal
        reason and the missing key, both strings, and nothing that identifies the caller
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the keys the
      route requires and the cause permission on a route Deny for a missing grant
    category: edge
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the keys
        the route requires and the cause permission on a route Deny for a missing grant
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the cause
        access, never permission, when holding the key would not help (a system route without staff
        authority)
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the refusal
        reason and the missing key, both strings, and nothing that identifies the caller
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the keys
        the route requires and the cause permission on a route Deny for a missing grant
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#names the keys
        the route requires and the cause permission on a route Deny for a missing grant
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-09
    title: Apply application access and licensing checks
    level: api
    proves:
      - BR-CAP-PFM-SEC-015-09
    preconditions: Prepare an application-access denial and a withheld workspace licence.
    steps: Request the affected application and Platform in each case.
    expected: >-
      The application returns the appropriate access or licensing code; Platform still serves its
      route.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#evaluates
        the application access step in enforce, which is what every case below is about
      - TST-PFM-SEC-047
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused it once a deny names the access key, though the allow beside it is untouched
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#is
        still served a Platform route, so the person can read their own context and be told why
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#refuses
        the same route in the withheld tenant, under the licence step’s own code
    provenance: test
    source: >-
      services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#evaluates
      the application access step in enforce, which is what every case below is about
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#evaluates
        the application access step in enforce, which is what every case below is about
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#is
        refused it once a deny names the access key, though the allow beside it is untouched
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#is
        still served a Platform route, so the person can read their own context and be told why
      - >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#refuses
        the same route in the withheld tenant, under the licence step’s own code
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#evaluates
        the application access step in enforce, which is what every case below is about
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/e2e/application-access-enforced.e2e.spec.js#evaluates
        the application access step in enforce, which is what every case below is about
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-10
    title: Measure the gateway cache limit
    level: api
    proves:
      - BR-CAP-PFM-SEC-015-11
    preconditions: 'Establish a cached Allow, then revoke its grant.'
    steps: Submit a flush and request the route before and after cache expiry.
    expected: >-
      A cached Allow may persist within the window; a fresh decision after expiry refuses. Workflow
      success alone does not prove eviction.
    negative: true
    data_needs: >-
      A deployed gateway is needed to measure persistence of cached decisions; local bindings
      establish only flush-workflow execution.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js#succeeds,
        having flushed every stage
      - TST-PFM-SEC-081
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js#succeeds,
      having flushed every stage
    category: boundary
    test_references:
      - >-
        services-platform:modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js#succeeds,
        having flushed every stage
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js#succeeds,
        having flushed every stage
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js#succeeds,
        having flushed every stage
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-11
    title: Validate service-function route ownership
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-13
    preconditions: Prepare service operations and function declarations.
    steps: >-
      Validate an unserved route, sensitive route behind an ordinary function and function naming an
      unknown route.
    expected: Reject each declaration and identify the offending route or permission.
    negative: true
    data_needs: Use the records described in the setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a service route no service function lists, naming it
      - TST-PFM-SEC-198
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a route whose catalog permission is sensitive behind a function that is not, naming the key
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a function that lists a route absent from the route list
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
      a service route no service function lists, naming it
    category: permission
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a service route no service function lists, naming it
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a route whose catalog permission is sensitive behind a function that is not, naming the key
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a function that lists a route absent from the route list
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a service route no service function lists, naming it
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a service route no service function lists, naming it
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-12
    title: Enforce access at request boundaries with maintain workspace information and operating status
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-002-09
      - BR-CAP-PFM-SEC-015-06
    exercises:
      - CAP-PFM-SEC-015
      - CAP-PFM-SEC-002
    preconditions: An ordinary caller has valid authority over an active workspace.
    steps: Disable the workspace and issue a new protected request after the availability cache expires.
    expected: The new request is refused even though the role assignment remains present.
    negative: true
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities, and records owned by
      this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/tenants/services/access/tenant-availability.ts:tenantAvailabilityOf
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#returns a Deny
        for the whole route when no grant could apply anywhere
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/tenants/services/access/tenant-availability.ts:tenantAvailabilityOf
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#returns a Deny
          for the whole route when no grant could apply anywhere
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/tenants/services/access/tenant-availability.ts:tenantAvailabilityOf
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#returns a Deny
          for the whole route when no grant could apply anywhere
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-015-13
    title: Enforce access at request boundaries with understand available workspace access
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-007-03
      - BR-CAP-PFM-SEC-015-07
    exercises:
      - CAP-PFM-SEC-015
      - CAP-PFM-SEC-007
    preconditions: A member has an allow and a matching deny.
    steps: Read effective access and issue the denied operation through fresh gateway evaluation.
    expected: The access explanation and gateway agree that the denied permission is absent.
    negative: true
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities, and records owned by
      this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/integration/services/security-context/get-security-context.integration.spec.js#takes
        the denied key out of the allowed keys, though another grant allows it
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a grant
        slice over 6 KB rather than truncating it
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/security-context/get-security-context.integration.spec.js#takes
          the denied key out of the allowed keys, though another grant allows it
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a grant
          slice over 6 KB rather than truncating it
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/security-context/get-security-context.integration.spec.js#takes
          the denied key out of the allowed keys, though another grant allows it
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses a grant
          slice over 6 KB rather than truncating it
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-015-14
    title: Enforce access at request boundaries with assign roles within a controlled scope
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-010-03
      - BR-CAP-PFM-SEC-015-12
    exercises:
      - CAP-PFM-SEC-015
      - CAP-PFM-SEC-010
    preconditions: A member holds a record-type grant for one customer record type.
    steps: Request the permitted type and another type through the public operation.
    expected: >-
      The gateway permits applicable passage while the resource service hides the unrelated record
      type.
    negative: false
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities, and records owned by
      this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
        an archived custom object, naming the object key, and writes nothing
      - >-
        services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the gateway lets
        the holder through to an object the scope does not name, so the service answers it as not
        found
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
          an archived custom object, naming the object key, and writes nothing
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the gateway
          lets the holder through to an object the scope does not name, so the service answers it as
          not found
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/membership/resource-scopes.integration.spec.js#refuses
          an archived custom object, naming the object key, and writes nothing
        - >-
          services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#the gateway
          lets the holder through to an object the scope does not name, so the service answers it as
          not found
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-015-15
    title: Enforce access at request boundaries with suspend and resume service principals
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-015-13
      - BR-CAP-PFM-SEC-018-04
    exercises:
      - CAP-PFM-SEC-015
      - CAP-PFM-SEC-018
    preconditions: An active registered service principal can call its declared operation.
    steps: 'Suspend it, wait for registry refresh, call again, resume it, and repeat after refresh.'
    expected: The suspended caller is refused; the resumed eligible caller succeeds without deployment.
    negative: true
    data_needs: >-
      An isolated customer with two workspaces, ordinary and staff identities, and records owned by
      this run.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
        a route whose catalog permission is sensitive behind a function that is not, naming the key
      - >-
        services-platform:modules/security/tests/integration/services/service-principals/registered-principals.integration.spec.js#is
        refused 403
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
          a route whose catalog permission is sensitive behind a function that is not, naming the
          key
        - >-
          services-platform:modules/security/tests/integration/services/service-principals/registered-principals.integration.spec.js#is
          refused 403
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/service-function-rules.spec.ts#refuses
          a route whose catalog permission is sensitive behind a function that is not, naming the
          key
        - >-
          services-platform:modules/security/tests/integration/services/service-principals/registered-principals.integration.spec.js#is
          refused 403
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-015-16
    title: Registry loading fails when throttling leaves no usable manifest
    category: failure
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-07
    preconditions: Start a manifest loader with an empty cache and a registry-read double that always throttles.
    steps:
      - do: Load the configured service and stage while recording registry-read attempts.
        expect: Loading rejects after three read attempts; no manifest is returned by the loader.
        provenance: test
        source:
          - >-
            services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#refuses
            with the throttle when no manifest was ever loaded
    expected: Loading rejects after three read attempts; no manifest is returned by the loader.
    negative: true
    data_needs: >-
      Run-owned input values, dependency counters and storage fixtures; no shared records are
      mutated.
    bound_to:
      - TST-PFM-SEC-271
    provenance: test
    source:
      - >-
        services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#refuses
        with the throttle when no manifest was ever loaded
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#refuses
        with the throttle when no manifest was ever loaded
    action_evidence:
      provenance: test
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#refuses
          with the throttle when no manifest was ever loaded
    assertion_evidence:
      provenance: test
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#refuses
          with the throttle when no manifest was ever loaded
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
  - id: ATS-PFM-SEC-015-17
    title: Concurrent loads of one registry partition share a read
    category: concurrency
    level: unit
    proves:
      - BR-CAP-PFM-SEC-015-07
    preconditions: Prepare a loader with a blocked registry-read double returning one valid manifest.
    steps:
      - do: Start fifty loads for the same service and stage before releasing the read.
        expect: All fifty loads return and the registry-read counter is one.
        provenance: test
        source:
          - >-
            services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#shares
            one read between concurrent loads of one partition
    expected: All fifty loads return and the registry-read counter is one.
    negative: false
    data_needs: >-
      Run-owned input values, dependency counters and storage fixtures; no shared records are
      mutated.
    bound_to:
      - TST-PFM-SEC-271
    provenance: test
    source:
      - >-
        services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#shares
        one read between concurrent loads of one partition
    test_references:
      - >-
        services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#shares
        one read between concurrent loads of one partition
    action_evidence:
      provenance: test
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#shares
          one read between concurrent loads of one partition
    assertion_evidence:
      provenance: test
      source:
        - >-
          services-platform:modules/security/tests/unit/authorizer/manifest-throttling.spec.ts#shares
          one read between concurrent loads of one partition
    binding_scope: >-
      The binding applies to the cited executable observations and their dependency boundary. Extra
      setup, operations or combinations in this scenario still require evidence before the whole
      scenario is qualified.
evidence:
  - >-
    Pinned services-platform references for the structured rows:
    modules/security/tests/e2e/application-access-enforced.e2e.spec.js;
    modules/security/tests/e2e/staff-separation.e2e.spec.js;
    modules/security/tests/integration/lifecycle/authorizer-flush/authorizer-flush-workflow.integration.spec.js;
    modules/security/tests/unit/authorizer/authorize.spec.ts;
    modules/security/tests/unit/authorizer/authorizer-source.spec.ts;
    modules/security/tests/unit/authorizer/registry-manifest.spec.ts;
    modules/security/tests/unit/authorizer/service-function-rules.spec.ts.
  - >-
    Testing back-fill reviews the pinned suite assertions, related capabilities, domain invariants,
    state transitions and workflow failure boundaries. New unbound rows remain proposed.
coverage:
  rule: 'gap: FND-PFM-SEC-072'
  transition: 'gap: FND-PFM-SEC-072'
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  rule: No scenario isolates this category. The referenced finding retains the missing design proof.
  transition: No scenario isolates this category. The referenced finding retains the missing design proof.
invariant_proofs:
  - scenario: ATS-PFM-SEC-015-02
    proves:
      - INV-DOM-PFM-SEC-003-02
      - INV-DOM-PFM-SEC-003-05
    provenance: test
    source: >-
      services-platform:modules/security/tests/unit/authorizer/authorize.spec.ts#refuses an unknown
      identity with 401 and writes nothing: the authorizer never creates one
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
permission_proof_review:
  - key: platform.application.access
    scenario_candidates:
      - ATS-PFM-SEC-015-03
      - ATS-PFM-SEC-015-04
      - ATS-PFM-SEC-015-06
      - ATS-PFM-SEC-015-09
      - ATS-PFM-SEC-015-11
      - ATS-PFM-SEC-015-12
      - ATS-PFM-SEC-015-14
      - ATS-PFM-SEC-015-15
    qualification: >-
      These are candidate scenarios for the authority boundary, not an exhaustive allowed/refused
      matrix for this key. The finding retains combinations not established by the executable
      assertions.
    gap: FND-PFM-SEC-072
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-015-decide-every-request-against-the-caller-s-grants.md
  - key: platform.security-self.read
    scenario_candidates:
      - ATS-PFM-SEC-015-03
      - ATS-PFM-SEC-015-04
      - ATS-PFM-SEC-015-06
      - ATS-PFM-SEC-015-09
      - ATS-PFM-SEC-015-11
      - ATS-PFM-SEC-015-12
      - ATS-PFM-SEC-015-14
      - ATS-PFM-SEC-015-15
    qualification: >-
      These are candidate scenarios for the authority boundary, not an exhaustive allowed/refused
      matrix for this key. The finding retains combinations not established by the executable
      assertions.
    gap: FND-PFM-SEC-072
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-015-decide-every-request-against-the-caller-s-grants.md
examiner: codex
examined_at: '2026-10-23'
final_text: OpenAI
---
# Enforce access at request boundaries: Capability Test Specification

## Coverage

| Required rule | Scenario or finding |
|---|---|
| `BR-CAP-PFM-SEC-015-01` | `ATS-PFM-SEC-015-01` |
| `BR-CAP-PFM-SEC-015-02` | `ATS-PFM-SEC-015-02` |
| `BR-CAP-PFM-SEC-015-03` | `ATS-PFM-SEC-015-03` |
| `BR-CAP-PFM-SEC-015-04` | `ATS-PFM-SEC-015-04` |
| `BR-CAP-PFM-SEC-015-05` | `ATS-PFM-SEC-015-05` |
| `BR-CAP-PFM-SEC-015-06` | `ATS-PFM-SEC-015-06`, `ATS-PFM-SEC-015-12` |
| `BR-CAP-PFM-SEC-015-07` | `ATS-PFM-SEC-015-07`, `ATS-PFM-SEC-015-13`, `ATS-PFM-SEC-015-16`, `ATS-PFM-SEC-015-17` |
| `BR-CAP-PFM-SEC-015-08` | `ATS-PFM-SEC-015-08` |
| `BR-CAP-PFM-SEC-015-09` | `ATS-PFM-SEC-015-09` |
| `BR-CAP-PFM-SEC-015-11` | `ATS-PFM-SEC-015-10` |
| `BR-CAP-PFM-SEC-015-13` | `ATS-PFM-SEC-015-11`, `ATS-PFM-SEC-015-15` |

The category map records specified proof. A covered category can contain proposed scenarios; it does not mean the checks have run. The bindings identify individual executable contributions, while findings retain missing rules, permissions, transitions and assertions.

## Interactions With Related Capabilities

The interaction scenarios connect this capability to the related actions and workflow handoffs named in their proof references. Each case observes the outcome after the connected action changes records or available authority. A collection of separately bound checks does not establish an ordered feature journey.

Source revisions and assertion references are recorded in frontmatter. Steps and expected observations carry separate evidence annotations. Proposed checks remain distinct from executable test statements, and static review supplies no run result.
