---
id: ATS-MBR-STR-012
scope: CAP-MBR-STR-012
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-012-01
    title: Invalid product identities leave no package
    level: api
    proves:
      - BR-CAP-MBR-STR-012-01
      - AS-JNY-MBR-STR-004-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Attempt package creation without identity, with an absent product and with the wrong product type.'
    expected: Each attempt returns its validation problem and creates no extension.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package create that names no product id: there is no name to make one from
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package whose product Accounting does not have, 400
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package on a product of another type, 400
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0099.acceptance.ts#MBR-ACC-0099: A
        package is created as the extension of a product Accounting already holds: no id, an unknown
        product, a product of another type and the product's own fields are each refused, and the created
        package answers the product's name, status, visibility and audiences
      - TST-MBR-STR-096
      - TST-MBR-STR-039
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
      API › refuses a package create that names no product id: there is no name to make one from
    category: validation
  - id: ATS-MBR-STR-012-02
    title: Shared product fields remain Accounting-owned
    level: api
    proves:
      - BR-CAP-MBR-STR-012-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Submit a package name and separately read an existing package.
    expected: 'The write rejects the shared field while the read returns product name, status and visibility.'
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses the common fields a package no longer carries of its own (they are the product’s)
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › extends a product of type membership_package and answers the product’s name, status and
        visibility as the package’s
      - TST-MBR-STR-096
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
      API › refuses the common fields a package no longer carries of its own (they are the product’s)
    category: boundary
  - id: ATS-MBR-STR-012-03
    title: Product retirement affects package availability
    level: api
    proves:
      - BR-CAP-MBR-STR-012-03
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Make the product inactive, inspect the active package list and attempt a staff renewal.'
    expected: The extension no longer appears active and the renewal fails.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › lists packages with the product’s fields and filters is_active by the product’s status
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a renewal term on a package whose product Accounting deactivated
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0091.acceptance.ts#MBR-ACC-0091: A
        package follows its Accounting product: when Accounting deactivates, retires or deletes the
        product the package answers that status, sells no term and writes nothing in Membership
      - TST-MBR-STR-096
      - TST-MBR-STR-031
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
      API › lists packages with the product’s fields and filters is_active by the product’s status
    category: transition
  - id: ATS-MBR-STR-012-04
    title: A product cannot receive a second package extension
    level: api
    proves:
      - BR-CAP-MBR-STR-012-04
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Repeat creation under an already extended product identity.
    expected: The existing extension remains and duplicate creation fails.
    negative: true
    data_needs: The FAA example records or equivalent records prepared within the isolated workspace.
    bound_to:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a second extension of the same product: the package id is the product id
      - TST-MBR-STR-096
    provenance: test
    source: >-
      services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
      API › refuses a second extension of the same product: the package id is the product id
    category: invariant
  - id: ATS-MBR-STR-012-05
    title: Package classification matches its program
    level: api
    proves:
      - BR-CAP-MBR-STR-012-05
      - INV-DOM-MBR-STR-004-01
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Submit one program with another program’s classification.
    expected: The classification field is named in the refusal and no package is retained.
    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 package of a type of another program 400 naming membership_type_id, and
        writes no package nor unit rule
      - 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 package of a type of another program 400 naming membership_type_id, and writes
      no package nor unit rule
    category: validation
  - id: ATS-MBR-STR-012-06
    title: Renewal targets cannot be self or cross-program
    level: api
    proves:
      - BR-CAP-MBR-STR-012-06
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Compare renewal targets pointing to the package itself and to a different program’s package.
    expected: Both cases report an invalid renewal target.
    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 package renewing into itself 400 naming renews_into_package_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 another program’s package 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 package renewing into itself 400 naming renews_into_package_id
    category: validation
  - id: ATS-MBR-STR-012-07
    title: Policy-set references require an existing set
    level: api
    proves:
      - BR-CAP-MBR-STR-012-08
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: Create an otherwise valid package with an absent policy set.
    expected: The response names the policy-set field and rejects creation.
    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 change policy set that does not exist 400 naming change_policy_set_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 change policy set that does not exist 400 naming change_policy_set_id
    category: validation
  - id: ATS-MBR-STR-012-08
    title: Removing an unused extension preserves its product
    category: transition
    level: api
    proves:
      - BR-CAP-MBR-STR-012-09
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Create an unused package extension and remove it at the version just read; inspect the underlying
      product.
    expected: The package is absent and the product retains its identity and shared fields.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - id: ATS-MBR-STR-012-09
    title: Package presentation has a zero display position
    category: edge
    level: api
    proves:
      - BR-CAP-MBR-STR-012-10
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Read extensions whose underlying products have different labels and inspect their returned display
      positions.
    expected: Both extension answers carry zero display position.
    negative: true
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - 'services-membership:modules/structure/ports/accounting-products.ts:accountingProductsFromTables'
  - id: ATS-MBR-STR-012-10
    title: An empty filtered package page still continues
    category: boundary
    level: api
    proves:
      - BR-CAP-MBR-STR-012-11
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Prepare three package references with inactive products on the first page and an active product on
      a later page; follow the active-only cursor.
    expected: The empty first answer supplies a cursor; continuation eventually reaches the active example.
    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-012-11
    title: Invalid grants roll back the package aggregate
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-012-05
      - BR-CAP-MBR-STR-013-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: Create a package with a valid primary program and a grant paired with another program’s type.
    expected: The refusal names the grant classification and leaves no package or child 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: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 grant whose type is not of the granted program 400 naming grants:
        membership_type_id, and writes no package
    exercises:
      - CAP-MBR-STR-012
      - CAP-MBR-STR-013
  - id: ATS-MBR-STR-012-12
    title: Package references protect stored change policies
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-012-08
      - BR-CAP-MBR-STR-014-01
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Save a policy set, reference it from a package and try deleting the policy set at its current
      version.
    expected: The package remains readable and the referenced policy set cannot be removed.
    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 change policy set that does not exist 400 naming change_policy_set_id
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
    exercises:
      - CAP-MBR-STR-012
      - CAP-MBR-STR-014
  - id: ATS-MBR-STR-012-13
    title: A partial offering edit validates the complete term rule
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-012-01
      - BR-CAP-MBR-STR-015-02
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Save an annual anniversary package, change only its basis to calendar without the required month
      and then submit a valid combined correction.
    expected: >-
      The incomplete combined rule fails without altering the package; the valid correction saves and
      computes the sample dates.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package whose product Accounting does not have, 400
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
    exercises:
      - CAP-MBR-STR-012
      - CAP-MBR-STR-015
  - id: ATS-MBR-STR-012-14
    title: Accounting activity changes both kinds of extension reads
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-012-02
      - BR-CAP-MBR-STR-008-03
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: 'Prepare package and dues extensions, retire their underlying products and read each extension.'
    expected: >-
      Both answers reflect Accounting activity without copying product state into Membership
      configuration.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses the common fields a package no longer carries of its own (they are the product’s)
      - >-
        services-membership:modules/structure/tests/unit/accounting-products.spec.ts#the Accounting
        products port › answers a product Accounting no longer has as its id, inactive, hidden, with no
        product status
    exercises:
      - CAP-MBR-STR-012
      - CAP-MBR-STR-008
  - id: ATS-MBR-STR-012-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-012
      - CAP-MBR-STR-019
