---
id: ATS-PFM-INT-017
scope: CAP-PFM-INT-017
product_area: platform
module: integrations
revision: 3
status: proposed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-INT-017-01
    title: Unsupported handler configuration saves nothing
    level: api
    proves:
      - BR-CAP-PFM-INT-017-01
    preconditions: A workspace can create customization handlers.
    steps: 'Submit a Lambda target without qualifier, an HTTPS target and a target with unknown properties.'
    expected: Lambda defaults to live; HTTPS and extra properties refuse without a record.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-468
      - TST-PFM-INT-459
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
      an HTTPS handler as not available, naming decision D-02, and writes nothing
    category: validation
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        an HTTPS handler as not available, naming decision D-02, and writes nothing
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        a target property the kind does not name, so no secret value reaches the row
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#creates a Lambda handler
        with its qualifier defaulted to live, and never answers its secret
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-02
    title: Rule authors cannot create code handlers
    level: api
    proves:
      - BR-CAP-PFM-INT-017-02
    preconditions: A caller has rule-management authority only.
    steps: Create a function handler and a rule handler.
    expected: The function refuses authorization while the rule is accepted.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-459
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a Lambda handler to a
      caller who may manage only rules
    category: permission
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a Lambda handler to
        a caller who may manage only rules
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#admits a rule handler to a
        caller who may manage only rules
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-03
    title: Bound handler deletion protects configuration and secrets
    level: api
    proves:
      - BR-CAP-PFM-INT-017-03
    preconditions: A Lambda handler owns a secret and has a binding.
    steps: 'Read it, attempt deletion, remove the binding and delete again.'
    expected: >-
      No read exposes the secret, the bound delete refuses and the unbound delete schedules secret
      destruction.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-468
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#never
      stores the secret value anywhere in the row
    category: invariant
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#never
        stores the secret value anywhere in the row
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#hands
        the secret to the port under the handler’s own name, and stores only that name
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        to delete a handler a binding uses, naming why, and keeps it
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#deletes
        a handler no binding uses, scheduling its secret’s deletion, and records it
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#writes
        no row when the secret cannot be written
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-04
    title: Handler declarations fit every implemented point
    level: api
    proves:
      - BR-CAP-PFM-INT-017-04
    preconditions: Two points have five- and ten-second timeout ceilings.
    steps: >-
      Create an eight-second handler for both, use an unknown point, duplicate a name and omit a rule
      body.
    expected: Each invalid configuration reports its field and is rejected.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-468
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
      a timeout above the shortest ceiling of the points it implements
    category: boundary
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        a timeout above the shortest ceiling of the points it implements
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        implements naming a point that is not registered
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        a second handler of one name in the tenant as InvalidRequest naming the field
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        a rule without a definition, and a definition on a Lambda handler
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-05
    title: Rule contracts are checked on save and bind
    level: api
    proves:
      - BR-CAP-PFM-INT-017-05
      - AS-JNY-PFM-INT-005-01
    preconditions: A pricing point exposes a current contract version.
    steps: 'Save an invalid rule, save a valid one and bind it against an incompatible version.'
    expected: >-
      Save validation reports each problem, the valid rule persists and incompatible binding identifies
      its failures.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-468
      - TST-PFM-INT-460
      - TST-PFM-INT-466
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
      a rule that does not fit, naming each problem by its path and code, and writes nothing
    category: validation
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#refuses
        a rule that does not fit, naming each problem by its path and code, and writes nothing
      - >-
        services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#refuses a rule its
        point’s contract refuses as 400, naming each problem by its path and code
      - >-
        services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#saves a rule that fits
        its point’s current version
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
        to bind the rule on a version it does not fit, naming the problems, and writes nothing
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-handlers.integration.spec.js#accepts
        a rule that implements no point yet, to be checked when it is bound
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-06
    title: Binding validation rejects each faulty prerequisite
    level: api
    proves:
      - BR-CAP-PFM-INT-017-06
    preconditions: A pricing point and implementing handler are available.
    steps: 'Vary scope, version, failure policy and effective-window faults, then submit a valid binding.'
    expected: Invalid cases identify their fault; only the valid configuration is saved.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-466
      - TST-PFM-INT-459
      - TST-PFM-INT-475
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
      an overriding failure policy without a note, and accepts it with one
    category: validation
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
        an overriding failure policy without a note, and accepts it with one
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a scope kind the
        point does not allow as 400 naming the field
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a handler that does
        not implement the point as 400
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a version that is
        not current or supported as 400
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
        an effective window that ends before it starts
      - 'services-platform:modules/extensions/tests/unit/lifecycle.spec.js#refuses a retired point'
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-07
    title: Binding lifecycle and concurrency remain enforced
    level: unit
    proves:
      - BR-CAP-PFM-INT-017-07
    preconditions: Bindings exist in every lifecycle state.
    steps: Exercise every transition and attempt a stale replacement.
    expected: Only allowed transitions succeed and the stale replacement refuses.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-475
      - TST-PFM-INT-466
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become enabled,
      shadow or disabled, enabled and shadow to trade places, disabled to return, and orphaned only to be
      disabled
    category: concurrency
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become enabled,
        shadow or disabled, enabled and shadow to trade places, disabled to return, and orphaned only to
        be disabled
      - >-
        services-platform:modules/extensions/tests/unit/lifecycle.spec.js#never moves a binding to
        orphaned, which only the intake sets, and says so
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#lets
        an orphaned binding be disabled and nothing else
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
        a change at a stale version
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-08
    title: Single-answer configuration permits one winner
    level: api
    proves:
      - BR-CAP-PFM-INT-017-08
    preconditions: A product has an enabled binding for a single-answer point.
    steps: Enable another at the same product scope.
    expected: The second activation conflicts.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-459
      - TST-PFM-INT-466
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a second enabled
      binding at one scope of a single point as 409
    category: invariant
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses a second enabled
        binding at one scope of a single point as 409
      - >-
        services-platform:modules/extensions/tests/integration/services/persistence/extension-bindings.integration.spec.js#refuses
        a second enabled binding at one scope as an IllegalOperation naming the scope
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-09
    title: Preview explains scope precedence and ambiguity
    level: api
    proves:
      - BR-CAP-PFM-INT-017-09
    preconditions: Product and workspace bindings exist for one point.
    steps: 'Preview that product, another product and a tied choice.'
    expected: >-
      The product wins locally, workspace answers elsewhere and the tie reports both choices as a
      configuration error.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-478
      - TST-PFM-INT-459
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/unit/resolution.spec.js#lets a product binding beat a
      tenant binding
    category: edge
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/unit/resolution.spec.js#lets a product binding beat a
        tenant binding
      - >-
        services-platform:modules/extensions/tests/unit/resolution.spec.js#answers a tie as a
        configuration error naming both bindings
      - >-
        services-platform:modules/extensions/tests/unit/resolution.spec.js#excludes a shadow binding from
        the selection and answers it beside the winner
      - >-
        services-platform:modules/extensions/tests/unit/resolution.spec.js#leaves a binding outside its
        window out of the resolution
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#answers the default for a
        scope no binding matches
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-10
    title: Rule tests produce recorded outcomes
    level: api
    proves:
      - BR-CAP-PFM-INT-017-10
      - AS-JNY-PFM-INT-005-02
    preconditions: A pricing rule identifies Northeast members.
    steps: 'Test a matching member, a member elsewhere and invalid input.'
    expected: >-
      The match returns fifteen percent off, the non-match returns not-applicable or neutral and invalid
      input fails before running; completed tests enter history.
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-460
      - TST-PFM-INT-459
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#records each run as a test
      invocation of the handler
    category: rule
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#records each run as a
        test invocation of the handler
      - >-
        services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#answers the Northeast
        member’s example with 15 percent of the package off, in minor units, in quote mode
      - >-
        services-platform:modules/extensions/tests/e2e/rule-handlers.e2e.spec.js#answers the member
        elsewhere with rule_not_applicable
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#answers an input the
        contract refuses as a contract failure
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#validates the named example
        and answers that no transport runs in Platform yet
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-11
    title: Record facts preserve unknown information
    level: unit
    proves:
      - BR-CAP-PFM-INT-017-11
    preconditions: 'Prepare records with true, false and absent values plus an absent record.'
    steps: Evaluate the configured record fact.
    expected: 'Results respectively distinguish true, false, unknown and known record absence as false.'
    negative: false
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-479
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/unit/rule-handlers.spec.js#answers FALSE for a subject
      no row answered, known absent
    category: edge
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/unit/rule-handlers.spec.js#answers FALSE for a subject
        no row answered, known absent
      - >-
        services-platform:modules/extensions/tests/unit/rule-handlers.spec.js#answers TRUE for a subject
        the predicate holds for
      - >-
        services-platform:modules/extensions/tests/unit/rule-handlers.spec.js#answers UNKNOWN for a
        subject whose predicate is NULL
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-12
    title: Invocation reads respect authority and capture settings
    level: api
    proves:
      - BR-CAP-PFM-INT-017-12
    preconditions: History contains capturing and non-capturing handler runs.
    steps: Read lists and detail with and without invocation-read.
    expected: >-
      Unauthorized reads refuse; permitted reads are newest first and expose payload references only for
      capturing handlers.
    negative: true
    data_needs: 'Fixtures establishing the stated accounts, declarations and history in an isolated workspace.'
    bound_to:
      - TST-PFM-INT-459
    provenance: test
    source: >-
      services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses the invocation
      history without platform.extension-invocations.read
    category: permission
    binding_evidence:
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#refuses the invocation
        history without platform.extension-invocations.read
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#lists the tenant’s
        invocations of a point, newest first
      - >-
        services-platform:modules/extensions/tests/e2e/extensions.e2e.spec.js#reads one invocation,
        withholding its captured reference while the handler does not capture
    binding_scope: >-
      contributions: the cited tests support their own asserted outcomes. Additional setup, branches
      and combined sequences remain proposed unless the cited assertions exercise them.
  - id: ATS-PFM-INT-017-13
    level: api
    negative: true
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Exercise the complete extension binding lifecycle
    category: transition
    proves:
      - BR-CAP-PFM-INT-017-07
    preconditions: >-
      Prepare one independent record in each starting state, including absent-record cases where the
      model defines creation.
    steps: >-
      Run each listed transition once with its guard satisfied, then repeat it with the guard denied.
      Inspect the resulting record and listed side effects after commit.
    expected: >-
      Each positive case reaches its listed destination and records its defined side effects. Each denied
      case preserves the starting record and creates no transition side effects.
    source: >-
      rhythm-v2-docs:specs/platform/integrations/design/state-models/STATE-PFM-INT-013-extension-binding-lifecycle.md#transitions
    state_model: STATE-PFM-INT-013
    transition_cases:
      - from: draft
        to: enabled
        trigger: Apply a prepared attachment
        guard: 'Point, scope, handler, version and failure-policy checks pass'
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become
          enabled, shadow or disabled, enabled and shadow to trade places, disabled to return, and
          orphaned only to be disabled
      - from: draft
        to: shadow
        trigger: Run an attachment in shadow
        guard: The same contract and binding checks pass
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become
          enabled, shadow or disabled, enabled and shadow to trade places, disabled to return, and
          orphaned only to be disabled
      - from: draft
        to: disabled
        trigger: Set a prepared attachment aside
        guard: The update passes normal binding validation and version checks
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: enabled
        to: shadow
        trigger: Switch applied work to shadow
        guard: Normal validation and the single-answer uniqueness rule permit it
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: shadow
        to: enabled
        trigger: Promote shadow work
        guard: Normal contract and binding validation succeeds
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: enabled
        to: disabled
        trigger: Stop applied work
        guard: The update passes normal validation and version checks
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: shadow
        to: disabled
        trigger: Stop shadow work
        guard: The update passes normal validation and version checks
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: disabled
        to: draft
        trigger: Prepare a stopped attachment again
        guard: The update passes normal validation and version checks
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: disabled
        to: enabled
        trigger: Reapply a stopped attachment
        guard: Normal contract and binding validation succeeds
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: disabled
        to: shadow
        trigger: Restart stopped work in shadow
        guard: Normal contract and binding validation succeeds
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: 'services-platform:modules/extensions/lifecycle.ts:BINDING_TRANSITIONS'
      - from: enabled
        to: orphaned
        trigger: Withdraw the point with applied work
        guard: The supplied owner declaration omits the point
        side_effects:
          - Log orphaned-binding count as a warning.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/integration/services/registration/register-service-extensions.integration.spec.js#orphans
          every binding of a retired point, in every tenant, whatever its status
      - from: draft
        to: orphaned
        trigger: Withdraw the point with prepared work
        guard: The supplied owner declaration omits the point
        side_effects:
          - Log orphaned-binding count as a warning.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/integration/services/registration/register-service-extensions.integration.spec.js#orphans
          every binding of a retired point, in every tenant, whatever its status
      - from: shadow
        to: orphaned
        trigger: Withdraw the point with shadow work
        guard: The supplied owner declaration omits the point
        side_effects:
          - Log orphaned-binding count as a warning.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/integration/services/registration/register-service-extensions.integration.spec.js#orphans
          every binding of a retired point, in every tenant, whatever its status
      - from: disabled
        to: orphaned
        trigger: Withdraw the point with stopped work
        guard: The supplied owner declaration omits the point
        side_effects:
          - Log orphaned-binding count as a warning.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/integration/services/registration/register-service-extensions.integration.spec.js#orphans
          every binding of a retired point, in every tenant, whatever its status
      - from: orphaned
        to: disabled
        trigger: Stop an orphaned attachment
        guard: The requested lifecycle move is disabled and remaining validation passes
        side_effects:
          - Commit a security audit entry.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become
          enabled, shadow or disabled, enabled and shadow to trade places, disabled to return, and
          orphaned only to be disabled
    proof_boundary: >-
      The parameterized cases prove the listed lifecycle outcomes. Separate assertions are needed for
      independent object invariants, numeric limits and simultaneous requests.
  - id: ATS-PFM-INT-017-14
    level: api
    negative: true
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Exercise the complete extension handler standing
    category: transition
    proves:
      - BR-CAP-PFM-INT-017-13
    preconditions: >-
      Prepare one independent record in each starting state, including absent-record cases where the
      model defines creation.
    steps: >-
      Run each listed transition once with its guard satisfied, then repeat it with the guard denied.
      Inspect the resulting record and listed side effects after commit.
    expected: >-
      Each positive case reaches its listed destination and records its defined side effects. Each denied
      case preserves the starting record and creates no transition side effects.
    source: >-
      rhythm-v2-docs:specs/platform/integrations/design/state-models/STATE-PFM-INT-017-extension-handler-standing.md#transitions
    state_model: STATE-PFM-INT-017
    transition_cases:
      - from: active
        to: disabled
        trigger: Disable the handler
        guard: >-
          The actor has authority for the handler kind and supplies the current version; update locking
          permits the edit
        side_effects:
          - Commit status and security audit together; existing bindings remain.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/services/persistence/extension-handlers.ts:updateExtensionHandler
      - from: disabled
        to: active
        trigger: Enable the handler
        guard: >-
          The actor has authority for the handler kind and supplies the current version; update locking
          permits the edit
        side_effects:
          - Commit status and security audit together so eligible bindings can select it again.
        provenance: proposed
        source: >-
          services-platform:modules/extensions/services/persistence/extension-handlers.ts:updateExtensionHandler
    proof_boundary: >-
      The parameterized cases prove the listed lifecycle outcomes. Separate assertions are needed for
      independent object invariants, numeric limits and simultaneous requests.
  - id: ATS-PFM-INT-017-15
    level: api
    negative: false
    data_needs: Run-owned records and principals in an isolated workspace.
    bound_to: []
    provenance: proposed
    title: Registry withdrawal orphans configured work
    category: interaction
    proves:
      - BR-CAP-PFM-INT-016-04
      - BR-CAP-PFM-INT-017-07
    exercises:
      - CAP-PFM-INT-017
      - CAP-PFM-INT-016
    preconditions: Two workspaces have bindings to one registered customization point.
    steps: 'Omit the point from registration, redeclare it and attempt to activate an orphaned binding.'
    expected: >-
      Withdrawal retires the point and orphans the bindings; redeclaration does not repair them and only
      disabling is available.
    source:
      - >-
        services-platform:modules/extensions/tests/integration/services/registration/register-service-extensions.integration.spec.js#orphans
        every binding of a retired point, in every tenant, whatever its status
      - >-
        services-platform:modules/extensions/tests/unit/lifecycle.spec.js#allows draft to become enabled,
        shadow or disabled, enabled and shadow to trade places, disabled to return, and orphaned only to
        be disabled
    workflows: []
