---
id: ATS-PFM-SEC-001
scope: CAP-PFM-SEC-001
product_area: platform
module: security
revision: 3
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-SEC-001-01
    title: Customer-level authority cannot create a customer
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-01
    preconditions: A customer administrator and staff callers with customer-create authority are available.
    steps: Attempt creation under customer-scoped authority and under permitted staff authority.
    expected: >-
      Customer-scoped creation returns forbidden while authorized staff creation succeeds; deletion
      separation is established by route evidence and still needs a dedicated refusal test.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a
        customer-scoped caller the system route that creates a customer
      - TST-PFM-SEC-041
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#refuses a create from
        an administrator of a customer as 403
      - TST-PFM-SEC-042
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer-scoped
      caller the system route that creates a customer
    category: permission
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a
        customer-scoped caller the system route that creates a customer
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#refuses a create from
        an administrator of a customer as 403
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a
        customer-scoped caller the system route that creates a customer
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a
        customer-scoped caller the system route that creates a 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-001-02
    title: Customer references require valid normalized unique values
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-02
      - INV-DOM-PFM-SEC-001-01
    preconditions: A customer reference is already assigned.
    steps: >-
      Request creation without a reference, with a generated-identifier shape, with a reserved
      reference and with taken references in plain and padded uppercase forms.
    expected: >-
      Invalid choices receive their required, invalid or reserved refusal; both spellings of an
      assigned reference receive the taken refusal.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that
        names no id as 400 customer_id_required, nothing generating one
      - TST-PFM-SEC-041
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a UUID-shaped id
        as 400 customer_id_invalid
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a reserved id as
        400 customer_id_reserved
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#reads
        a padded id in capitals as the id it normalizes to, and so finds it taken
      - TST-PFM-SEC-044
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that names
      no id as 400 customer_id_required, nothing generating one
    category: validation
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that
        names no id as 400 customer_id_required, nothing generating one
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a UUID-shaped id
        as 400 customer_id_invalid
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a reserved id as
        400 customer_id_reserved
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#reads
        a padded id in capitals as the id it normalizes to, and so finds it taken
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that
        names no id as 400 customer_id_required, nothing generating one
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that
        names no id as 400 customer_id_required, nothing generating 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-001-03
    title: Customer classification is required and closed
    level: unit
    proves:
      - BR-CAP-PFM-SEC-001-03
      - INV-DOM-PFM-SEC-001-02
    preconditions: Database fixtures can attempt direct customer inserts.
    steps: Insert an unsupported classification and a row without classification.
    expected: >-
      The database rejects both inserts through the closed classification and required-column
      constraints.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a type outside the closed list by customers_type_check
      - TST-PFM-SEC-043
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a row with no type, naming the column
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
      a type outside the closed list by customers_type_check
    category: validation
    test_references:
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a type outside the closed list by customers_type_check
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a row with no type, naming the column
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a type outside the closed list by customers_type_check
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        a type outside the closed list by customers_type_check
    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-001-04
    title: Address normalization and refusal protect customer records
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-04
      - INV-DOM-PFM-SEC-001-03
    preconditions: An editable customer and address inputs are available.
    steps: 'Save mixed-case and blank addresses, then submit an invalid address.'
    expected: >-
      Valid addresses are normalized, blank input leaves no address, and invalid input is rejected
      without an accepted write.
    negative: false
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        an address in its canonical form on create
      - TST-PFM-SEC-044
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        a blank address as none
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses an invalid
        e-mail address as 400 naming emailAddress
      - TST-PFM-SEC-041
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
      an address in its canonical form on create
    category: validation
    test_references:
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        an address in its canonical form on create
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        a blank address as none
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses an invalid
        e-mail address as 400 naming emailAddress
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        an address in its canonical form on create
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
        an address in its canonical form on create
    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-001-05
    title: Customer reads stay within the administrator’s boundary
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-05
    preconditions: Two customers exist and the caller administers one.
    steps: Read the customer list and request the unrelated customer.
    expected: The list contains only the authorized customer and the unrelated read returns not found.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#reduces the list to the
        caller's own customer when the caller is not staff
      - TST-PFM-SEC-041
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#answers a
        customer-scoped caller 404 for another customer's record, so the route cannot be walked
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#reduces the list to the
      caller's own customer when the caller is not staff
    category: boundary
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#reduces the list to the
        caller's own customer when the caller is not staff
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#answers a
        customer-scoped caller 404 for another customer's record, so the route cannot be walked
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#reduces the list to the
        caller's own customer when the caller is not staff
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#reduces the list to the
        caller's own customer when the caller is not staff
    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-001-06
    title: Stale replacement preserves the newer customer
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-06
      - INV-DOM-PFM-SEC-001-06
    preconditions: The customer has changed since the caller read its version.
    steps: Submit a replacement using that earlier version.
    expected: A stale-object conflict is returned and the newer stored record remains unchanged.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version
        as 409
      - TST-PFM-SEC-041
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#names StaleObject in the
        409
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#leaves
        the row at the version it reached when it refuses a stale version
      - TST-PFM-SEC-044
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version as
      409
    category: concurrency
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version
        as 409
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#names StaleObject in the
        409
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#leaves
        the row at the version it reached when it refuses a stale version
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version
        as 409
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version
        as 409
    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-001-07
    title: Proposed replacement proof reads the current commercial field
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-07
    preconditions: The customer has a populated commercial reference.
    steps: 'Replace the customer while omitting that reference, then read the persisted current field.'
    expected: >-
      The proposed proof must establish that the current field is absent; checking only a retired
      property name proves nothing about it.
    negative: false
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to: []
    provenance: proposed
    source: FND-PFM-SEC-032
    category: rule
    test_references: []
    action_evidence:
      provenance: proposed
      source: FND-PFM-SEC-032
    assertion_evidence:
      provenance: proposed
      source: FND-PFM-SEC-032
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-001-08
    title: Customer locks prevent the corresponding mutation
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-08
      - INV-DOM-PFM-SEC-001-05
    preconditions: Separate customer fixtures carry update and deletion locks.
    steps: Attempt the mutation restricted by each lock and read the customer afterward.
    expected: The service reports an illegal operation and the customer retains its prior stored state.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against updates as IllegalOperation
      - TST-PFM-SEC-044
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against deletes as IllegalOperation
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#leaves
        a locked record in place
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
      a record locked against updates as IllegalOperation
    category: invariant
    test_references:
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against updates as IllegalOperation
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against deletes as IllegalOperation
      - >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#leaves
        a locked record in place
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against updates as IllegalOperation
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
        a record locked against updates as IllegalOperation
    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-001-09
    title: HQ edits preserve their defined field boundary
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-10
    preconditions: A customer and a staff caller permitted to update it are available.
    steps: >-
      Edit the four supported fields, omit one, explicitly clear a nullable one and submit an
      unsupported field.
    expected: >-
      Supported changes persist, omission preserves the value, clearing removes it, and the
      unsupported field receives an invalid-request response.
    negative: false
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#edits the four HQ
        fields and answers the customer as the detail read answers it
      - TST-PFM-SEC-042
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#leaves a field the
        body does not name exactly as it was
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#clears a field the
        body sends as null
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#refuses a body naming
        a field the HQ screen does not edit as 400
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#edits the four HQ
      fields and answers the customer as the detail read answers it
    category: validation
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#edits the four HQ
        fields and answers the customer as the detail read answers it
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#leaves a field the
        body does not name exactly as it was
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#clears a field the
        body sends as null
      - >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#refuses a body naming
        a field the HQ screen does not edit as 400
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#edits the four HQ
        fields and answers the customer as the detail read answers it
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#edits the four HQ
        fields and answers the customer as the detail read answers 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-001-10
    title: Workspace dependencies block customer removal
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-11
      - INV-DOM-PFM-SEC-001-04
    preconditions: At least one workspace still references the customer.
    steps: Request customer deletion and read the customer afterward.
    expected: The request returns a dependency conflict and the customer remains readable.
    negative: true
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
        still has tenants as 409
      - TST-PFM-SEC-041
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#names
        ForeignKeyViolation in the 409
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        deleting a customer that tenants still reference by tenants_customer_fk
      - TST-PFM-SEC-043
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
      still has tenants as 409
    category: invariant
    test_references:
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
        still has tenants as 409
      - >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#names
        ForeignKeyViolation in the 409
      - >-
        services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
        deleting a customer that tenants still reference by tenants_customer_fk
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
        still has tenants as 409
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
        still has tenants as 409
    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-001-11
    title: Customer removal also removes standing staff grants
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-12
      - INV-DOM-PFM-SEC-001-07
    preconditions: The customer has no workspaces and a Rhythm Administrator has a standing grant to it.
    steps: Delete the customer through the path that removes standing access.
    expected: >-
      The transaction removes the customer and its standing grants, then submits a flush request;
      gateway eviction is outside what this test establishes.
    negative: false
    data_needs: Use the fixtures described by the cited test or the proposed scenario setup.
    bound_to:
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
        the customer
      - TST-PFM-SEC-191
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#submits
        one authorizer-cache flush naming the customer once the deletion has committed, so no cached
        answer outlives the grants
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
      the customer
    category: transition
    test_references:
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
        the customer
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#submits
        one authorizer-cache flush naming the customer once the deletion has committed, so no cached
        answer outlives the grants
    action_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
        the customer
    assertion_evidence:
      provenance: test
      source: >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
        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-001-12
    title: Establish and administer a customer with maintain workspace information and operating status
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-11
      - BR-CAP-PFM-SEC-002-01
    exercises:
      - CAP-PFM-SEC-001
      - CAP-PFM-SEC-002
    preconditions: A customer has one active workspace.
    steps: 'Try deleting the customer, then read the workspace and customer.'
    expected: Deletion is refused and both records retain their identities.
    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/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
        still has tenants as 409
      - >-
        services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#lists no tenant of another
        customer, which is what the reach reduction means
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer
          that still has tenants as 409
        - >-
          services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#lists no tenant of another
          customer, which is what the reach reduction means
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer
          that still has tenants as 409
        - >-
          services-platform:modules/tenants/tests/e2e/tenants.e2e.spec.js#lists no tenant of another
          customer, which is what the reach reduction means
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-001-13
    title: Establish and administer a customer with prepare a workspace for customer use
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-02
      - BR-CAP-PFM-SEC-003-03
    exercises:
      - CAP-PFM-SEC-001
      - CAP-PFM-SEC-003
    preconditions: A chosen customer and workspace identifier are unused.
    steps: >-
      Submit setup with a new customer, replay it, then try another submission with the same
      workspace identifier.
    expected: >-
      Replay returns the original customer, workspace and job; the competing submission creates
      none.
    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/customers/services/persistence/customers.ts:requireCustomerId'
      - >-
        services-platform:modules/tenants/tests/integration/services/provisioning/provision-tenant.integration.spec.js#answers
        the same tenant and the same job when the same submission is sent twice
    action_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/customers/services/persistence/customers.ts:requireCustomerId'
        - >-
          services-platform:modules/tenants/tests/integration/services/provisioning/provision-tenant.integration.spec.js#answers
          the same tenant and the same job when the same submission is sent twice
    assertion_evidence:
      provenance: proposed
      source:
        - 'services-platform:modules/customers/services/persistence/customers.ts:requireCustomerId'
        - >-
          services-platform:modules/tenants/tests/integration/services/provisioning/provision-tenant.integration.spec.js#answers
          the same tenant and the same job when the same submission is sent twice
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
  - id: ATS-PFM-SEC-001-14
    title: Establish and administer a customer with control staff access to customer workspaces
    category: interaction
    level: api
    proves:
      - BR-CAP-PFM-SEC-001-12
      - BR-CAP-PFM-SEC-017-08
    exercises:
      - CAP-PFM-SEC-001
      - CAP-PFM-SEC-017
    preconditions: A customer without workspaces has a standing staff grant.
    steps: Delete the customer and inspect retained staff-access records.
    expected: >-
      The customer and its standing grant are removed together; no usable assignment reaches the
      removed customer.
    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/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
        the customer
      - >-
        services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#gives
        each Rhythm Administrator one active customer-level standing grant in every customer but
        Rhythm’s own
    action_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
          the customer
        - >-
          services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#gives
          each Rhythm Administrator one active customer-level standing grant in every customer but
          Rhythm’s own
    assertion_evidence:
      provenance: proposed
      source:
        - >-
          services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
          the customer
        - >-
          services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#gives
          each Rhythm Administrator one active customer-level standing grant in every customer but
          Rhythm’s own
    binding_scope: >-
      No executable assertion is bound. The scenario specifies proposed proof and does not establish
      implemented or passing behavior.