evidence:
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0091.acceptance.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0099.acceptance.ts'
  - 'services-membership:tests/e2e/database-refusals.e2e.spec.ts'
  - 'services-membership:tests/e2e/packages-as-products.e2e.spec.ts'
  - >-
    services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API
    › refuses a package whose product Accounting does not have, 400
  - >-
    services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API
    › refuses the common fields a package no longer carries of its own (they are the product’s)
  - >-
    services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API
    › lists packages with the product’s fields and filters is_active by the product’s status
  - >-
    services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API
    › refuses a second extension of the same product: the package id is the product 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 of a type of another program 400 naming membership_type_id, and writes no
    package nor unit rule
  - >-
    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
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0115.acceptance.ts#MBR-ACC-0115: The
    renewal window and the reinstatement period decide what a member may buy, read live from the package:
    narrowing the window shuts an open renewal, widening the reinstatement period turns a rejoin back
    into a reinstatement, a blank period means no reinstatement at all, and the default renewal package
    is the one the configuration names
  - >-
    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 change policy set that does not exist 400 naming change_policy_set_id
  - >-
    services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - 'services-membership:modules/structure/ports/accounting-products.ts:accountingProductsFromTables'
  - >-
    services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
  - >-
    services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the API
    › refuses a package create that names no product id: there is no name to make one from
  - >-
    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 grant whose type is not of the granted program 400 naming grants:
    membership_type_id, and writes no package
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
  - >-
    services-membership:modules/structure/tests/unit/accounting-products.spec.ts#the Accounting products
    port › answers a product Accounting no longer has as its id, inactive, hidden, with no product status
  - 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: 'gap: FND-MBR-STR-065'
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-MBR-STR-065'
  boundary: covered
  concurrency: 'gap: FND-MBR-STR-065'
  invariant: covered
  interaction: covered
