---
id: ATS-PFM-MTD-010
scope: CAP-PFM-MTD-010
product_area: platform
module: metadata
revision: 2
status: proposed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-MTD-010-01
    title: Placement accepts available apps and valid groups
    level: api
    proves:
      - BR-CAP-PFM-MTD-010-01
    preconditions: Local and foreign custom apps exist.
    steps:
      - do: 'Place objects in a built-in app, a local custom app, unknown and foreign apps and a blank group.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
          an app that is neither an application nor a custom app, naming it, and writes nothing
    expected: Valid destinations succeed and unavailable apps or blank groups fail.
    negative: true
    data_needs: Local and foreign custom apps exist.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#places
        objects in orders, meetings and eligibility, console apps the licensing keys do not carry
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
        an app that is neither an application nor a custom app, naming it, and writes nothing
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
        a custom app of another tenant as unknown
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
        a group blank once trimmed
      - >-
        services-platform:modules/custom-objects/tests/unit/placement-apps.spec.ts#are the console’s seventeen
        navigation apps, in its order
      - TST-PFM-MTD-067
      - TST-PFM-MTD-074
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
      an app that is neither an application nor a custom app, naming it, and writes nothing
    category: validation
    assertions:
      - expect: Valid destinations succeed and unavailable apps or blank groups fail.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
          an app that is neither an application nor a custom app, naming it, and writes nothing
  - id: ATS-PFM-MTD-010-02
    title: An object can work without app placement
    level: browser
    proves:
      - BR-CAP-PFM-MTD-010-02
    preconditions: A valid object can be activated without placement or owner.
    steps:
      - do: Activate it and inspect a boat available only through contacts.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#activates
          an object that has neither a placement nor an owner association type (B4: no activation rule for
          either)
    expected: Activation succeeds and related records remain accessible without a menu entry.
    negative: false
    data_needs: A valid object can be activated without placement or owner.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#activates
        an object that has neither a placement nor an owner association type (B4: no activation rule for
        either)
      - >-
        react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S13.spec.ts#PLAT-M5-S13: a boat
        placed only on contacts has no menu entry and is reached from the contact's page
      - TST-PFM-MTD-067
      - TST-PFM-MTD-013
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#activates
      an object that has neither a placement nor an owner association type (B4: no activation rule for either)
    category: interaction
    assertions:
      - expect: Activation succeeds and related records remain accessible without a menu entry.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#activates
          an object that has neither a placement nor an owner association type (B4: no activation rule for
          either)
  - id: ATS-PFM-MTD-010-03
    title: Custom app definitions reject reserved identities and invalid values
    level: api
    proves:
      - BR-CAP-PFM-MTD-010-03
    preconditions: App creation and replacement are available.
    steps:
      - do: 'Submit reserved app keys, invalid labels, icons and order and a stale replacement.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
          the key of a platform application, which a placement could not tell from it
    expected: Invalid requests fail while a valid app starts unarchived at its first version.
    negative: true
    data_needs: App creation and replacement are available.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
        the key of a platform application, which a placement could not tell from it
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
        a console app’s key that is not a licensing key (meetings), as the key of a platform application
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
        a display order below zero
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-apps.e2e.spec.js#answers 409 to a
        replacement at a stale version
      - TST-PFM-MTD-060
      - TST-PFM-MTD-049
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
      the key of a platform application, which a placement could not tell from it
    category: validation
    assertions:
      - expect: Invalid requests fail while a valid app starts unarchived at its first version.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
          the key of a platform application, which a placement could not tell from it
  - id: ATS-PFM-MTD-010-04
    title: Live placements prevent app archival
    level: api
    proves:
      - BR-CAP-PFM-MTD-010-04
      - INV-DOM-PFM-MTD-004-07
    preconditions: Draft and active objects are placed in one app.
    steps:
      - do: Archive the app before and after moving or archiving its live objects.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
          to archive an app while a draft and an active object are placed in it, naming both, and leaves the
          app unchanged
    expected: >-
      The initial refusal names dependencies and the later archival succeeds while archived objects can retain
      placement.
    negative: true
    data_needs: Draft and active objects are placed in one app.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
        to archive an app while a draft and an active object are placed in it, naming both, and leaves the app
        unchanged
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#archives
        the app once its objects are moved elsewhere or archived; an archived object keeps its placement
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-apps.e2e.spec.js#answers 409 to archiving an
        app an active object is placed in, naming it, and the app stays listed with it (B3)
      - TST-PFM-MTD-060
      - TST-PFM-MTD-049
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
      to archive an app while a draft and an active object are placed in it, naming both, and leaves the app
      unchanged
    category: interaction
    assertions:
      - expect: >-
          The initial refusal names dependencies and the later archival succeeds while archived objects can
          retain placement.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
          to archive an app while a draft and an active object are placed in it, naming both, and leaves the
          app unchanged
  - id: ATS-PFM-MTD-010-05
    title: Archived apps reject new placement and editing
    level: api
    proves:
      - BR-CAP-PFM-MTD-010-05
    preconditions: A custom app is archived.
    steps:
      - do: Create or move a placement into it and replace its settings.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
          a new placement in an archived custom app, and writes nothing
    expected: All three operations fail without writes.
    negative: true
    data_needs: A custom app is archived.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
        a new placement in an archived custom app, and writes nothing
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
        a replacement that moves an object into an archived custom app, the row unchanged
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
        a replacement of an archived app, which must be restored first
      - TST-PFM-MTD-067
      - TST-PFM-MTD-060
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
      a new placement in an archived custom app, and writes nothing
    category: transition
    assertions:
      - expect: All three operations fail without writes.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#refuses
          a new placement in an archived custom app, and writes nothing
  - id: ATS-PFM-MTD-010-06
    title: Navigation follows object-scoped reach
    level: api
    proves:
      - BR-CAP-PFM-MTD-010-08
    preconditions: 'Scoped roles, broad readers and a caller without record access exist.'
    steps:
      - do: Request their custom navigation.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a role
          scoped to custom_object:boat the boats alone
    expected: >-
      Scoped callers receive only reachable objects, broad readers exclude roles-only objects and the
      empty-access caller is refused.
    negative: true
    data_needs: 'Scoped roles, broad readers and a caller without record access exist.'
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a role
        scoped to custom_object:boat the boats alone
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a role
        scoped to the roles-only object that object, which a customer-wide reader does not see
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#refuses 403 a
        caller who reads no object’s records
      - TST-PFM-MTD-051
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a role
      scoped to custom_object:boat the boats alone
    category: permission
    assertions:
      - expect: >-
          Scoped callers receive only reachable objects, broad readers exclude roles-only objects and the
          empty-access caller is refused.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a role
          scoped to custom_object:boat the boats alone
  - id: ATS-PFM-MTD-010-07
    title: Reach and lifecycle jointly select navigation
    category: interaction
    level: api
    exercises:
      - CAP-PFM-MTD-010
      - CAP-PFM-MTD-009
      - CAP-PFM-MTD-011
    proves:
      - BR-CAP-PFM-MTD-010-04
      - BR-CAP-PFM-MTD-010-08
      - BR-CAP-PFM-MTD-009-08
      - BR-CAP-PFM-MTD-011-02
    preconditions: A roles-only active object is placed in a custom app.
    steps:
      - do: >-
          Request navigation with broad and scoped grants, attempt app archival, archive the object and
          request navigation again.
        provenance: proposed
        source: &ref_0
          - >-
            services-platform:modules/custom-objects/tests/integration/services/persistence/custom-apps.integration.spec.js#refuses
            to archive an app while a draft and an active object are placed in it, naming both, and leaves the
            app unchanged
          - >-
            services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers a
            role scoped to custom_object:boat the boats alone
          - >-
            services-platform:modules/custom-objects/tests/integration/services/persistence/custom-objects.integration.spec.js#restores
            an archived object as a draft, keeping its object id
          - >-
            services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#keeps a
            roles-only object from a customer-wide assignment, answering as if it were not there
    expected: Compare navigation and app archival responses under each configuration.
    negative: false
    data_needs: A roles-only active object is placed in a custom app.
    bound_to: []
    provenance: proposed
    source: *ref_0
    assertions:
      - expect: Compare navigation and app archival responses under each configuration.
        provenance: proposed
        source: *ref_0
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1: the placement, custom apps and navigation
    specs named in each scenario.
  - 'react-console at baf6dae382d6f41dce976c7f848667ccc37e076f: Playwright PLAT-M5-S13.'
  - >-
    Codex testing back-fill: pinned capability, domain object, state model and workflow obligations reviewed;
    existing bound test files and exact case titles checked.