evidence:
  - >-
    services-platform at ad3f6a6: modules/customers (routes, schemas, mapper, persistence,
    customers.table.ts), customers e2e and integration suites; docs/hq/rhythm-hq-plan.md §D3, §D8,
    §D13, §K; docs/tenants/tenant-identity.md; aws/platform.service.ts (customers and hq.customers
    permissions).
  - >-
    react-console at baf6dae: app/(console)/hq/customers pages; e2e/mock/hq-ops.mock.spec.ts
    (customers).
  - >-
    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-058'
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-PFM-SEC-058'
  failure: 'gap: FND-PFM-SEC-058'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  edge: No scenario isolates this category. The referenced finding retains the missing design proof.
  failure: No scenario isolates this category. The referenced finding retains the missing design proof.
  rule: >-
    At least one required rule has no scenario. The rule table and finding identify the outstanding
    proof.
invariant_proofs:
  - scenario: ATS-PFM-SEC-001-02
    proves:
      - INV-DOM-PFM-SEC-001-01
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a body that names
      no id as 400 customer_id_required, nothing generating one
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-03
    proves:
      - INV-DOM-PFM-SEC-001-02
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/db/customers.table.integration.spec.js#refuses
      a type outside the closed list by customers_type_check
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-04
    proves:
      - INV-DOM-PFM-SEC-001-03
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#stores
      an address in its canonical form on create
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-06
    proves:
      - INV-DOM-PFM-SEC-001-06
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version as
      409
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-08
    proves:
      - INV-DOM-PFM-SEC-001-05
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
      a record locked against updates as IllegalOperation
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-10
    proves:
      - INV-DOM-PFM-SEC-001-04
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that
      still has tenants as 409
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
  - scenario: ATS-PFM-SEC-001-11
    proves:
      - INV-DOM-PFM-SEC-001-07
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
      the customer
    proof_scope: >-
      Only the cited assertions support this invariant mapping; a source reference does not
      establish a passing execution.