coverage_notes:
  rule: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
  failure: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
  concurrency: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
unproved_rules: []
coverage_obligations:
  permissions:
    - key: membership.configuration.read
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-012-15
      coverage: partial
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
      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-012-15
      coverage: partial
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
      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-012-15
      coverage: partial
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
      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-065
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-065
        - from: inactive
          to: active
          trigger: Staff save activity true.
          provenance: unknown
          source: FND-MBR-STR-065
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-065
        - from: active
          to: deleted
          trigger: Staff delete an active record.
          provenance: unknown
          source: FND-MBR-STR-065
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-065
        - from: inactive
          to: deleted
          trigger: Staff delete an inactive record.
          provenance: unknown
          source: FND-MBR-STR-065
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-065
      gap: FND-MBR-STR-065
  related:
    - capability: CAP-MBR-STR-013
      scenario: ATS-MBR-STR-012-11
      provenance: proposed
    - capability: CAP-MBR-STR-014
      scenario: ATS-MBR-STR-012-12
      provenance: proposed
    - capability: CAP-MBR-STR-015
      scenario: ATS-MBR-STR-012-13
      provenance: proposed
  invariants:
    - id: INV-DOM-MBR-STR-004-01
      scenario_ids:
        - ATS-MBR-STR-012-05
      coverage: specified
      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 package of a type of another program 400 naming membership_type_id,
          and writes no package nor unit rule
    - id: INV-DOM-MBR-STR-004-02
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
    - id: INV-DOM-MBR-STR-004-03
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
    - id: INV-DOM-MBR-STR-004-04
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
    - id: INV-DOM-MBR-STR-004-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
    - id: INV-DOM-MBR-STR-004-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
    - id: INV-DOM-MBR-STR-004-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
  workflows:
    - id: WF-MBR-STR-004
      scenarios:
        - ATS-MBR-STR-012-11
        - ATS-MBR-STR-012-12
        - ATS-MBR-STR-012-13
        - ATS-MBR-STR-012-14
      unproved_failure_and_concurrency: FND-MBR-STR-065
      provenance: unknown
      source: FND-MBR-STR-065
  workflow_interactions:
    - workflow: WF-MBR-STR-004
      capability: CAP-MBR-STR-013
      scenario_ids:
        - ATS-MBR-STR-012-11
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-012-11
      note: Cross-capability examples are cited from their owning specifications.
    - workflow: WF-MBR-STR-004
      capability: CAP-MBR-STR-008
      scenario_ids:
        - ATS-MBR-STR-012-14
      coverage: specified
      gap: null
      provenance: documented
      source:
        - ATS-MBR-STR-012-14
      note: Cross-capability examples are cited from their owning specifications.
