---
id: ATS-MBR-STR-015
scope: CAP-MBR-STR-015
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-015-01
    title: Invalid date rules report their combined problems
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-01
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: 'Compute a calendar rule with month units, no start month and an anniversary-only threshold.'
    expected: The refusal identifies the incompatible and missing settings together.
    negative: true
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        reports every problem of a rule at once
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        refuses a calendar term counted in anything but years
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        refuses a rule it cannot compute, naming what is wrong
      - TST-MBR-STR-019
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
      reports every problem of a rule at once
    category: validation
  - id: ATS-MBR-STR-015-02
    title: Partial basis changes validate the resulting rule
    level: api
    proves:
      - BR-CAP-MBR-STR-015-02
    preconditions: >-
      An isolated Membership-licensed workspace contains the relevant records, with caller permissions
      selected for the case.
    steps: 'Change an existing package to calendar basis alone, then include the required calendar start.'
    expected: The incomplete result fails and the complete result saves.
    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-0101.acceptance.ts#MBR-ACC-0101: A
        package update is judged on the record as it will be after the change: switching the basis alone
        is refused, switching it with the dependent fields is accepted, and the renewal, reinstatement,
        seat and proration settings round-trip, clear and bound correctly
      - TST-MBR-STR-041
    provenance: test
    source: >-
      services-membership:tests/acceptance/configuration/MBR-ACC-0101.acceptance.ts#MBR-ACC-0101: A
      package update is judged on the record as it will be after the change: switching the basis alone is
      refused, switching it with the dependent fields is accepted, and the renewal, reinstatement, seat
      and proration settings round-trip, clear and bound correctly
    category: validation
  - id: ATS-MBR-STR-015-03
    title: Continuation and restart use different starting points
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-03
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Calculate early and late continuations and a rule with continuation disabled.
    expected: 'Continuations begin after the existing end, while the disabled case begins from the purchase basis.'
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › extends an early renewal from the current end, whatever the basis
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › back-dates a late renewal to the day after the current end
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        starts a term fresh from the basis when the rule does not continue from the end (1.0 always
        continued)
      - >-
        services-membership:modules/terms/tests/unit/term-rules.property.spec.ts#term rules: generated
        cases › starts a continuation the day after the current end
      - >-
        services-membership:modules/terms/tests/unit/define-term-engine.spec.ts#defineTermEngine ›
        computes a purchase’s period from the offering’s rule the hook reads
      - TST-MBR-STR-019
      - TST-MBR-STR-018
      - TST-MBR-STR-013
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0 beyond
      its spec cases › extends an early renewal from the current end, whatever the basis
    category: rule
  - id: ATS-MBR-STR-015-04
    title: Reinstatement changes at its last permitted day
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-04
      - AS-JNY-MBR-STR-005-02
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: >-
      Calculate a terminated period at its reinstatement boundary, the following day and after its end
      with no allowance.
    expected: The boundary continues; later and zero-allowance-after-end cases restart.
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › continues a terminated term on the last day of its window
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › restarts a terminated term the day after its window closes
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › restarts a terminated term at once when the rule has no reinstatement
        window
      - TST-MBR-STR-019
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0 beyond
      its spec cases › continues a terminated term on the last day of its window
    category: boundary
  - id: ATS-MBR-STR-015-05
    title: Anniversary examples retain the expected rounding
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-05
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: 'Calculate the April annual purchase, its threshold-day variant and the January week case.'
    expected: 'The resulting ends are April month-end, May month-end and seven days after the week start.'
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 284-292: anniversary in years, should calculate
        properly for a single year › ends a purchase of 2019-04-03 at the end of the anniversary month,
        2020-04-30
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 327-337: anniversary in years, should respect sell
        after month › moves a new term bought 2019-04-15 with the threshold 15 a month on, to 2020-05-31
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 625-634: anniversary, should calculate properly for
        a single week › ends a month-end week bought 2019-01-01 seven days on, 2019-01-08, keeping the
        full count
      - >-
        services-membership:modules/terms/tests/unit/term-rules.property.spec.ts#term rules: generated
        cases › ends a month-end rule on the last day of a month
      - TST-MBR-STR-019
      - TST-MBR-STR-018
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
      calculate-expiration-date.spec.js › 1.0 lines 284-292: anniversary in years, should calculate
      properly for a single year › ends a purchase of 2019-04-03 at the end of the anniversary month,
      2020-04-30
    category: rule
  - id: ATS-MBR-STR-015-06
    title: Fixed windows cover ordinary and wrapping ranges
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-06
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: >-
      Calculate an in-window start, a February start in a wrapping range and a start immediately after a
      non-December window closes.
    expected: 'Each uses the selected fixed end, including the boundary-adjusted case.'
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 853-866: fixed expiration dates, simple in-year
        range › ends a purchase of 2023-04-03 in the window 03-01 to 05-31 on 2023-11-14
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        finds a February purchase in the window that began the previous October: 2021-12-31 (1.0 fell
        through to the plain rule: 2022-02-28)
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: fixed term ends ›
        places a renewal starting the day after a window’s end in that window (RB-2585)
      - TST-MBR-STR-019
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
      calculate-expiration-date.spec.js › 1.0 lines 853-866: fixed expiration dates, simple in-year range
      › ends a purchase of 2023-04-03 in the window 03-01 to 05-31 on 2023-11-14
    category: boundary
  - id: ATS-MBR-STR-015-07
    title: Calendar joins and continuations do not overlap
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-07
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Compare a fresh April calendar purchase with a late continuation.
    expected: 'The fresh period can start in January, while continuation starts after its existing end.'
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 70-77: calendar, a standard year, should calculate
        properly for April › back-dates a purchase of 2019-04-03 to 2019-01-01 and ends it 2019-12-31
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        starts a late calendar renewal of a term ending 2023-04-30 on 2023-05-01 (1.0 back-dated it to
        2023-01-01, overlapping the old term)
      - TST-MBR-STR-019
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
      calculate-expiration-date.spec.js › 1.0 lines 70-77: calendar, a standard year, should calculate
      properly for April › back-dates a purchase of 2019-04-03 to 2019-01-01 and ends it 2019-12-31
    category: rule
  - id: ATS-MBR-STR-015-08
    title: Leap-year February uses leap day
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-08
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Calculate a calendar period finishing in February 2024.
    expected: Its inclusive ending is the twenty-ninth of February.
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        ends a calendar period ending in February on its last day in a leap year: 2024-02-29 (decided by
        Andrew 2026-10-18: the real last day of the month; 1.0 added a year with Moment and ended it
        2024-02-28)
      - TST-MBR-STR-019
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides › ends
      a calendar period ending in February on its last day in a leap year: 2024-02-29 (decided by Andrew
      2026-10-18: the real last day of the month; 1.0 added a year with Moment and ended it 2024-02-28)
    category: boundary
  - id: ATS-MBR-STR-015-09
    title: Repeated computations leave inputs intact
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-10
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: Calculate twice from frozen inputs and again from equivalent copies.
    expected: All results match and input values remain unchanged.
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        gives the same result twice for the same frozen inputs (1.0 turned the fixed dates into Moments
        in place, so a second call read other inputs)
      - >-
        services-membership:modules/terms/tests/unit/term-rules.property.spec.ts#term rules: generated
        cases › gives the same result for the same inputs, computed again or from copies
      - TST-MBR-STR-019
      - TST-MBR-STR-018
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
      gives the same result twice for the same frozen inputs (1.0 turned the fixed dates into Moments in
      place, so a second call read other inputs)
    category: rule
  - id: ATS-MBR-STR-015-10
    title: Import isolation detects a planted dependency
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-11
    preconditions: Explicit inputs or a recording fixture represent the cases exercised below.
    steps: >-
      Run the engine import inspection, then introduce an external module import in the isolated test
      fixture.
    expected: The ordinary engine passes and the planted dependency is reported.
    negative: false
    data_needs: 'Literal term, state or declaration fixtures for the described cases.'
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/isolation.spec.ts#term engine isolation › imports
        nothing of the repository outside modules/terms
      - >-
        services-membership:modules/terms/tests/unit/isolation.spec.ts#term engine isolation › refuses a
        planted file that imports another module, by name
      - TST-MBR-STR-015
    provenance: test
    source: >-
      services-membership:modules/terms/tests/unit/isolation.spec.ts#term engine isolation › imports
      nothing of the repository outside modules/terms
    category: invariant
  - id: ATS-MBR-STR-015-11
    title: Independent grants require a valid date rule
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-013-03
      - BR-CAP-MBR-STR-015-01
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Submit a grant using its own anniversary rule but missing length, then correct the rule and
      calculate its period beside the primary term.
    expected: The invalid aggregate saves nothing; the corrected rule yields the separately expected period.
    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:packageResource
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        reports every problem of a rule at once
    exercises:
      - CAP-MBR-STR-015
      - CAP-MBR-STR-013
  - id: ATS-MBR-STR-015-12
    title: Calculated continuation dates avoid a standing gap
    category: interaction
    level: api
    proves:
      - BR-CAP-MBR-STR-015-03
      - BR-CAP-MBR-STR-016-01
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Compute a continuation from a term ending September 30 and evaluate coverage on September 30 and
      October 1.
    expected: >-
      The old term covers the first date and the continuation covers the next date without a grace
      interval.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › extends an early renewal from the current end, whatever the basis
      - >-
        services-membership:modules/terms/tests/unit/status.spec.ts#holding status: the precedence of §2
        › puts suspended over grace
    exercises:
      - CAP-MBR-STR-015
      - CAP-MBR-STR-016
  - id: ATS-MBR-STR-015-13
    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-015
      - CAP-MBR-STR-019
  - id: ATS-MBR-STR-015-14
    title: Generated periods preserve repeatable dates
    category: invariant
    level: unit
    proves:
      - BR-CAP-MBR-STR-015-10
    preconditions: An isolated workspace contains the records and current versions needed for the case.
    steps: >-
      Calculate every generated valid rule twice using equivalent inputs and inspect the resulting period
      bounds.
    expected: Each repeated result agrees and every finite end is on or after its start.
    negative: false
    data_needs: Separate fixture records for the accepted and refused operations.
    bound_to:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.property.spec.ts#ends every term on or
        after its start, and only a term of a none rule never ends
      - >-
        services-membership:modules/terms/tests/unit/term-rules.property.spec.ts#gives the same result
        for the same inputs, computed again or from copies
      - TST-MBR-STR-018
    provenance: test
    source: 'services-membership:modules/terms/tests/unit/term-rules.property.spec.ts'