permission_proof_review:
  - key: hq.customers.create
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
  - key: hq.customers.read
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
  - key: hq.customers.update
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
  - key: hq.customers.delete
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
  - key: platform.customers.read
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
  - key: platform.customers.update
    scenario_candidates:
      - ATS-PFM-SEC-001-01
      - ATS-PFM-SEC-001-05
    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-058
    provenance: inferred
    source: >-
      specs/platform/security/planning/capabilities/CAP-PFM-SEC-001-onboard-a-customer-and-keep-its-record.md
examiner: codex
examined_at: '2026-10-23'
final_text: OpenAI
---
# Establish and administer a customer: Capability Test Specification

## Coverage

| Required rule | Scenario or finding |
|---|---|
| `BR-CAP-PFM-SEC-001-01` | `ATS-PFM-SEC-001-01` |
| `BR-CAP-PFM-SEC-001-02` | `ATS-PFM-SEC-001-02`, `ATS-PFM-SEC-001-13` |
| `BR-CAP-PFM-SEC-001-03` | `ATS-PFM-SEC-001-03` |
| `BR-CAP-PFM-SEC-001-04` | `ATS-PFM-SEC-001-04` |
| `BR-CAP-PFM-SEC-001-05` | `ATS-PFM-SEC-001-05` |
| `BR-CAP-PFM-SEC-001-06` | `ATS-PFM-SEC-001-06` |
| `BR-CAP-PFM-SEC-001-07` | `ATS-PFM-SEC-001-07` |
| `BR-CAP-PFM-SEC-001-08` | `ATS-PFM-SEC-001-08` |
| `BR-CAP-PFM-SEC-001-10` | `ATS-PFM-SEC-001-09` |
| `BR-CAP-PFM-SEC-001-11` | `ATS-PFM-SEC-001-10`, `ATS-PFM-SEC-001-12` |
| `BR-CAP-PFM-SEC-001-12` | `ATS-PFM-SEC-001-11`, `ATS-PFM-SEC-001-14` |
| `BR-CAP-PFM-SEC-001-13` | no scenario: `FND-PFM-SEC-58` |

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.
