---
id: ATS-MBR-MEM-002
scope: CAP-MBR-MEM-002
product_area: membership
module: memberships
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-MBR-MEM-002-01
    title: Refuse direct additions to organizational child memberships
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-01
    preconditions: A corporate principal membership has flow-down and assigned children.
    steps:
      - do: >-
          Attempt to add a staff term directly to each organizational child
          membership.
        expect: >-
          Each request is rejected as a direct child write, and no additional
          term is saved.
        provenance: proposed
        source: >-
          services-membership:modules/memberships/services/operations/staff-operations.ts:refuseChild
    expected: >-
      Each request is rejected as a direct child write, and no additional term
      is saved.
    negative: true
    data_needs: >-
      Corporate, flow-down and assigned membership fixtures with their source
      parent.
    bound_to: []
    provenance: proposed
    source: >-
      services-membership:modules/memberships/services/operations/staff-operations.ts:refuseChild
    category: validation
  - id: ATS-MBR-MEM-002-02
    title: Compute the next period when dates are omitted
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
    preconditions: >-
      An existing membership has a dated effective term and a package that
      continues from its end.
    steps:
      - do: >-
          Add another term under the continuing package without supplying start
          or end dates.
        expect: >-
          The new period begins immediately after the earlier end and follows
          the package’s duration.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
            a renewal continuing the latest term when no dates are given
          - >-
            services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#commits
            the Alumni term for the next term, continuing from the Student
            term’s end
    expected: >-
      The new period begins immediately after the earlier end and follows the
      package’s duration.
    negative: false
    data_needs: An annual continuing package and a membership with a known term end.
    bound_to:
      - TST-MBR-MEM-028
      - TST-MBR-MEM-246
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
      a renewal continuing the latest term when no dates are given
    category: rule
    binding_evidence:
      - suite: TST-MBR-MEM-028
        source: >-
          services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
          a renewal continuing the latest term when no dates are given
      - suite: TST-MBR-MEM-246
        source: >-
          services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#commits
          the Alumni term for the next term, continuing from the Student term’s
          end
  - id: ATS-MBR-MEM-002-03
    title: Classify additions by their actual gaps
    level: unit
    proves:
      - BR-CAP-MBR-MEM-002-03
    preconditions: >-
      Classification inputs include a first term, a contiguous term, a gap
      inside the window, a gap outside it and a package without a window.
    steps:
      - do: Classify each supplied period relative to its predecessor.
        expect: >-
          The results are enrolment, renewal, reinstatement, rejoin and rejoin,
          in that order.
        provenance: test
        source:
          - >-
            services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
            an enrolment for the first effective term
          - >-
            services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
            a renewal for a term starting the day after the latest ended
          - >-
            services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
            a reinstatement for a term starting within the reinstatement window
          - >-
            services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
            a rejoin for a term starting after the reinstatement window
            (scenario 4: 437 days)
          - >-
            services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
            a rejoin for any gap when the package has no reinstatement window
          - >-
            services-membership:tests/acceptance/memberships/MBR-ACC-0169.acceptance.ts#MBR-ACC-0169:
            A dropped member who comes back is continued inside the
            reinstatement window and started afresh outside it, and the event
            log says renewed, reinstated or rejoined by the gap
    expected: >-
      The results are enrolment, renewal, reinstatement, rejoin and rejoin, in
      that order.
    negative: false
    data_needs: Local dates and reinstatement settings for each classification boundary.
    bound_to:
      - TST-MBR-MEM-036
      - TST-MBR-MEM-188
    provenance: test
    source: >-
      services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
      an enrolment for the first effective term
    category: transition
    binding_evidence:
      - suite: TST-MBR-MEM-036
        source: >-
          services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
          an enrolment for the first effective term
      - suite: TST-MBR-MEM-036
        source: >-
          services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
          a renewal for a term starting the day after the latest ended
      - suite: TST-MBR-MEM-036
        source: >-
          services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
          a reinstatement for a term starting within the reinstatement window
      - suite: TST-MBR-MEM-036
        source: >-
          services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
          a rejoin for a term starting after the reinstatement window (scenario
          4: 437 days)
      - suite: TST-MBR-MEM-036
        source: >-
          services-membership:modules/memberships/tests/unit/term-events.spec.ts#is
          a rejoin for any gap when the package has no reinstatement window
      - suite: TST-MBR-MEM-188
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0169.acceptance.ts#MBR-ACC-0169:
          A dropped member who comes back is continued inside the reinstatement
          window and started afresh outside it, and the event log says renewed,
          reinstated or rejoined by the gap
  - id: ATS-MBR-MEM-002-04
    title: Retain original joining history across a long interruption
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-04
    preconditions: >-
      Sam has an expired, dropped term and a program with a 60-day continuity
      allowance.
    steps:
      - do: >-
          Record his return after a 437-day gap and inspect the resulting
          membership.
        expect: >-
          The original join date remains recorded and continuity contains two
          episodes.
        provenance: test
        source:
          - >-
            services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#keeps
            the original join date and restarts continuity at the rejoin (a gap
            of 437 days is more than 60)
          - >-
            services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#answers
            two continuity episodes
    expected: >-
      The original join date remains recorded and continuity contains two
      episodes.
    negative: false
    data_needs: 'Sam’s original period, drop reason and returning period.'
    bound_to:
      - TST-MBR-MEM-247
    provenance: test
    source: >-
      services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#keeps the
      original join date and restarts continuity at the rejoin (a gap of 437
      days is more than 60)
    category: invariant
    binding_evidence:
      - suite: TST-MBR-MEM-247
        source: >-
          services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#keeps
          the original join date and restarts continuity at the rejoin (a gap of
          437 days is more than 60)
      - suite: TST-MBR-MEM-247
        source: >-
          services-membership:tests/e2e/s04-lapse-and-rejoin.e2e.spec.ts#answers
          two continuity episodes
  - id: ATS-MBR-MEM-002-05
    title: Keep the earlier type until a future renewal begins
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-05
    preconditions: >-
      Maria has a Student period followed by an Alumni renewal beginning on 1
      July 2027.
    steps:
      - do: >-
          Inspect the original term and read membership on 30 June and 1 July
          2027.
        expect: >-
          The original Student term is unchanged; the date-specific types are
          Student and Alumni respectively.
        provenance: test
        source:
          - >-
            services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#leaves
            the Student term untouched and current today, with the renewal
            recorded as renewed
          - >-
            services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#answers
            Student as of 30 June 2027
          - >-
            services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#answers
            Alumni as of 1 July 2027
    expected: >-
      The original Student term is unchanged; the date-specific types are
      Student and Alumni respectively.
    negative: false
    data_needs: Student and Alumni types and their annual packages.
    bound_to:
      - TST-MBR-MEM-246
    provenance: test
    source: >-
      services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#leaves
      the Student term untouched and current today, with the renewal recorded as
      renewed
    category: interaction
    binding_evidence:
      - suite: TST-MBR-MEM-246
        source: >-
          services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#leaves
          the Student term untouched and current today, with the renewal
          recorded as renewed
      - suite: TST-MBR-MEM-246
        source: >-
          services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#answers
          Student as of 30 June 2027
      - suite: TST-MBR-MEM-246
        source: >-
          services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#answers
          Alumni as of 1 July 2027
  - id: ATS-MBR-MEM-002-06
    title: Reject a caller without authority to add terms
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-08
    preconditions: The caller can read memberships but lacks permission to create terms.
    steps:
      - do: Submit a further-term request using the restricted caller.
        expect: The API returns a forbidden response.
        provenance: test
        source:
          - >-
            services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#refuses
            a term from a caller without membership.terms.create
    expected: The API returns a forbidden response.
    negative: true
    data_needs: A readable membership and a caller without term-creation authority.
    bound_to:
      - TST-MBR-MEM-246
    provenance: test
    source: >-
      services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#refuses
      a term from a caller without membership.terms.create
    category: permission
    binding_evidence:
      - suite: TST-MBR-MEM-246
        source: >-
          services-membership:tests/e2e/s01-early-renewal-type-change.e2e.spec.ts#refuses
          a term from a caller without membership.terms.create
  - id: ATS-MBR-MEM-002-07
    title: Renew the source and its package grant together
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
      - BR-CAP-MBR-MEM-006-02
    interacts_with:
      - CAP-MBR-MEM-006
    workflows:
      - WF-MBR-MEM-002
    relies_on:
      - ATS-MBR-MEM-002-02
      - ATS-MBR-MEM-006-02
    preconditions: A person has a source term and its mirrored grant.
    steps:
      - do: Add an undated source renewal and inspect the granted holding.
        expect: >-
          The renewed source has a source-linked granted period calculated for
          that renewal.
        provenance: proposed
        source: &ref_0
          - >-
            services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
            a renewal continuing the latest term when no dates are given
          - >-
            services-membership:modules/memberships/tests/unit/grant-plan.spec.ts#plans
            a granted term with the primary term’s dates for a grant same as
            primary
    expected: >-
      The renewed source has a source-linked granted period calculated for that
      renewal.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_0
  - id: ATS-MBR-MEM-002-08
    title: Carry ending chapters into an undated renewal
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
      - BR-CAP-MBR-MEM-007-07
    interacts_with:
      - CAP-MBR-MEM-007
    workflows:
      - WF-MBR-MEM-002
    relies_on:
      - ATS-MBR-MEM-002-02
      - ATS-MBR-MEM-007-07
    preconditions: A principal holding has a current term with permitted chapters.
    steps:
      - do: >-
          Renew without a chapter list, then try a separate renewal with an
          empty list under a required minimum.
        expect: >-
          Omission creates inherited chapter rows on the new term; the
          empty-list attempt leaves no additional term.
        provenance: proposed
        source: &ref_1
          - >-
            services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
            a renewal continuing the latest term when no dates are given
          - >-
            services-membership:modules/memberships/tests/integration/services/affiliations.integration.spec.ts#writes
            a staff term’s chapters from its first day, the first of the type
            the primary
    expected: >-
      Omission creates inherited chapter rows on the new term; the empty-list
      attempt leaves no additional term.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_1
  - id: ATS-MBR-MEM-002-09
    title: Let a pending purchase constrain a staff addition
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
      - BR-CAP-MBR-MEM-009-05
    interacts_with:
      - CAP-MBR-MEM-009
    workflows: []
    relies_on:
      - ATS-MBR-MEM-002-02
      - ATS-MBR-MEM-009-05
    preconditions: A member has a current term and a pending renewal reservation.
    steps:
      - do: >-
          Attempt a staff period overlapping the reserved dates, release the
          purchase reservation, and repeat the addition.
        expect: >-
          The reserved attempt fails overlap; the released dates become
          available for the staff term.
        provenance: proposed
        source: &ref_2
          - >-
            services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
            a renewal continuing the latest term when no dates are given
          - >-
            services-membership:modules/commerce-participant/tests/unit/eligibility.spec.ts#refuses
            a suspended membership
    expected: >-
      The reserved attempt fails overlap; the released dates become available
      for the staff term.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_2
  - id: ATS-MBR-MEM-002-10
    title: Resolve expiry attention through staff renewal
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
      - BR-CAP-MBR-MEM-014-07
    interacts_with:
      - CAP-MBR-MEM-014
    workflows:
      - WF-MBR-MEM-002
    relies_on:
      - ATS-MBR-MEM-002-02
      - ATS-MBR-MEM-014-07
    preconditions: Publication is enabled and an expiring member has an open item.
    steps:
      - do: >-
          Add a staff renewal and inspect the term and the attention item in the
          committed result.
        expect: The new term exists and the original item is resolved as renewed.
        provenance: proposed
        source: &ref_3
          - >-
            services-membership:modules/memberships/tests/integration/services/staff-operations.integration.spec.ts#adds
            a renewal continuing the latest term when no dates are given
          - >-
            services-membership:modules/memberships/tests/integration/services/expiring-memberships.integration.spec.ts#resolves
            the item renewed in the staff renewal’s transaction
    expected: The new term exists and the original item is resolved as renewed.
    negative: false
    data_needs: >-
      An isolated workspace with the specified membership, package, chapter and
      partner-port fixtures.
    bound_to: []
    provenance: proposed
    source: *ref_3
  - id: ATS-MBR-MEM-002-11
    title: Require the grant for adding staff coverage
    category: permission
    level: api
    proves:
      - BR-CAP-MBR-MEM-002-02
    permissions:
      - membership.terms.create
    preconditions: A principal membership has a current annual term.
    steps:
      - do: >-
          Add an undated renewal as staff, repeat as the read-only colleague,
          then read the term count.
        expect: >-
          Staff adds one term; the colleague receives a missing-permission
          refusal and the holding has exactly two terms.
        provenance: test
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0172.acceptance.ts
    expected: >-
      Staff adds one term; the colleague receives a missing-permission refusal
      and the holding has exactly two terms.
    negative: true
    data_needs: >-
      Suite-owned workspaces, annual membership fixtures, signed staff and
      restricted contexts.
    bound_to:
      - TST-MBR-MEM-191
    binding_evidence:
      - suite: TST-MBR-MEM-191
        source: >-
          services-membership:tests/acceptance/memberships/MBR-ACC-0172.acceptance.ts
    provenance: test
    source: >-
      services-membership:tests/acceptance/memberships/MBR-ACC-0172.acceptance.ts