evidence:
  - 'services-membership:modules/terms/tests/unit/define-term-engine.spec.ts'
  - 'services-membership:modules/terms/tests/unit/isolation.spec.ts'
  - 'services-membership:modules/terms/tests/unit/term-rules.property.spec.ts'
  - 'services-membership:modules/terms/tests/unit/term-rules.spec.ts'
  - 'services-membership:tests/acceptance/configuration/MBR-ACC-0101.acceptance.ts'
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
    reports every problem of a rule at once
  - >-
    services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0 beyond
    its spec cases › extends an early renewal from the current end, whatever the basis
  - 'services-membership:modules/terms/services/term-rules.ts:startingPoint'
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
    calculate-expiration-date.spec.js › 1.0 lines 284-292: anniversary in years, should calculate
    properly for a single year › ends a purchase of 2019-04-03 at the end of the anniversary month,
    2020-04-30
  - 'services-membership:modules/terms/services/term-rules.ts:fixedWindowProbe'
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
    calculate-expiration-date.spec.js › 1.0 lines 70-77: calendar, a standard year, should calculate
    properly for April › back-dates a purchase of 2019-04-03 to 2019-01-01 and ends it 2019-12-31
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides › ends a
    calendar period ending in February on its last day in a leap year: 2024-02-29 (decided by Andrew
    2026-10-18: the real last day of the month; 1.0 added a year with Moment and ended it 2024-02-28)
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
    calculate-expiration-date.spec.js › 1.0 lines 248-255: no expiration, should calculate properly ›
    starts on 2019-04-03 and never ends
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides › gives
    the same result twice for the same frozen inputs (1.0 turned the fixed dates into Moments in place,
    so a second call read other inputs)
  - >-
    services-membership:modules/terms/tests/unit/isolation.spec.ts#term engine isolation › imports
    nothing of the repository outside modules/terms
  - 'services-membership:modules/terms/services/term-rules.ts:fixedTermEnd'
  - >-
    services-membership:tests/acceptance/configuration/MBR-ACC-0101.acceptance.ts#MBR-ACC-0101: A package
    update is judged on the record as it will be after the change: switching the basis alone is refused,
    switching it with the dependent fields is accepted, and the renewal, reinstatement, seat and
    proration settings round-trip, clear and bound correctly
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0 beyond
    its spec cases › continues a terminated term on the last day of its window
  - >-
    services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
    calculate-expiration-date.spec.js › 1.0 lines 853-866: fixed expiration dates, simple in-year range ›
    ends a purchase of 2023-04-03 in the window 03-01 to 05-31 on 2023-11-14
  - >-
    services-membership:modules/terms/tests/unit/status.spec.ts#holding status: the precedence of §2 ›
    puts suspended over grace
  - 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: 'gap: FND-MBR-STR-068'
  validation: covered
  permission: covered
  edge: 'gap: FND-MBR-STR-068'
  failure: 'gap: FND-MBR-STR-068'
  boundary: covered
  concurrency: 'gap: FND-MBR-STR-068'
  invariant: covered
  interaction: covered
