---
id: ATS-PFM-MTD-012
scope: CAP-PFM-MTD-012
product_area: platform
module: metadata
revision: 2
status: proposed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-MTD-012-01
    title: Object status gates record writes
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-01
      - INV-DOM-PFM-MTD-005-01
    preconditions: Draft and archived objects and retained records exist.
    steps:
      - do: 'Attempt draft creation, read an archived-object record and perform each write action on it.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#refuses every
          write of it with 409 and changes nothing
    expected: Creation and writes fail while identity reads remain available.
    negative: true
    data_needs: Draft and archived objects and retained records exist.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#answers 409 to a
        record of a draft object
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#reads the record
        by id with its properties and an empty association list
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#refuses every
        write of it with 409 and changes nothing
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a record of another object under this one’s key
      - TST-PFM-MTD-051
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#refuses every write
      of it with 409 and changes nothing
    category: transition
    assertions:
      - expect: Creation and writes fail while identity reads remain available.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#refuses every
          write of it with 409 and changes nothing
  - id: ATS-PFM-MTD-012-02
    title: Property errors are specific and discreet
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-02
      - INV-DOM-PFM-MTD-005-02
    preconditions: An active object has typed properties.
    steps:
      - do: 'Submit invalid, unknown and archived properties.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
          the object’s label in that refusal, never "custom records" (D4)
    expected: Refusals identify the property and object label without echoing rejected values.
    negative: true
    data_needs: An active object has typed properties.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a property the object does not have, by name
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
        the object’s label in that refusal, never "custom records" (D4)
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a value its data type does not accept, without echoing it
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a value for an archived property
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
      the object’s label in that refusal, never "custom records" (D4)
    category: validation
    assertions:
      - expect: Refusals identify the property and object label without echoing rejected values.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
          the object’s label in that refusal, never "custom records" (D4)
  - id: ATS-PFM-MTD-012-03
    title: Required values cannot be omitted or cleared
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-03
    preconditions: A property is API-required.
    steps:
      - do: Create without it and clear it on update.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a record without a property required by the API
    expected: Both writes fail.
    negative: true
    data_needs: A property is API-required.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a record without a property required by the API
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a record without a property required by the API
    category: validation
    assertions:
      - expect: Both writes fail.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a record without a property required by the API
  - id: ATS-PFM-MTD-012-04
    title: Conditional rules evaluate resulting values
    level: browser
    proves:
      - BR-CAP-PFM-MTD-012-04
    preconditions: Insured and survey fields have conditional rules.
    steps:
      - do: 'Save an insured boat without its survey date, save an unrequired case and submit a hidden value.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a sail area on a vessel that is not a sloop, the property being hidden
    expected: The unmet requirement and changed hidden value fail; the optional case succeeds.
    negative: true
    data_needs: Insured and survey fields have conditional rules.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#requires
        the sail area of a sloop
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a sail area on a vessel that is not a sloop, the property being hidden
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#accepts
        a patch that hides the sail area while clearing it
      - >-
        services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#never hides a property whose
        visible_when cannot be decided
      - >-
        react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S04.spec.ts#PLAT-M5-S04: Survey
        Due is required only when Insured is ticked
      - TST-PFM-MTD-065
      - TST-PFM-MTD-076
      - TST-PFM-MTD-004
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a sail area on a vessel that is not a sloop, the property being hidden
    category: rule
    assertions:
      - expect: The unmet requirement and changed hidden value fail; the optional case succeeds.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a sail area on a vessel that is not a sloop, the property being hidden
  - id: ATS-PFM-MTD-012-05
    title: Creation fills compatible defaults
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-05
    preconditions: Date and text properties have defaults.
    steps:
      - do: 'Create with omitted values, supplied values and blank inputs.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
          a date to the tenant’s today, and a text to its literal
    expected: Defaults fill absent information while explicit valid values prevail.
    negative: false
    data_needs: Date and text properties have defaults.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
        a date to the tenant’s today, and a text to its literal
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
        a property a create sends as null
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#keeps
        the value a create gives over the default
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
        a contact reference to the portal caller’s contact
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
      a date to the tenant’s today, and a text to its literal
    category: rule
    assertions:
      - expect: Defaults fill absent information while explicit valid values prevail.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#defaults
          a date to the tenant’s today, and a text to its literal
  - id: ATS-PFM-MTD-012-06
    title: References identify records of the correct type
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-06
      - INV-DOM-PFM-MTD-005-04
    preconditions: Contact and custom-object references exist.
    steps:
      - do: 'Reference missing, foreign, wrong-object and archived targets.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a manufacturer that is a boat
    expected: Invalid targets are refused while compatible existing targets succeed.
    negative: true
    data_needs: Contact and custom-object references exist.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#accepts
        a manufacturer that is a record of the manufacturers object
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a manufacturer the tenant has no record of
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a manufacturer that is a boat
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a manufacturer that is a boat
    category: validation
    assertions:
      - expect: Invalid targets are refused while compatible existing targets succeed.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a manufacturer that is a boat
  - id: ATS-PFM-MTD-012-07
    title: Unique values are normalized and released
    level: browser
    proves:
      - BR-CAP-PFM-MTD-012-07
      - INV-DOM-PFM-MTD-005-03
    preconditions: One record already reserves its Hull Number.
    steps:
      - do: 'Submit the same normalized text on another record, change the holder and try the released value.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a hull number another boat holds, naming the property and the boat
    expected: The duplicate fails without a save; the changed value releases the old reservation.
    negative: true
    data_needs: One record already reserves its Hull Number.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a hull number another boat holds, naming the property and the boat
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        the same hull number in another case and with spaces around it
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#frees
        a hull number a patch changes, so another boat may take it
      - >-
        react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S05.spec.ts#PLAT-M5-S05: a
        duplicate Hull Number is refused, naming Hull Number, and nothing is saved
      - TST-PFM-MTD-065
      - TST-PFM-MTD-005
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a hull number another boat holds, naming the property and the boat
    category: invariant
    assertions:
      - expect: The duplicate fails without a save; the changed value releases the old reservation.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a hull number another boat holds, naming the property and the boat
  - id: ATS-PFM-MTD-012-08
    title: Naming properties remain single-line
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-08
    preconditions: Primary and secondary naming properties are configured.
    steps:
      - do: 'Submit an internal line break, boundary line breaks and a multiline unrelated value.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a create whose secondary display property holds one, naming the property, and writes no record
    expected: 'Internal naming breaks fail, boundaries are trimmed and unrelated multiline information survives.'
    negative: true
    data_needs: Primary and secondary naming properties are configured.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a create whose secondary display property holds one, naming the property, and writes no record
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a primary display property holding a carriage return
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#accepts
        a line break in a property the display name is not composed from
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#trims
        line breaks at the ends of a value, as every string is trimmed
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a create whose secondary display property holds one, naming the property, and writes no record
    category: boundary
    assertions:
      - expect: 'Internal naming breaks fail, boundaries are trimmed and unrelated multiline information survives.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a create whose secondary display property holds one, naming the property, and writes no record
  - id: ATS-PFM-MTD-012-09
    title: Names and numbers are derived consistently
    level: unit
    proves:
      - BR-CAP-PFM-MTD-012-09
    preconditions: An object has primary and secondary naming fields and a prefix.
    steps:
      - do: Create records with populated and absent naming values and oversized names.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is the number with the
          object’s prefix when the display property holds nothing
    expected: Names combine available components or the prefixed number and are bounded without splitting a character.
    negative: false
    data_needs: An object has primary and secondary naming fields and a prefix.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#numbers
        a boat after the well-known twelve, and the next one after it
      - >-
        services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is the display property
        followed by the secondary properties in parentheses
      - >-
        services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is the number with the
        object’s prefix when the display property holds nothing
      - 'services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is cut to 500 characters'
      - TST-PFM-MTD-065
      - TST-PFM-MTD-076
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is the number with the object’s
      prefix when the display property holds nothing
    category: rule
    assertions:
      - expect: >-
          Names combine available components or the prefixed number and are bounded without splitting a
          character.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is the number with the
          object’s prefix when the display property holds nothing
  - id: ATS-PFM-MTD-012-10
    title: Patch and replacement have different omission semantics
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-10
    preconditions: A record contains several properties.
    steps:
      - do: 'Patch one value, clear another with null and replace with an incomplete property map.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#clears
          every property a replacement leaves out
    expected: Patches retain omitted properties and replacement clears omitted live properties.
    negative: false
    data_needs: A record contains several properties.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#changes
        the properties a patch names and keeps the others
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#clears
        a property a patch sends as null
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#clears
        every property a replacement leaves out
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#clears
      every property a replacement leaves out
    category: rule
    assertions:
      - expect: Patches retain omitted properties and replacement clears omitted live properties.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#clears
          every property a replacement leaves out
  - id: ATS-PFM-MTD-012-11
    title: Concurrent editors receive a recoverable conflict
    level: browser
    proves:
      - BR-CAP-PFM-MTD-012-11
      - INV-DOM-PFM-MTD-005-07
    preconditions: Two registrars open one boat version.
    steps:
      - do: 'Save the first edit and submit the second stale edit, then try an update-locked record.'
        provenance: test
        source: >-
          react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S06.spec.ts#PLAT-M5-S06: two
          registrars edit one boat; the second is told and nothing is lost
    expected: >-
      The stale and locked edits fail without overwriting the committed record, and the console retains the
      unsaved work.
    negative: true
    data_needs: Two registrars open one boat version.
    bound_to:
      - >-
        react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S06.spec.ts#PLAT-M5-S06: two
        registrars edit one boat; the second is told and nothing is lost
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a change at another version, and leaves the record as it was
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a change of an archived record
      - TST-PFM-MTD-006
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S06.spec.ts#PLAT-M5-S06: two
      registrars edit one boat; the second is told and nothing is lost
    category: concurrency
    assertions:
      - expect: >-
          The stale and locked edits fail without overwriting the committed record, and the console retains
          the unsaved work.
        provenance: test
        source: >-
          react-console:acceptance/custom-objects/playwright/journeys/PLAT-M5-S06.spec.ts#PLAT-M5-S06: two
          registrars edit one boat; the second is told and nothing is lost
  - id: ATS-PFM-MTD-012-12
    title: External identities cannot collide
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-12
      - INV-DOM-PFM-MTD-005-12
    preconditions: A record holds an external identifier.
    steps:
      - do: Create or patch another record to the same identifier.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a create under an external id another record of the object holds, naming sys_external_id
    expected: Both requests fail and identify the external-identifier conflict.
    negative: true
    data_needs: A record holds an external identifier.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a create under an external id another record of the object holds, naming sys_external_id
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
        a patch to an external id another record of the object holds, and leaves the record unchanged
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
      a create under an external id another record of the object holds, naming sys_external_id
    category: invariant
    assertions:
      - expect: Both requests fail and identify the external-identifier conflict.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#refuses
          a create under an external id another record of the object holds, naming sys_external_id
  - id: ATS-PFM-MTD-012-13
    title: Files are prepared before the record mutation
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-13
      - INV-DOM-PFM-MTD-005-06
    preconditions: Image and document properties are configured.
    steps:
      - do: Save valid file sources and attempt missing or unsavable sources.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
          a create’s missing upload at properties.<key>, without the address sent
    expected: >-
      Valid files are managed in the record folder; failures identify the property without revealing the
      address.
    negative: true
    data_needs: Image and document properties are configured.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#copies
        an uploaded image into the record’s custom-objects folder and stores its permanent reference
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
        a create’s missing upload at properties.<key>, without the address sent
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
        a reference outside the file store at properties.<key>, without the address sent
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
      a create’s missing upload at properties.<key>, without the address sent
    category: interaction
    assertions:
      - expect: >-
          Valid files are managed in the record folder; failures identify the property without revealing the
          address.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
          a create’s missing upload at properties.<key>, without the address sent
  - id: ATS-PFM-MTD-012-14
    title: 'Lists, reads and audits follow record contracts'
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-14
      - INV-DOM-PFM-MTD-005-11
    preconditions: Records span publication and archive states.
    steps:
      - do: 'List with filters and limits, read a record and create or edit records.'
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#audits the read
          of a record as a view
    expected: >-
      Lists contain appropriate ordered pages, reads include links and view audits, and mutation audits
      disclose actions rather than values.
    negative: false
    data_needs: Records span publication and archive states.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#lists the
        unarchived boats by display name, a page with its cursor
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#lists the
        published boats when asked for them
      - >-
        services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#audits the read
        of a record as a view
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#records
        the update in the audit events
      - TST-PFM-MTD-051
      - TST-PFM-MTD-065
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#audits the read of
      a record as a view
    category: rule
    assertions:
      - expect: >-
          Lists contain appropriate ordered pages, reads include links and view audits, and mutation audits
          disclose actions rather than values.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/e2e/custom-object-records.e2e.spec.js#audits the read
          of a record as a view
  - id: ATS-PFM-MTD-012-15
    title: Untouched hidden values are retained
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-04
    preconditions: A stored value has a conditional visibility driver.
    steps:
      - do: Patch only the driver to hide the property.
        provenance: proposed
        source: >-
          services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:requireRecordRules
    expected: The save retains the untouched value.
    negative: false
    data_needs: A stored value has a conditional visibility driver.
    bound_to: []
    provenance: proposed
    source: >-
      services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:requireRecordRules
    category: edge
    assertions:
      - expect: The save retains the untouched value.
        provenance: proposed
        source: >-
          services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:requireRecordRules
  - id: ATS-PFM-MTD-012-16
    title: An active object permits deletion of an archived record
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-11
    preconditions: An archived record is unlocked and has no restricting links.
    steps:
      - do: Delete it using its current version.
        provenance: proposed
        source: 'services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:deleteRecord'
    expected: The record is removed despite its archive state.
    negative: false
    data_needs: An archived record is unlocked and has no restricting links.
    bound_to: []
    provenance: proposed
    source: 'services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:deleteRecord'
    category: transition
    assertions:
      - expect: The record is removed despite its archive state.
        provenance: proposed
        source: 'services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:deleteRecord'
  - id: ATS-PFM-MTD-012-17
    title: Scaled numeric storage respects safe precision
    level: api
    proves:
      - BR-CAP-PFM-MTD-012-15
    preconditions: Numeric definitions specify precision.
    steps:
      - do: Store a decimal and evaluate a value beyond safe scaled range.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#stores
          a decimal with its scaled integer shadow
    expected: The decimal stores its exact integer shadow and the unsafe result produces no shadow.
    negative: false
    data_needs: Numeric definitions specify precision.
    bound_to:
      - >-
        services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#stores
        a decimal with its scaled integer shadow
      - >-
        services-platform:modules/custom-objects/tests/unit/record-rules.spec.ts#is null for a value whose
        scaled form is beyond a safe integer
      - TST-PFM-MTD-065
      - TST-PFM-MTD-076
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#stores
      a decimal with its scaled integer shadow
    category: boundary
    assertions:
      - expect: The decimal stores its exact integer shadow and the unsafe result produces no shadow.
        provenance: test
        source: >-
          services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#stores
          a decimal with its scaled integer shadow
  - id: ATS-PFM-MTD-012-18
    title: Saving a prepared record precedes owner-link effects
    category: interaction
    level: api
    exercises:
      - CAP-PFM-MTD-012
      - CAP-PFM-MTD-006
      - CAP-PFM-MTD-007
      - CAP-PFM-MTD-013
      - CAP-PFM-MTD-014
      - CAP-PFM-MTD-015
      - CAP-PFM-MTD-018
    proves:
      - BR-CAP-PFM-MTD-012-13
      - BR-CAP-PFM-MTD-014-01
      - BR-CAP-PFM-MTD-015-01
      - BR-CAP-PFM-MTD-018-01
    preconditions: 'An active object has a file property, a conditional rule and an owner type.'
    steps:
      - do: >-
          Prepare a file, save valid properties, create the owner link, inspect record history and events,
          then reject a conditional-property save.
        provenance: proposed
        source: &ref_0
          - >-
            services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.integration.spec.js#names
            a create’s missing upload at properties.<key>, without the address sent
          - >-
            services-platform:modules/custom-objects/tests/integration/services/history/custom-object-record-history.integration.spec.js#answers
            the association, the update and the creation, newest first
          - >-
            services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.events.integration.spec.js#publishes
            nothing when the transaction that wrote the record rolls back, in the ledger or the queue
          - >-
            services-platform:modules/associations/tests/integration/services/persistence/associations.integration.spec.js#refuses
            a record associated with itself
    expected: Compare committed record and link effects with the rejected save’s unchanged history and event counts.
    negative: false
    data_needs: 'An active object has a file property, a conditional rule and an owner type.'
    bound_to: []
    provenance: proposed
    source: *ref_0
    assertions:
      - expect: >-
          Compare committed record and link effects with the rejected save’s unchanged history and event
          counts.
        provenance: proposed
        source: *ref_0
  - id: ATS-PFM-MTD-012-19
    title: A failed owner change preserves the earlier content save
    proves:
      - BR-CAP-PFM-MTD-012-10
      - BR-CAP-PFM-MTD-018-01
    preconditions: A saved boat has an owner and an editable property.
    steps:
      - do: >-
          Save a model correction, allow removal of the previous owner link, refuse the new owner link and
          reload the boat.
        provenance: proposed
        source: >-
          specs/platform/metadata/design/workflows/WF-PFM-MTD-006-change-a-custom-record-s-owner-after-its-save.md:hops
    expected: >-
      The corrected model remains saved and the owner replacement has not completed; retry uses the current
      content and link state.
    source: >-
      specs/platform/metadata/design/workflows/WF-PFM-MTD-006-change-a-custom-record-s-owner-after-its-save.md:hops
    category: failure
    level: browser
    negative: true
    data_needs: A saved boat has an owner and an editable property.
    bound_to: []
    provenance: proposed
    assertions:
      - expect: >-
          The corrected model remains saved and the owner replacement has not completed; retry uses the
          current content and link state.
        provenance: proposed
        source: >-
          specs/platform/metadata/design/workflows/WF-PFM-MTD-006-change-a-custom-record-s-owner-after-its-save.md:hops
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1: the record store specs named in each
    scenario.
  - 'react-console at baf6dae382d6f41dce976c7f848667ccc37e076f: Playwright PLAT-M5-S04, S05, S06.'
  - >-
    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: covered
  transition: covered
  validation: covered
  permission: 'gap: FND-PFM-MTD-074'
  edge: covered
  failure: covered
  boundary: covered
  concurrency: covered
  invariant: covered
  interaction: covered
