---
id: WF-MBR-STR-004
title: 'Offer a package: the Accounting product first, then its membership extension'
trigger: Staff or a client prepare an Accounting product and then create its package or local dues extension.
product_area: membership
module: structure
revision: 1
status: draft
design_intent: confirmed
origin: >-
  D-2 (Andrew, 2026-10-18: the console saves the product through Accounting first, then the package):
  services-membership:docs/migrations/packages-as-products/README.md#Packages as products: the membership
  package and the unit dues product are extensions of Accounting products
implementation_evidence: partially-observed
source_sha:
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
inference: []
implements:
  - CAP-MBR-STR-012
  - CAP-MBR-STR-013
  - CAP-MBR-STR-008
moves: []
crosses:
  - SVC-ACC-PRD-products
  - SVC-MBR-STR-configuration
  - SVC-MBR-STR-structure
idempotency: >-
  Extension identity is product identity, so a duplicate create is rejected without rewriting the existing
  extension. Product retries follow Accounting's contract; a failed extension can be retried independently.
concurrency: >-
  Concurrent extension creations contend on the primary key. Product validation does not lock Accounting
  through a shared offering workflow, so later product changes are reflected on subsequent reads.
hops:
  - 'n': 1
    actor_or_service: 'Console or API client, through Accounting'
    action: >-
      Follow the documented product-first sequence by saving shared description, status, prices and Accounting
      setup for the appropriate product type.
    anchor: 'services-membership:docs/migrations/packages-as-products/README.md#What could not be done here'
    seam: sync
    on_failure: Accounting failure stops preparation; its API transaction guarantees are outside this verification.
    left_inconsistent: Membership has not yet written an extension.
    observer: the client
    provenance: documented
    source: >-
      services-membership:docs/migrations/packages-as-products/README.md#Packages as products: the membership
      package and the unit dues product are extensions of Accounting products
  - 'n': 2
    actor_or_service: Configuration API or Structure API
    action: >-
      Send a separate Membership create request using product identity and the required package or structure
      management permission.
    anchor: 'services-membership:aws/api/configuration-routes.ts:configurationRoutes'
    seam: sync
    on_failure: Authorization or request validation rejects the Membership step.
    left_inconsistent: A separately committed product may remain without its extension.
    observer: the client only
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
  - 'n': 3
    actor_or_service: Products port
    action: >-
      Read the product through the port and check existence and product type; package schema validation
      rejects shared product fields.
    anchor: 'services-membership:modules/structure/ports/accounting-products.ts:extensionProblems'
    seam: sync
    on_failure: Reject an absent or wrong-type product and other input problems.
    left_inconsistent: Any previously saved product remains outside Membership's rollback.
    observer: the client only
    provenance: test
    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
  - 'n': 4
    actor_or_service: Configuration service
    action: 'For packages, validate the combined term rule, submitted grants and kind requirements.'
    anchor: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
    seam: sync
    on_failure: Reject invalid membership configuration before commit.
    left_inconsistent: The base product remains available for correction or retirement in Accounting.
    observer: the client only
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:packageResource
  - 'n': 5
    actor_or_service: Configuration or structure service
    action: >-
      Persist the extension and submitted lists in one Membership transaction, applying program references and
      extension uniqueness constraints.
    anchor: >-
      services-membership:modules/configuration/services/persistence/membership-packages.ts:replacePackageChildren
    seam: sync
    on_failure: A Membership constraint refusal rolls back the extension and its lists.
    left_inconsistent: The separately committed Accounting product is retained.
    observer: the client only
    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
  - 'n': 6
    actor_or_service: Configuration or structure service
    action: >-
      Read the joined extension inside its transaction; package content history settles at commit before
      returning the result.
    anchor: >-
      services-membership:modules/configuration/services/persistence/membership-packages.ts:getMembershipPackage
    seam: sync
    on_failure: Read-back failure rolls back Membership while leaving the separately saved product.
    left_inconsistent: A product without an extension can remain after Membership failure.
    observer: package revisions in the memberships module's history
    provenance: observed
    source: >-
      services-membership:modules/configuration/services/configuration/configuration-records.ts:createConfiguration
branches:
  - >-
    Product save and retirement functions are exposed by the port, but no Membership caller composes an atomic
    offering save.
  - 'Package updates skip renewed product validation, and package deletion retains the base product.'
  - Local dues extensions can change their package reference but have no Membership deletion route.
waits:
  - The client coordinates the separate product and extension requests.
evidence:
  - >-
    services-membership at 5994c270: modules/structure/ports/accounting-products.ts;
    services/persistence/accounting-products.ts, organizational-unit-dues-products.ts;
    modules/configuration/services/configuration/configuration-records.ts (packageResource,
    createConfiguration); services/persistence/membership-packages.ts;
    docs/migrations/packages-as-products/README.md.
controls: [TC-AVL-002, TC-AVL-003, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-DAT-001, TC-DAT-004, TC-DAT-007, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-SEC-001, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
---

# Offer a package: the Accounting product first, then its membership extension

## What It Does

The documented offering sequence prepares the shared Accounting product before its Membership extension. The verified API implements the extension step, with product existence and type checks. No administration screen implements the complete sequence in the permitted console scope.

## The Flow

```mermaid
sequenceDiagram
  actor Staff
  participant Accounting
  participant Membership
  Staff->>Accounting: save base product
  Accounting-->>Staff: product identity
  Staff->>Membership: create extension using identity
  Membership->>Accounting: read and validate product type
  Membership->>Membership: validate membership settings
  Membership->>Membership: write extension and lists transactionally
  Membership-->>Staff: joined offering or refusal
```

## When Something Fails

Membership rolls back only its own extension transaction. A product saved separately can remain without membership effect, and no examined job finds or compensates that condition. Joined read-back occurs before Membership commit and is part of its rollback boundary.

## Open Design Questions

Product must choose between implementing a composed atomic save and detecting incomplete offerings. The existing product-write port supports such composition but does not constitute a completed atomic workflow.