coverage:
  rule: 'gap: FND-PFM-MTD-072'
  transition: covered
  validation: covered
  permission: covered
  edge: 'gap: FND-PFM-MTD-072'
  failure: 'gap: FND-PFM-MTD-072'
  boundary: 'gap: FND-PFM-MTD-072'
  concurrency: 'gap: FND-PFM-MTD-072'
  invariant: 'gap: FND-PFM-MTD-072'
  interaction: covered
coverage_obligations:
  - rule: BR-CAP-PFM-MTD-010-01
    scenarios:
      - ATS-PFM-MTD-010-01
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-02
    scenarios:
      - ATS-PFM-MTD-010-02
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-03
    scenarios:
      - ATS-PFM-MTD-010-03
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-04
    scenarios:
      - ATS-PFM-MTD-010-04
      - ATS-PFM-MTD-010-07
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-05
    scenarios:
      - ATS-PFM-MTD-010-05
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-08
    scenarios:
      - ATS-PFM-MTD-010-06
      - ATS-PFM-MTD-010-07
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
  - rule: BR-CAP-PFM-MTD-010-09
    scenarios: []
    finding: FND-PFM-MTD-072
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
interactions:
  - with: CAP-PFM-MTD-011
    scenarios:
      - ATS-PFM-MTD-010-07
    finding: null
    provenance: proposed
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-010-put-custom-objects-where-staff-look-for-them.md
---

# Make Custom Records Discoverable: Capability Test Specification

## Coverage

- Obligation 01: 1 scenario cite this obligation.
- Obligation 02: 1 scenario cite this obligation.
- Obligation 03: 1 scenario cite this obligation.
- Obligation 04: 2 scenarios cite this obligation.
- Obligation 05: 1 scenario cite this obligation.
- Obligation 08: 2 scenarios cite this obligation.
- Obligation 09: The coverage finding records the missing scenario.

Category coverage identifies the kind of proof present. Detailed transition, permission and invariant obligations and unbound proposals remain in the coverage finding until their individual cases are accounted for.

## Interactions With Related Capabilities

Only reachable active placement contributes to navigation and blocks app archival. The structured interaction rows identify the related capabilities exercised and any remaining gap. Cross-capability workflow seams remain subject to the detailed coverage audit.