coverage_notes:
  transition: >-
    No dedicated scenario establishes this category across the capability’s declared operations and
    design obligations.
  edge: >-
    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:
  - BR-CAP-MBR-STR-015-09
  - BR-CAP-MBR-STR-015-13
coverage_obligations:
  permissions:
    - key: membership.configuration.manage
      scenario_ids:
        - ATS-MBR-STR-019-02
      local_scenario_ids:
        - ATS-MBR-STR-015-13
      coverage: partial
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
      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-015-13
      coverage: partial
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
      note: >-
        These cases establish declared authority or representative admission; exhaustive operation and
        caller combinations remain open.
  state_models:
    - id: STATE-MBR-STR-004
      transitions:
        - from: pending
          to: committed
          trigger: Commerce completes a claimed package purchase.
          provenance: unknown
          source: FND-MBR-STR-068
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-068
        - from: pending
          to: cancelled
          trigger: Commerce authorizes cancellation of a pending purchase.
          provenance: unknown
          source: FND-MBR-STR-068
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-068
        - from: committed
          to: committed
          trigger: 'Staff correct dates, classification or package on a committed term.'
          provenance: unknown
          source: FND-MBR-STR-068
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-068
        - from: committed
          to: terminated
          trigger: 'Staff end a started term, or Commerce applies an authorized termination.'
          provenance: unknown
          source: FND-MBR-STR-068
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-068
        - from: committed
          to: cancelled
          trigger: 'Staff end a term on or before its start, or Commerce cancels an unstarted term.'
          provenance: unknown
          source: FND-MBR-STR-068
          scenario_ids: []
          coverage: gap
          gap: FND-MBR-STR-068
      gap: FND-MBR-STR-068
  related:
    - capability: CAP-MBR-STR-013
      scenario: ATS-MBR-STR-015-11
      provenance: proposed
    - capability: CAP-MBR-STR-016
      scenario: ATS-MBR-STR-015-12
      provenance: proposed
  invariants:
    - id: INV-DOM-MBR-STR-004-01
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-02
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-03
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-04
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-004-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-01
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-02
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-03
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-04
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-05
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-06
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-07
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-08
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
    - id: INV-DOM-MBR-STR-005-09
      scenario_ids: []
      coverage: gap
      gap: FND-MBR-STR-068
      provenance: unknown
      source: FND-MBR-STR-068
  workflows: []
  workflow_interactions: []