rule_coverage:
  - rule: BR-CAP-MBR-STR-012-01
    scenarios:
      - ATS-MBR-STR-012-01
      - ATS-MBR-STR-012-13
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package create that names no product id: there is no name to make one from
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a package whose product Accounting does not have, 400
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
  - rule: BR-CAP-MBR-STR-012-02
    scenarios:
      - ATS-MBR-STR-012-02
      - ATS-MBR-STR-012-14
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses the common fields a package no longer carries of its own (they are the product’s)
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses the common fields a package no longer carries of its own (they are the product’s)
      - >-
        services-membership:modules/structure/tests/unit/accounting-products.spec.ts#the Accounting
        products port › answers a product Accounting no longer has as its id, inactive, hidden, with no
        product status
  - rule: BR-CAP-MBR-STR-012-03
    scenarios:
      - ATS-MBR-STR-012-03
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › lists packages with the product’s fields and filters is_active by the product’s status
  - rule: BR-CAP-MBR-STR-012-04
    scenarios:
      - ATS-MBR-STR-012-04
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/e2e/packages-as-products.e2e.spec.ts#packages as products, through the
        API › refuses a second extension of the same product: the package id is the product id
  - rule: BR-CAP-MBR-STR-012-05
    scenarios:
      - ATS-MBR-STR-012-05
      - ATS-MBR-STR-012-11
    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 package of a type of another program 400 naming membership_type_id, and
        writes no package nor unit rule
      - >-
        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: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 grant whose type is not of the granted program 400 naming grants:
        membership_type_id, and writes no package
  - rule: BR-CAP-MBR-STR-012-06
    scenarios:
      - ATS-MBR-STR-012-06
    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 package renewing into itself 400 naming renews_into_package_id
  - rule: BR-CAP-MBR-STR-012-08
    scenarios:
      - ATS-MBR-STR-012-07
      - ATS-MBR-STR-012-12
    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 change policy set that does not exist 400 naming change_policy_set_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 change policy set that does not exist 400 naming change_policy_set_id
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:policySetResource
  - rule: BR-CAP-MBR-STR-012-09
    scenarios:
      - ATS-MBR-STR-012-08
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/configuration/services/persistence/configuration-store.ts:configurationStore
  - rule: BR-CAP-MBR-STR-012-10
    scenarios:
      - ATS-MBR-STR-012-09
    gap: null
    provenance: documented
    source:
      - 'services-membership:modules/structure/ports/accounting-products.ts:accountingProductsFromTables'
  - rule: BR-CAP-MBR-STR-012-11
    scenarios:
      - ATS-MBR-STR-012-10
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/configuration/services/persistence/membership-packages.ts:listMembershipPackages
narrative_sources:
  - section: Coverage
    provenance: documented
    source:
      - CAP-MBR-STR-012
      - FND-MBR-STR-065
  - section: Interactions With Related Capabilities
    provenance: proposed
    source:
      - ATS-MBR-STR-012-11
      - ATS-MBR-STR-012-12
      - ATS-MBR-STR-012-13
      - ATS-MBR-STR-012-14
---

# Offer membership through an Accounting product: 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-012-01` | `ATS-MBR-STR-012-01`, `ATS-MBR-STR-012-13` |
| `BR-CAP-MBR-STR-012-02` | `ATS-MBR-STR-012-02`, `ATS-MBR-STR-012-14` |
| `BR-CAP-MBR-STR-012-03` | `ATS-MBR-STR-012-03` |
| `BR-CAP-MBR-STR-012-04` | `ATS-MBR-STR-012-04` |
| `BR-CAP-MBR-STR-012-05` | `ATS-MBR-STR-012-05`, `ATS-MBR-STR-012-11` |
| `BR-CAP-MBR-STR-012-06` | `ATS-MBR-STR-012-06` |
| `BR-CAP-MBR-STR-012-08` | `ATS-MBR-STR-012-07`, `ATS-MBR-STR-012-12` |
| `BR-CAP-MBR-STR-012-09` | `ATS-MBR-STR-012-08` |
| `BR-CAP-MBR-STR-012-10` | `ATS-MBR-STR-012-09` |
| `BR-CAP-MBR-STR-012-11` | `ATS-MBR-STR-012-10` |

## Interactions With Related Capabilities

Invalid grants roll back the package aggregate. Create a package with a valid primary program and a grant paired with another program’s type.

Package references protect stored change policies. Save a policy set, reference it from a package and try deleting the policy set at its current version.

A partial offering edit validates the complete term rule. Save an annual anniversary package, change only its basis to calendar without the required month and then submit a valid combined correction.

Accounting activity changes both kinds of extension reads. Prepare package and dues extensions, retire their underlying products and read each extension.