coverage_obligations:
  - rule: BR-CAP-PFM-MTD-012-01
    scenarios:
      - ATS-PFM-MTD-012-01
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-02
    scenarios:
      - ATS-PFM-MTD-012-02
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-03
    scenarios:
      - ATS-PFM-MTD-012-03
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-04
    scenarios:
      - ATS-PFM-MTD-012-04
      - ATS-PFM-MTD-012-15
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-05
    scenarios:
      - ATS-PFM-MTD-012-05
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-06
    scenarios:
      - ATS-PFM-MTD-012-06
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-07
    scenarios:
      - ATS-PFM-MTD-012-07
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-08
    scenarios:
      - ATS-PFM-MTD-012-08
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-09
    scenarios:
      - ATS-PFM-MTD-012-09
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-10
    scenarios:
      - ATS-PFM-MTD-012-10
      - ATS-PFM-MTD-012-19
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-11
    scenarios:
      - ATS-PFM-MTD-012-11
      - ATS-PFM-MTD-012-16
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-12
    scenarios:
      - ATS-PFM-MTD-012-12
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-13
    scenarios:
      - ATS-PFM-MTD-012-13
      - ATS-PFM-MTD-012-18
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-14
    scenarios:
      - ATS-PFM-MTD-012-14
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - rule: BR-CAP-PFM-MTD-012-15
    scenarios:
      - ATS-PFM-MTD-012-17
    finding: null
    provenance: documented
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
interactions:
  - with: CAP-PFM-MTD-011
    scenarios: []
    finding: FND-PFM-MTD-074
    provenance: proposed
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - with: CAP-PFM-MTD-013
    scenarios:
      - ATS-PFM-MTD-012-18
    finding: null
    provenance: proposed
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - with: CAP-PFM-MTD-014
    scenarios:
      - ATS-PFM-MTD-012-18
    finding: null
    provenance: proposed
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
  - with: CAP-PFM-MTD-015
    scenarios:
      - ATS-PFM-MTD-012-18
    finding: null
    provenance: proposed
    source: >-
      specs/platform/metadata/planning/capabilities/CAP-PFM-MTD-012-keep-the-records-of-a-custom-object-validated-by-its-properties.md
---

# Maintain Valid Custom Records: 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 06: 1 scenario cite this obligation.
- Obligation 07: 1 scenario cite this obligation.
- Obligation 08: 1 scenario cite this obligation.
- Obligation 09: 1 scenario cite this obligation.
- Obligation 10: 2 scenarios cite this obligation.
- Obligation 11: 2 scenarios cite this obligation.
- Obligation 12: 1 scenario cite this obligation.
- Obligation 13: 2 scenarios cite this obligation.
- Obligation 14: 1 scenario cite this obligation.
- Obligation 15: 1 scenario cite this obligation.

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 successful mutations contribute their history and business events. The structured interaction rows identify the related capabilities exercised and any remaining gap. Cross-capability workflow seams remain subject to the detailed coverage audit.
