---
id: ATS-MBR-STR-018
scope: CAP-MBR-STR-018
product_area: membership
module: structure
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-MBR-STR-018-01
    title: Readable identities are derived or validated
    level: api
    proves:
      - BR-CAP-MBR-STR-018-01
      - AS-JNY-MBR-STR-004-01
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Compare creation by name, explicit reference, whitespace-bearing reference and reserved reference.'
    expected: Allowed references are retained and invalid identities leave no record.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0081.acceptance.ts#MBR-ACC-0081:
        Readable ids are made from names, names keep their accents and length, and the longest id the
        grammar allows is accepted
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts#MBR-ACC-0082: Ids
        an administrator types are validated, never repaired: spaces, reserved words, a name that yields
        nothing, a taken id and an id changed on update are all refused with nothing written
      - TST-MBR-STR-021
      - TST-MBR-STR-022
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0081.acceptance.ts#MBR-ACC-0081:
      Readable ids are made from names, names keep their accents and length, and the longest id the
      grammar allows is accepted
    category: validation
  - id: ATS-MBR-STR-018-02
    title: Existing identity cannot be silently replaced
    level: api
    proves:
      - BR-CAP-MBR-STR-018-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Submit a different body identity and separately create a duplicate of an existing record.
    expected: Both attempts fail without altering the original record.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts#MBR-ACC-0082: Ids
        an administrator types are validated, never repaired: spaces, reserved words, a name that yields
        nothing, a taken id and an id changed on update are all refused with nothing written
      - TST-MBR-STR-022
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts#MBR-ACC-0082: Ids an
      administrator types are validated, never repaired: spaces, reserved words, a name that yields
      nothing, a taken id and an id changed on update are all refused with nothing written
    category: invariant
  - id: ATS-MBR-STR-018-03
    title: Versioned mutations reject missing and stale versions
    level: api
    proves:
      - BR-CAP-MBR-STR-018-03
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Save at the current version, then use a stale version, omit the version and attempt stale deletion.'
    expected: Only the current save succeeds and advances its version.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0083.acceptance.ts#MBR-ACC-0083: Every
        change and delete carries the version read: the version climbs by one per save, a stale save or
        delete is refused, and a missing version is refused before anything is read
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0084.acceptance.ts#MBR-ACC-0084: Two
        administrators edit the Regular package at the same time: the first save wins, the second is told
        to re-read, and the grants she re-sends get new ids
      - TST-MBR-STR-023
      - TST-MBR-STR-024
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0083.acceptance.ts#MBR-ACC-0083: Every
      change and delete carries the version read: the version climbs by one per save, a stale save or
      delete is refused, and a missing version is refused before anything is read
    category: concurrency
  - id: ATS-MBR-STR-018-04
    title: Partial updates retain omitted content
    level: api
    proves:
      - BR-CAP-MBR-STR-018-04
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: >-
      Change one key, clear a nullable key, submit blank required text and an unknown key, and omit a
      child list.
    expected: Allowed fields change or clear; invalid input fails and omitted lists remain.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0085.acceptance.ts#MBR-ACC-0085: An
        update changes only the keys it names: null clears, an empty string is null, whitespace-only text
        is refused, unknown keys are refused, and an owned list is replaced only when sent
      - TST-MBR-STR-025
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0085.acceptance.ts#MBR-ACC-0085: An
      update changes only the keys it names: null clears, an empty string is null, whitespace-only text
      is refused, unknown keys are refused, and an owned list is replaced only when sent
    category: rule
  - id: ATS-MBR-STR-018-05
    title: Dependent records prevent deletion until released
    level: api
    proves:
      - BR-CAP-MBR-STR-018-05
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Try deleting each configuration category while referenced, then remove its dependencies and retry.'
    expected: Referenced records stay and free records are removed with owned rows.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0086.acceptance.ts#MBR-ACC-0086:
        Nothing that another record points at can be deleted: program, type, package, status reason and
        policy set are each refused while in use, then deleted with their owned rows once free
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0118.acceptance.ts#MBR-ACC-0118:
        References other applications hold: a billing template that drops members names a program and a
        terminated reason and blocks their deletion, while a Committees eligibility rule naming a type is
        invisible to Membership and the type can be deleted from under it
      - TST-MBR-STR-026
      - TST-MBR-STR-055
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0086.acceptance.ts#MBR-ACC-0086: Nothing
      that another record points at can be deleted: program, type, package, status reason and policy set
      are each refused while in use, then deleted with their owned rows once free
    category: transition
  - id: ATS-MBR-STR-018-06
    title: Failed aggregates leave no partial state
    level: api
    proves:
      - BR-CAP-MBR-STR-018-06
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Create a package with invalid grants and update an existing record with an invalid child list.
    expected: The failed create leaves no package and the failed update retains the previous aggregate.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0116.acceptance.ts#MBR-ACC-0116: A
        refused save in the middle of a series keeps the valid saves before it and loses nothing after it
        is corrected: the record never holds half a body, and a create that fails on its children leaves
        no record at all
      - >-
        services-membership:tests/acceptance/structure/MBR-ACC-0035.acceptance.ts#MBR-ACC-0035 a refusal
        after valid work
      - TST-MBR-STR-053
      - TST-MBR-STR-092
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0116.acceptance.ts#MBR-ACC-0116: A
      refused save in the middle of a series keeps the valid saves before it and loses nothing after it
      is corrected: the record never holds half a body, and a create that fails on its children leaves no
      record at all
    category: failure
  - id: ATS-MBR-STR-018-07
    title: Database refusals identify relevant fields
    level: api
    proves:
      - BR-CAP-MBR-STR-018-07
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Submit a classification under a missing program and a package renewing into itself.
    expected: Responses identify the invalid program and renewal fields instead of returning an unexpected error.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses a type of a program that does not exist 400 naming program_id
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses a package renewing into itself 400 naming renews_into_package_id
      - TST-MBR-STR-095
    provenance: test
    source: >-
      services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
      through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
      refuses › refuses a type of a program that does not exist 400 naming program_id
    category: validation
  - id: ATS-MBR-STR-018-08
    title: Workspace isolation applies to every mutation
    level: api
    proves:
      - BR-CAP-MBR-STR-018-09
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Read, update and delete a foreign workspace record identity under the caller’s own context.'
    expected: All operations treat the foreign record as absent.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0089.acceptance.ts#MBR-ACC-0089: Two
        associations with identically named configuration never see each other: the same ids live in both
        workspaces and a cross-workspace read, update or delete answers not found
      - TST-MBR-STR-029
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0089.acceptance.ts#MBR-ACC-0089: Two
      associations with identically named configuration never see each other: the same ids live in both
      workspaces and a cross-workspace read, update or delete answers not found
    category: boundary
  - id: ATS-MBR-STR-018-09
    title: Configuration paging follows its own size contract
    category: boundary
    level: api
    proves:
      - BR-CAP-MBR-STR-018-08
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Create enough records for continuation, omit page size, then request an oversized page and an
      activity-filtered package page with no matching first-page rows.
    expected: >-
      The omitted size selects 50, the oversized size caps at 500 and an empty filtered page can still
      continue.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
  - id: ATS-MBR-STR-018-10
    title: Program reference errors leave no saved aggregate
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-009-02
      - BR-CAP-MBR-STR-018-06
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: Create a program with absent structural references and read its proposed identity.
    expected: The refusal identifies the missing references and no program remains.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:programResource
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-009
  - id: ATS-MBR-STR-018-11
    title: Invalid flow-down preserves prior selections
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-011-03
      - BR-CAP-MBR-STR-018-06
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: Update a valid type with enabled flow-down and an absent target alongside changed role selections.
    expected: The failed update preserves the prior type version and selected roles.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses flow-down enabled without a target type 400 naming flowdown_type_id, and writes
        no type
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-011
  - id: ATS-MBR-STR-018-12
    title: Failed package changes preserve owned lists
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-012-05
      - BR-CAP-MBR-STR-018-06
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: Change a package to a mismatched classification while submitting new unit rules.
    expected: The refusal preserves the original package version and original unit-rule rows.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses a package of a type of another program 400 naming membership_type_id, and
        writes no package nor unit rule
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-012
  - id: ATS-MBR-STR-018-13
    title: Stale policy saves preserve the accepted cases
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-014-01
      - BR-CAP-MBR-STR-018-03
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Read a policy set twice, save one replacement and submit a different list using the earlier
      version.
    expected: The stale request fails and retains the first replacement’s content and allocated case identities.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-014
  - id: ATS-MBR-STR-018-14
    title: Reason references distinguish retirement from removal
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-017-01
      - BR-CAP-MBR-STR-018-05
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: 'Prepare a reason used by a term, retire it through a versioned save and try deleting it.'
    expected: Retirement saves without rewriting the term and the retained reference prevents deletion.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:statusReasonContent
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-017
  - id: ATS-MBR-STR-018-15
    title: Workspace authority gates this capability
    category: permission
    level: api
    proves:
      - BR-CAP-MBR-STR-019-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Exercise the capability’s exposed operations with its read-only and management callers, then repeat
      with application access removed, an unlicensed workspace and an unavailable workspace.
    expected: >-
      Each named operation requires its declared authority; admission failures occur before any record or
      calculated result is exposed.
    negative: true
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/unit/route-authorization.spec.ts#route authorization: every Membership
        operation names its permission › requires membership.structure.read of every structure read and
        membership.structure.manage of every change
      - >-
        services-membership:modules/configuration/aws/api/configuration.routes.ts:configurationModuleRoutes
      - >-
        services-membership:tests/e2e/terms-api.e2e.spec.ts#the terms API, end to end in process ›
        refuses a caller who holds no key in the tenant
    exercises:
      - CAP-MBR-STR-018
      - CAP-MBR-STR-019