evidence:
  - >-
    services-membership at 5994c270e8a37a30f848e99ba5b5ee6b87c08893: reviewed
    staff operations, term-event classification, staff integration tests,
    early-renewal and lapse/rejoin API suites, and the reinstatement acceptance
    suite.
  - >-
    rhythm-v2-docs:specs/membership/memberships/design/: domain objects, state
    models and workflows reviewed for coverage.
coverage:
  rule: 'gap: FND-MBR-MEM-084'
  transition: 'gap: FND-MBR-MEM-084'
  validation: covered
  permission: covered
  edge: 'gap: FND-MBR-MEM-084'
  failure: 'gap: FND-MBR-MEM-084'
  boundary: 'gap: FND-MBR-MEM-084'
  concurrency: 'gap: FND-MBR-MEM-084'
  invariant: 'gap: FND-MBR-MEM-084'
  interaction: covered
coverage_notes:
  transition: >-
    The scenarios do not enumerate every applicable object transition; the
    finding preserves the remaining transition matrix.
  permission: >-
    The signed API acceptance case asserts allowed staff work and
    missing-permission refusals for every named key.
  invariant: >-
    Existing invariant examples do not cover every applicable domain-object
    constraint.
  interaction: >-
    Every related capability and each cross-capability workflow has an explicit
    composite scenario. Unbound composites remain proposed.