rule_coverage:
  - rule: BR-CAP-MBR-STR-015-01
    scenarios:
      - ATS-MBR-STR-015-01
      - ATS-MBR-STR-015-11
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        reports every problem of a rule at once
      - >-
        services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        reports every problem of a rule at once
  - rule: BR-CAP-MBR-STR-015-02
    scenarios:
      - ATS-MBR-STR-015-02
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:tests/acceptance/configuration/MBR-ACC-0101.acceptance.ts#MBR-ACC-0101: A
        package update is judged on the record as it will be after the change: switching the basis alone
        is refused, switching it with the dependent fields is accepted, and the renewal, reinstatement,
        seat and proration settings round-trip, clear and bound correctly
  - rule: BR-CAP-MBR-STR-015-03
    scenarios:
      - ATS-MBR-STR-015-03
      - ATS-MBR-STR-015-12
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › extends an early renewal from the current end, whatever the basis
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › extends an early renewal from the current end, whatever the basis
      - >-
        services-membership:modules/terms/tests/unit/status.spec.ts#holding status: the precedence of §2
        › puts suspended over grace
  - rule: BR-CAP-MBR-STR-015-04
    scenarios:
      - ATS-MBR-STR-015-04
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: the rules of 1.0
        beyond its spec cases › continues a terminated term on the last day of its window
  - rule: BR-CAP-MBR-STR-015-05
    scenarios:
      - ATS-MBR-STR-015-05
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 284-292: anniversary in years, should calculate
        properly for a single year › ends a purchase of 2019-04-03 at the end of the anniversary month,
        2020-04-30
  - rule: BR-CAP-MBR-STR-015-06
    scenarios:
      - ATS-MBR-STR-015-06
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 853-866: fixed expiration dates, simple in-year
        range › ends a purchase of 2023-04-03 in the window 03-01 to 05-31 on 2023-11-14
  - rule: BR-CAP-MBR-STR-015-07
    scenarios:
      - ATS-MBR-STR-015-07
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: 1.0
        calculate-expiration-date.spec.js › 1.0 lines 70-77: calendar, a standard year, should calculate
        properly for April › back-dates a purchase of 2019-04-03 to 2019-01-01 and ends it 2019-12-31
  - rule: BR-CAP-MBR-STR-015-08
    scenarios:
      - ATS-MBR-STR-015-08
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        ends a calendar period ending in February on its last day in a leap year: 2024-02-29 (decided by
        Andrew 2026-10-18: the real last day of the month; 1.0 added a year with Moment and ended it
        2024-02-28)
  - rule: BR-CAP-MBR-STR-015-09
    scenarios: []
    gap: FND-MBR-STR-068
    provenance: unknown
    source: FND-MBR-STR-068
  - rule: BR-CAP-MBR-STR-015-10
    scenarios:
      - ATS-MBR-STR-015-09
      - ATS-MBR-STR-015-14
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/term-rules.spec.ts#term rules: what 2.0 decides ›
        gives the same result twice for the same frozen inputs (1.0 turned the fixed dates into Moments
        in place, so a second call read other inputs)
      - 'services-membership:modules/terms/tests/unit/term-rules.property.spec.ts'
  - rule: BR-CAP-MBR-STR-015-11
    scenarios:
      - ATS-MBR-STR-015-10
    gap: null
    provenance: documented
    source:
      - >-
        services-membership:modules/terms/tests/unit/isolation.spec.ts#term engine isolation › imports
        nothing of the repository outside modules/terms
  - rule: BR-CAP-MBR-STR-015-13
    scenarios: []
    gap: FND-MBR-STR-068
    provenance: unknown
    source: FND-MBR-STR-068