evidence:
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0081.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0083.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0084.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0085.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0086.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0089.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0116.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0118.acceptance.ts'
  - 'services-membership:tests/acceptance/structure/MBR-ACC-0035.acceptance.ts'
  - 'services-membership:tests/e2e/database-refusals.e2e.spec.ts'
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:updateConfiguration
  - >-
    services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - >-
    services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
    through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
    refuses › refuses a type of a program that does not exist 400 naming program_id
  - >-
    services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
  - 'services-membership:aws/api/route-inputs.ts:found'
  - 'react-console:acceptance/membership/journeys/configuration/MBR-ACC-0122.json'
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0081.acceptance.ts#MBR-ACC-0081: Readable
    ids are made from names, names keep their accents and length, and the longest id the grammar allows
    is accepted
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts#MBR-ACC-0082: Ids an
    administrator types are validated, never repaired: spaces, reserved words, a name that yields
    nothing, a taken id and an id changed on update are all refused with nothing written
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0083.acceptance.ts#MBR-ACC-0083: Every
    change and delete carries the version read: the version climbs by one per save, a stale save or
    delete is refused, and a missing version is refused before anything is read
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0085.acceptance.ts#MBR-ACC-0085: An update
    changes only the keys it names: null clears, an empty string is null, whitespace-only text is
    refused, unknown keys are refused, and an owned list is replaced only when sent
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0086.acceptance.ts#MBR-ACC-0086: Nothing
    that another record points at can be deleted: program, type, package, status reason and policy set
    are each refused while in use, then deleted with their owned rows once free
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0116.acceptance.ts#MBR-ACC-0116: A refused
    save in the middle of a series keeps the valid saves before it and loses nothing after it is
    corrected: the record never holds half a body, and a create that fails on its children leaves no
    record at all
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0089.acceptance.ts#MBR-ACC-0089: Two
    associations with identically named configuration never see each other: the same ids live in both
    workspaces and a cross-workspace read, update or delete answers not found
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:programResource
  - >-
    services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
    through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
    refuses › refuses flow-down enabled without a target type 400 naming flowdown_type_id, and writes no
    type
  - >-
    services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
    through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
    refuses › refuses a package of a type of another program 400 naming membership_type_id, and writes no
    package nor unit rule
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:statusReasonContent
  - membership.programs.read
  - membership.programs.manage
  - membership.configuration.read
  - membership.configuration.manage
  - membership.application.access
  - >-
    services-membership:tests/unit/route-authorization.spec.ts#route authorization: every Membership
    operation names its permission › requires membership.structure.read of every structure read and
    membership.structure.manage of every change
  - 'services-membership:modules/configuration/aws/api/configuration.routes.ts:configurationModuleRoutes'
  - >-
    services-membership:tests/e2e/terms-api.e2e.spec.ts#the terms API, end to end in process › refuses a
    caller who holds no key in the tenant
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-MBR-STR-071'
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  edge: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
unproved_rules: []
coverage_obligations:
  permissions:
    - key: membership.programs.read
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-018-15
      coverage: partial
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
    - key: membership.programs.manage
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-018-15
      coverage: partial
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
    - key: membership.configuration.read
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-018-15
      coverage: partial
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
    - key: membership.configuration.manage
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-018-15
      coverage: partial
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
    - key: membership.application.access
      scenario_ids:
        - ATS-MBR-STR-019-04
      local_scenario_ids:
        - ATS-MBR-STR-018-15
      coverage: partial
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
  state_models:
    - id: STATE-MBR-STR-006
      transitions:
        - from: active
          to: inactive
          trigger: Staff save activity false.
          provenance: unknown
          source: FND-MBR-STR-071
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-071
        - from: inactive
          to: active
          trigger: Staff save activity true.
          provenance: unknown
          source: FND-MBR-STR-071
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-071
        - from: active
          to: deleted
          trigger: Staff delete an active record.
          provenance: unknown
          source: FND-MBR-STR-071
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-071
        - from: inactive
          to: deleted
          trigger: Staff delete an inactive record.
          provenance: unknown
          source: FND-MBR-STR-071
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-071
      gap: FND-MBR-STR-071
  related:
    - capability: CAP-MBR-STR-009
      scenario: ATS-MBR-STR-018-10
      provenance: proposed
    - capability: CAP-MBR-STR-011
      scenario: ATS-MBR-STR-018-11
      provenance: proposed
    - capability: CAP-MBR-STR-012
      scenario: ATS-MBR-STR-018-12
      provenance: proposed
    - capability: CAP-MBR-STR-014
      scenario: ATS-MBR-STR-018-13
      provenance: proposed
    - capability: CAP-MBR-STR-017
      scenario: ATS-MBR-STR-018-14
      provenance: proposed
  invariants:
    - id: INV-DOM-MBR-STR-006-01
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-02
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-03
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-04
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
    - id: INV-DOM-MBR-STR-006-08
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
  workflows:
    - id: WF-MBR-STR-005
      scenarios:
        - ATS-MBR-STR-018-03
        - ATS-MBR-STR-018-06
        - ATS-MBR-STR-018-10
        - ATS-MBR-STR-018-11
        - ATS-MBR-STR-018-12
        - ATS-MBR-STR-018-13
        - ATS-MBR-STR-018-14
      unproved_failure_and_concurrency: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
  workflow_interactions:
    - workflow: WF-MBR-STR-005
      capability: CAP-MBR-STR-009
      scenario_ids:
        - ATS-MBR-STR-018-10
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-018-10
      note: Cross-capability examples are cited from their owning specifications.
    - workflow: WF-MBR-STR-005
      capability: CAP-MBR-STR-010
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-071
      provenance: unknown
      source: FND-MBR-STR-071
      note: >-
        The shared workflow names both capabilities, but a concrete interaction oracle is not established
        for this pairing.
    - workflow: WF-MBR-STR-005
      capability: CAP-MBR-STR-011
      scenario_ids:
        - ATS-MBR-STR-018-11
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-018-11
      note: Cross-capability examples are cited from their owning specifications.
    - workflow: WF-MBR-STR-005
      capability: CAP-MBR-STR-014
      scenario_ids:
        - ATS-MBR-STR-018-13
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-018-13
      note: Cross-capability examples are cited from their owning specifications.
    - workflow: WF-MBR-STR-005
      capability: CAP-MBR-STR-017
      scenario_ids:
        - ATS-MBR-STR-018-14
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-018-14
      note: Cross-capability examples are cited from their owning specifications.
