---
id: CAP-ACC-PRD-007
title: Keep domain products under their owning application
feature: FEAT-ACC-PRD-01
product_area: accounting
module: products
revision: 4
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-ACC-PRD-018
  - react-console acceptance inventory RUL-ACC-PRD-046
  - react-console acceptance inventory RUL-ACC-PRD-054
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - 'services-accounting:docs/migrations/accounting-modernization/products-shared/README.md#D-1'
  - 'services-accounting:docs/migrations/accounting-modernization/products-shared/README.md#D-2'
purpose: A domain application saves the Accounting product alongside its own extension record.
outcomes:
  - Shared product writes respect application ownership.
concepts:
  - CON-ACC-PRD-001
actors:
  - ACT-ACC-010
  - ACT-ACC-006
surfaces:
  - api
  - job
permissions:
  - key: accounting.products.create
    where_checked: service
    scope: record
  - key: accounting.products.update
    where_checked: service
    scope: record
  - key: accounting.products.delete
    where_checked: service
    scope: record
rules:
  - id: BR-CAP-ACC-PRD-007-01
    proved_by: [ATS-ACC-PRD-001-06, ATS-ACC-PRD-007-01, TST-ACC-PRD-042, TST-ACC-PRD-066]
    statement: >-
      Service callers may mutate only product kinds owned by their application, with refusals identifying the
      owner, kind and caller.
    kind: permission
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.shared-base.integration.spec.js#refuses
      Commerce creating a membership package, naming the owner, the type and the caller
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-PRD-007-02
    proved_by: [ATS-ACC-PRD-007-01, TST-ACC-PRD-042, TST-ACC-PRD-066]
    statement: Domain service callers cannot mutate Accounting-owned product kinds.
    kind: permission
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.shared-base.integration.spec.js#refuses
      a service creating an Accounting-owned type
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-PRD-007-03
    proved_by: [ATS-ACC-PRD-007-02, TST-ACC-PRD-029, TST-ACC-PRD-042, TST-ACC-PRD-066]
    statement: >-
      Console and system callers may write any active product kind, allowing the console to save the common
      product before its domain extension.
    kind: permission
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/unit/rules/product-rules.spec.js#lets a console caller write
      any type (the console calls Accounting first and the domain second)
    origin: 'services-accounting:docs/migrations/accounting-modernization/products-shared/README.md#D-2'
  - id: BR-CAP-ACC-PRD-007-04
    proved_by: [ATS-ACC-PRD-007-03, ATS-ACC-PRD-007-10, TST-ACC-PRD-032]
    statement: >-
      A domain can save the common product within its own database transaction so the common record and
      extension share a commit boundary.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js#is
      the same product Accounting’s own persistence service reads
    origin: 'services-accounting:docs/migrations/accounting-modernization/products-shared/README.md#D-2'
  - id: BR-CAP-ACC-PRD-007-05
    proved_by: [ATS-ACC-PRD-007-04, TST-ACC-PRD-032]
    statement: >-
      Database product writes derive workspace, actor and application from session context and reject sessions
      lacking a workspace or application identity.
    kind: permission
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js#refuses
      a transaction with no tenant as RHP04
    origin: none
  - id: BR-CAP-ACC-PRD-007-06
    proved_by: [ATS-ACC-PRD-007-05, TST-ACC-PRD-032]
    statement: >-
      The database saving path shares product validation sentences with Accounting and distinguishes invalid
      input, incompatible state, absence and unauthorized callers.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js#refuses
      bundled prices above the product price with one sentence on both paths
    origin: none
  - id: BR-CAP-ACC-PRD-007-07
    proved_by: [ATS-ACC-PRD-007-06, TST-ACC-PRD-032]
    statement: >-
      The database saving path requires a supplied identity, writes no accounting setup and rejects
      product-owned confirmation content.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js#refuses
      a product-specific confirmation e-mail, which Accounting’s API composes
    origin: none
  - id: BR-CAP-ACC-PRD-007-08
    proved_by: [ATS-ACC-PRD-007-07, ATS-ACC-PRD-007-10, TST-ACC-PRD-032]
    statement: >-
      Database replacement preserves omitted scalar properties, clears explicit empty values and replaces
      collections in full.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js#replaces
      the row and every collection as a whole, keeping an omitted property and clearing a null one
    origin: none
  - id: BR-CAP-ACC-PRD-007-09
    proved_by: []
    statement: >-
      The database path provides creation, replacement and retirement; console API reactivation and deletion
      currently expose no domain service route.
    kind: policy
    enforced_at:
      - database
    must_prove: false
    provenance: documented
    source: 'services-accounting:docs/accounting/products.md#The domain write path'
    origin: none
  - id: BR-CAP-ACC-PRD-007-10
    proved_by: []
    statement: >-
      Accounting permits a domain product without an extension record and leaves completion or retirement to
      that domain.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-accounting:docs/accounting/products.md#Who may write a product (the owner rule)'
    origin: none
  - id: BR-CAP-ACC-PRD-007-11
    proved_by: [ATS-ACC-PRD-007-08]
    statement: >-
      Database domain saving defaults an omitted component price to zero and compares unweighted component
      price sums with the bundle’s list prices.
    kind: calculation
    enforced_at:
      - database
    must_prove: true
    provenance: observed
    source: 'services-accounting:db/migrations/20261019201047_product_lock_and_held_bundle_components.sql'
    origin: none
  - id: BR-CAP-ACC-PRD-007-12
    proved_by: [ATS-ACC-PRD-007-09]
    statement: >-
      Database domain saving continues admitting audiences already held by the product when Commerce’s
      audience registry exists.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: observed
    source: 'services-accounting:db/migrations/20261019201047_product_lock_and_held_bundle_components.sql'
    origin: none
