---
id: CAP-PFM-SEC-001
title: Establish and administer a customer
feature: FEAT-PFM-SEC-01
product_area: platform
module: security
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/hq/rhythm-hq-plan.md#K8'
  - 'services-platform:docs/hq/rhythm-hq-plan.md#D8'
  - 'services-platform:docs/tenants/tenant-identity.md#The rules (Andrew, 2026-10-17)'
purpose: >-
  Staff establish the paying organization, while authorized customer administrators maintain the details
  within their own boundary.
outcomes:
  - A permanent readable reference identifies the paying organization behind its workspaces.
  - Customer administration stays within the caller’s authorized customer boundary.
  - Deletion respects the implemented workspace dependency and record safeguards.
concepts:
  - CON-PFM-SEC-001
actors:
  - ACT-PFM-004
  - ACT-PFM-005
  - ACT-PFM-001
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: hq.customers.create
    where_checked: api
    scope: global
  - key: hq.customers.read
    where_checked: api
    scope: global
  - key: hq.customers.update
    where_checked: api
    scope: global
  - key: hq.customers.delete
    where_checked: api
    scope: global
  - key: platform.customers.read
    where_checked: api
    scope: record
  - key: platform.customers.update
    where_checked: api
    scope: record
rules:
  - id: BR-CAP-PFM-SEC-001-01
    statement: >-
      Customer creation and deletion require Rhythm staff authority; customer-level administrative grants do
      not authorize either action.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    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
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D1'
  - id: BR-CAP-PFM-SEC-001-02
    statement: >-
      A supplied customer identifier is trimmed and lowercased, contains 1 to 64 permitted characters, starts
      and ends alphanumerically, has no consecutive dots, and is refused if reserved,
      generated-identifier-shaped or already assigned.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/customers/services/persistence/customers.ts:requireCustomerId'
    origin: 'services-platform:docs/tenants/tenant-identity.md#The rules (Andrew, 2026-10-17)'
    examples:
      positive: A capitalized domain-shaped reference is normalized to lowercase.
      negative: Creation without a chosen identifier is refused.
  - id: BR-CAP-PFM-SEC-001-03
    statement: 'Every customer has exactly one classification from customer, trial, demo and training.'
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    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
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D13'
  - id: BR-CAP-PFM-SEC-001-04
    statement: >-
      Customer e-mail is normalized to lowercase, a blank value becomes absent, and an invalid address is
      refused before it is stored.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
      an invalid address on create as ObjectInvalid naming the field
    origin: none
  - id: BR-CAP-PFM-SEC-001-05
    statement: >-
      Customer administrators receive only their own customer in lists, and an unauthorized customer read
      returns the same not-found result as an absent record.
    kind: permission
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      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
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D3'
  - id: BR-CAP-PFM-SEC-001-06
    statement: >-
      Customer replacement uses the version supplied by the caller and refuses a stale version without
      overwriting the current record.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: 'services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a stale version as 409'
    origin: 'services-platform:docs/standards/record-write-contracts.md#D-05'
  - id: BR-CAP-PFM-SEC-001-07
    statement: >-
      The product should clear omitted editable values during replacement; the available assertions do not
      prove that the current commercial field is cleared.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: unknown
    source: FND-PFM-SEC-032
    origin: none
  - id: BR-CAP-PFM-SEC-001-08
    statement: >-
      The persistence service refuses updates to an update-locked customer and deletion of a delete-locked
      customer.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/customers/tests/integration/services/persistence/customers.integration.spec.js#refuses
      a record locked against updates as IllegalOperation
    origin: none
  - id: BR-CAP-PFM-SEC-001-09
    statement: >-
      Rhythm HQ presents customers in name order with workspace counts, name or identifier search, a default
      page size of 25 and a maximum of 100.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#answers a page of records, each
      with its tenant count, and no cursor when it is the last
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D7'
  - id: BR-CAP-PFM-SEC-001-10
    statement: >-
      The HQ edit accepts the name, classification and two accounting-system references, preserving omitted
      fields, accepting explicit clearing of nullable fields and rejecting fields outside that edit.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      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
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D13'
  - id: BR-CAP-PFM-SEC-001-11
    statement: An existing workspace reference prevents deletion of its customer.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#refuses a customer that still has
      tenants as 409
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D8'
  - id: BR-CAP-PFM-SEC-001-12
    statement: >-
      Customer deletion removes the Rhythm Administrators’ standing grants for that customer within the
      deletion transaction.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/security/tests/integration/services/staff-access/rhythm-administrator-standing-access.integration.spec.js#deletes
      the customer
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#K1b'
  - id: BR-CAP-PFM-SEC-001-13
    statement: >-
      The product should also prevent deletion while console users or custom security definitions remain,
      require the customer name and a reason, and record the decision in security history.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: unknown
    source: FND-PFM-SEC-003
    origin: 'services-platform:docs/hq/rhythm-hq-plan.md#D8'
scenarios:
  - id: SC-CAP-PFM-SEC-001-01
    given: Staff are establishing the paying organization for a management company.
    when: >-
      An authorized Rhythm Administrator creates a customer with a readable reference and customer
      classification.
    then: >-
      HQ returns the new customer with no workspaces; its creation test does not establish the missing
      standing-access hook.
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/hq-customers.e2e.spec.js#creates a customer for a staff
      member holding hq.customers.create, answering the record
  - id: SC-CAP-PFM-SEC-001-02
    given: An existing workspace belongs to the customer.
    when: Authorized staff attempt to remove that customer.
    then: The dependency blocks removal and a subsequent read still returns the customer.
    provenance: test
    source: >-
      services-platform:modules/customers/tests/e2e/customers.e2e.spec.js#leaves a customer that still has
      tenants readable
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-PFM-SEC-002
  - CAP-PFM-SEC-003
  - CAP-PFM-SEC-017
journeys:
  - JNY-PFM-SEC-001
open_questions: []
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).'
  - 'services-platform at ad3f6a6, tables named by this capability: customers.table.ts.'
  - >-
    services-platform at ad3f6a6, operations this capability covers: createCustomer, createHqCustomer,
    listCustomers, getCustomer, updateCustomer, patchCustomer, deleteCustomer, listHqCustomers, getHqCustomer,
    patchHqCustomer.
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-003, TC-DAT-004, TC-IAM-003, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Establish and Administer a Customer

## Purpose

Customer administration establishes the paying organization behind association workspaces. Rhythm staff own onboarding and removal, while authorized customer administrators maintain their own customer’s details.

## How It Works

A Rhythm Administrator begins in Customers within Rhythm HQ. The administrator supplies the organization’s readable reference and classification, then reviews the returned record. The customer can exist before any workspace is added. Staff find it through the HQ list and open its details to inspect the workspaces beneath it. The HQ form supports the customer fields assigned to that administrative surface. Customer-level access provides a separate route for maintaining authorized details within the customer boundary. A replacement is checked against the supplied version, and intervening edits require the caller to reload or reconcile the newer record. Locks and workspace dependencies can prevent an otherwise authorized change or removal. Deleting an eligible customer also removes its standing staff-access records within the same transaction. The product should add the remaining deletion safeguards and resolve the different standing-access behavior of the two creation paths.

## Relationships

- Workspace provisioning needs an owning customer and can request its creation alongside the workspace.
- Standing staff access connects Rhythm Administrators to customer work and participates in customer deletion.
- Commercial links provide the default account relationship used during workspace preparation.