rule_coverage:
  - rule: BR-CAP-MBR-STR-018-01
    scenarios:
      - ATS-MBR-STR-018-01
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0081.acceptance.ts#MBR-ACC-0081:
        Readable ids are made from names, names keep their accents and length, and the longest id the
        grammar allows is accepted
  - rule: BR-CAP-MBR-STR-018-02
    scenarios:
      - ATS-MBR-STR-018-02
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0082.acceptance.ts#MBR-ACC-0082: Ids
        an administrator types are validated, never repaired: spaces, reserved words, a name that yields
        nothing, a taken id and an id changed on update are all refused with nothing written
  - rule: BR-CAP-MBR-STR-018-03
    scenarios:
      - ATS-MBR-STR-018-03
      - ATS-MBR-STR-018-13
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0083.acceptance.ts#MBR-ACC-0083: Every
        change and delete carries the version read: the version climbs by one per save, a stale save or
        delete is refused, and a missing version is refused before anything is read
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - rule: BR-CAP-MBR-STR-018-04
    scenarios:
      - ATS-MBR-STR-018-04
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0085.acceptance.ts#MBR-ACC-0085: An
        update changes only the keys it names: null clears, an empty string is null, whitespace-only text
        is refused, unknown keys are refused, and an owned list is replaced only when sent
  - rule: BR-CAP-MBR-STR-018-05
    scenarios:
      - ATS-MBR-STR-018-05
      - ATS-MBR-STR-018-14
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0086.acceptance.ts#MBR-ACC-0086:
        Nothing that another record points at can be deleted: program, type, package, status reason and
        policy set are each refused while in use, then deleted with their owned rows once free
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:statusReasonContent
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - rule: BR-CAP-MBR-STR-018-06
    scenarios:
      - ATS-MBR-STR-018-06
      - ATS-MBR-STR-018-10
      - ATS-MBR-STR-018-11
      - ATS-MBR-STR-018-12
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0116.acceptance.ts#MBR-ACC-0116: A
        refused save in the middle of a series keeps the valid saves before it and loses nothing after it
        is corrected: the record never holds half a body, and a create that fails on its children leaves
        no record at all
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:programResource
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses flow-down enabled without a target type 400 naming flowdown_type_id, and writes
        no type
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses a package of a type of another program 400 naming membership_type_id, and
        writes no package nor unit rule
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
  - rule: BR-CAP-MBR-STR-018-07
    scenarios:
      - ATS-MBR-STR-018-07
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/e2e/database-refusals.e2e.spec.ts#mistakes only the database catches,
        through the API › DEF-MBR-0086: a configuration write a check, a foreign key or an integer column
        refuses › refuses a type of a program that does not exist 400 naming program_id
  - rule: BR-CAP-MBR-STR-018-08
    scenarios:
      - ATS-MBR-STR-018-09
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
  - rule: BR-CAP-MBR-STR-018-09
    scenarios:
      - ATS-MBR-STR-018-08
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0089.acceptance.ts#MBR-ACC-0089: Two
        associations with identically named configuration never see each other: the same ids live in both
        workspaces and a cross-workspace read, update or delete answers not found