narrative_sources:
  - section: Coverage
    provenance: documented
    source:
      - CAP-MBR-STR-015
      - FND-MBR-STR-068
  - section: Interactions With Related Capabilities
    provenance: proposed
    source:
      - ATS-MBR-STR-015-11
      - ATS-MBR-STR-015-12
---

# Determine a term’s start and end dates: 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-015-01` | `ATS-MBR-STR-015-01`, `ATS-MBR-STR-015-11` |
| `BR-CAP-MBR-STR-015-02` | `ATS-MBR-STR-015-02` |
| `BR-CAP-MBR-STR-015-03` | `ATS-MBR-STR-015-03`, `ATS-MBR-STR-015-12` |
| `BR-CAP-MBR-STR-015-04` | `ATS-MBR-STR-015-04` |
| `BR-CAP-MBR-STR-015-05` | `ATS-MBR-STR-015-05` |
| `BR-CAP-MBR-STR-015-06` | `ATS-MBR-STR-015-06` |
| `BR-CAP-MBR-STR-015-07` | `ATS-MBR-STR-015-07` |
| `BR-CAP-MBR-STR-015-08` | `ATS-MBR-STR-015-08` |
| `BR-CAP-MBR-STR-015-09` | Gap: `FND-MBR-STR-068` |
| `BR-CAP-MBR-STR-015-10` | `ATS-MBR-STR-015-09`, `ATS-MBR-STR-015-14` |
| `BR-CAP-MBR-STR-015-11` | `ATS-MBR-STR-015-10` |
| `BR-CAP-MBR-STR-015-13` | Gap: `FND-MBR-STR-068` |

## Interactions With Related Capabilities

Independent grants require a valid date rule. Submit a grant using its own anniversary rule but missing length, then correct the rule and calculate its period beside the primary term.

Calculated continuation dates avoid a standing gap. Compute a continuation from a term ending September 30 and evaluate coverage on September 30 and October 1.