scenarios:
  - id: SC-CAP-ACC-PRD-007-01
    given: Membership saves a package inside its own transaction.
    when: The extension write fails after the common product save.
    then: 'The documented workflow expects rollback of both records, pending executable cross-domain proof.'
    provenance: documented
    source: 'react-console:acceptance/accounting/journeys/products/ACT-ACC-0147.json'
  - id: SC-CAP-ACC-PRD-007-02
    given: Commerce acts under its service identity.
    when: It attempts to create a Membership package.
    then: Ownership validation names Membership and rejects the write.
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.shared-base.integration.spec.js#refuses
      Commerce creating a membership package, naming the owner, the type and the caller
edge_cases: []
constraints: []
depends_on:
  - CAP-ACC-PRD-001
related:
  - CAP-ACC-PRD-002
  - CAP-ACC-PRD-004
  - CAP-ACC-PRD-005
journeys:
  - JNY-ACC-PRD-006
open_questions:
  - OQ-ACC-PRD-003
evidence:
  - 'react-console:acceptance/accounting/journeys/products/ACT-ACC-0147.json'
  - 'services-accounting:db/migrations/20261018200018_products_shared_base.sql'
  - 'services-accounting:db/migrations/20261019201047_product_lock_and_held_bundle_components.sql'
  - 'services-accounting:docs/accounting/products.md'
  - 'services-accounting:docs/migrations/accounting-modernization/products-shared/README.md'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md'
  - 'services-accounting:modules/products/services/persistence/products.ts'
  - 'services-accounting:modules/products/services/rules/product-rules.ts'
  - 'services-accounting:modules/products/tests/integration/db/save-product-function.integration.spec.js'
  - >-
    services-accounting:modules/products/tests/integration/services/persistence/products.shared-base.integration.spec.js
  - 'services-accounting:modules/products/tests/unit/rules/product-rules.spec.js'
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-DAT-004, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-005]
exercised_by: [FAS-ACC-PRD-01-02]
suites: [TST-ACC-PRD-029, TST-ACC-PRD-032, TST-ACC-PRD-042, TST-ACC-PRD-066]
---

# Keep domain products under their owning application

## Purpose

A domain application saves the Accounting product alongside its own extension record.

## How It Works

A domain application saves the Accounting product alongside its own extension record. Its trusted database session establishes which application is acting. The owner check confines service callers to their own kinds. Console and system callers have a broader path because domain screens save the common record before the extension. Database saves can share the domain transaction, unlike the console’s separate requests. The database contract keeps omitted scalar values, whereas the API replacement clears them. Product-owned template composition also remains an API concern. A domain needs an executable rollback test before claiming its extension and the common product always fail together.

## Relationships

This outcome uses understand the available product kinds.