narrative_sources:
  - section: Coverage
    provenance: documented
    source:
      - CAP-MBR-STR-018
      - FND-MBR-STR-071
  - section: Interactions With Related Capabilities
    provenance: proposed
    source:
      - ATS-MBR-STR-018-10
      - ATS-MBR-STR-018-11
      - ATS-MBR-STR-018-12
      - ATS-MBR-STR-018-13
      - ATS-MBR-STR-018-14
---

# Maintain configuration with consistent change safeguards: Capability Test Specification

## Coverage

The coverage map distinguishes specified examples from missing proof. A covered category means an expected case is specified; executable bindings and their run results remain separate evidence. Each required rule has a proof link or an explicit coverage finding in the structured rule ledger.

| Required Rule | Scenario or Finding |
|---|---|
| `BR-CAP-MBR-STR-018-01` | `ATS-MBR-STR-018-01` |
| `BR-CAP-MBR-STR-018-02` | `ATS-MBR-STR-018-02` |
| `BR-CAP-MBR-STR-018-03` | `ATS-MBR-STR-018-03`, `ATS-MBR-STR-018-13` |
| `BR-CAP-MBR-STR-018-04` | `ATS-MBR-STR-018-04` |
| `BR-CAP-MBR-STR-018-05` | `ATS-MBR-STR-018-05`, `ATS-MBR-STR-018-14` |
| `BR-CAP-MBR-STR-018-06` | `ATS-MBR-STR-018-06`, `ATS-MBR-STR-018-10`, `ATS-MBR-STR-018-11`, `ATS-MBR-STR-018-12` |
| `BR-CAP-MBR-STR-018-07` | `ATS-MBR-STR-018-07` |
| `BR-CAP-MBR-STR-018-08` | `ATS-MBR-STR-018-09` |
| `BR-CAP-MBR-STR-018-09` | `ATS-MBR-STR-018-08` |

## Interactions With Related Capabilities

Program reference errors leave no saved aggregate. Create a program with absent structural references and read its proposed identity.

Invalid flow-down preserves prior selections. Update a valid type with enabled flow-down and an absent target alongside changed role selections.

Failed package changes preserve owned lists. Change a package to a mismatched classification while submitting new unit rules.

Stale policy saves preserve the accepted cases. Read a policy set twice, save one replacement and submit a different list using the earlier version.

Reason references distinguish retirement from removal. Prepare a reason used by a term, retire it through a versioned save and try deleting it.