---

# Extend an existing membership through a staff term: Capability Test Specification

## Coverage

| Rule | Scenario or Finding |
|---|---|
| `BR-CAP-MBR-MEM-002-01` | `ATS-MBR-MEM-002-01` |
| `BR-CAP-MBR-MEM-002-02` | `ATS-MBR-MEM-002-02`, `ATS-MBR-MEM-002-07`, `ATS-MBR-MEM-002-08`, `ATS-MBR-MEM-002-09`, `ATS-MBR-MEM-002-10`, `ATS-MBR-MEM-002-11` |
| `BR-CAP-MBR-MEM-002-03` | `ATS-MBR-MEM-002-03` |
| `BR-CAP-MBR-MEM-002-04` | `ATS-MBR-MEM-002-04` |
| `BR-CAP-MBR-MEM-002-05` | `ATS-MBR-MEM-002-05` |
| `BR-CAP-MBR-MEM-002-06` | no scenario: `FND-MBR-MEM-008` |
| `BR-CAP-MBR-MEM-002-07` | no scenario: `FND-MBR-MEM-009` |
| `BR-CAP-MBR-MEM-002-08` | `ATS-MBR-MEM-002-06` |

## Interactions With Related Capabilities

The interaction cases test how a shared holding, period, chapter connection or recorded change affects related work. Each case names its related capabilities and workflows in frontmatter. A composite case remains proposed until executable assertions establish every part of its expected outcome.