evidence:
  - >-
    Each binding identifies an existing source test at the pinned revision; unbound scenarios remain
    proposed proof obligations.
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-PFM-INT-084'
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_notes:
  covered: A designed scenario exists; executable coverage and a passing run require separate evidence.
  gaps: >-
    The finding records categories without a designed scenario and remaining permission and rule audit
    obligations.
---
# Apply tested customizations where an application allows them: Capability Test Specification

## Coverage

| Rule | Proved By |
|---|---|
| `BR-CAP-PFM-INT-017-01` | `ATS-PFM-INT-017-01` |
| `BR-CAP-PFM-INT-017-02` | `ATS-PFM-INT-017-02` |
| `BR-CAP-PFM-INT-017-03` | `ATS-PFM-INT-017-03` |
| `BR-CAP-PFM-INT-017-04` | `ATS-PFM-INT-017-04` |
| `BR-CAP-PFM-INT-017-05` | `ATS-PFM-INT-017-05` |
| `BR-CAP-PFM-INT-017-06` | `ATS-PFM-INT-017-06` |
| `BR-CAP-PFM-INT-017-07` | `ATS-PFM-INT-017-07`, `ATS-PFM-INT-017-13` |
| `BR-CAP-PFM-INT-017-08` | `ATS-PFM-INT-017-08` |
| `BR-CAP-PFM-INT-017-09` | `ATS-PFM-INT-017-09` |
| `BR-CAP-PFM-INT-017-10` | `ATS-PFM-INT-017-10` |
| `BR-CAP-PFM-INT-017-11` | `ATS-PFM-INT-017-11` |
| `BR-CAP-PFM-INT-017-12` | `ATS-PFM-INT-017-12` |

## Interactions With Related Capabilities

Handlers and bindings depend on the registered point and contract. The interaction cases follow withdrawal, redeclaration and contract changes through retained customer configuration and invocation history; sample console screens do not establish a connected execution path.

The structured scenarios name the capabilities and rules involved. A bound test contributes only the outcomes it asserts; proposed transition matrices and composed interactions still require executable proof.
