---
id: WF-PFM-MTD-005
title: Save a custom record with its files
trigger: A record save prepares attachments before opening its persistence transaction.
product_area: platform
module: metadata
revision: 1
status: draft
design_intent: confirmed
origin: Custom objects architecture D-02 (typed shared store, approved 2026-10-18); PLAT-M5 D3, D4, D5 (services-platform:docs/milestones/PLAT-M5-custom-records.md#Decisions);
  services-platform:docs/assets.md §5 (prepare, then commit) and PLAT-M10 D1 (copy at save); the event hop follows
  services-platform:docs/business-events.md, whose milestone PLAT-M7 is proposed
implementation_evidence: observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
- CAP-PFM-MTD-012
- CAP-PFM-MTD-007
- CAP-PFM-MTD-014
- CAP-PFM-MTD-015
moves:
- STATE-PFM-MTD-005
- STATE-PFM-MTD-007
- STATE-PFM-MTD-008
crosses:
- SVC-PFM-MTD-custom-objects
- SVC-PFM-MTD-metadata
idempotency: Creation has no idempotency key; an external identifier can prevent duplicate creates within the object.
  Updates require the read version, and concurrent attachment preparations can leave an abandoned losing copy.
concurrency: Creation has no idempotency key; an external identifier can prevent duplicate creates within the object.
  Updates require the read version, and concurrent attachment preparations can leave an abandoned losing copy.
hops:
- n: 1
  actor_or_service: Records API
  action: The record API authorizes the operation at object scope and resolves the object without exposing unrelated
    records.
  anchor: services-platform:modules/custom-objects/aws/api/custom-object-records.routes.ts
  seam: sync
  on_failure: Return forbidden or not found before persistence.
  left_inconsistent: No record or attachment mutation is committed.
  observer: The initiating person or service and its operation logs.
  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
- n: 2
  actor_or_service: Asset pipeline (custom record binding)
  action: The asset binding mints create identity and prepares changed attachments outside the record transaction,
    with uploader checks and bounded copying or import.
  anchor: services-platform:modules/custom-objects/services/assets/custom-object-record-assets.ts:customObjectRecordAssets
  seam: sync
  on_failure: Reject the save at the property and abandon prepared copies when any preparation fails.
  left_inconsistent: Uncommitted copies remain because Platform lacks cleanup deployment.
  observer: The initiating person or service and its operation logs.
  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 pro
- n: 3
  actor_or_service: Custom record store
  action: The store holds the active parent shared, checks the record version and rules, then writes derived naming,
    values, reservations, attachments, audit and supported events transactionally.
  anchor: services-platform:modules/custom-objects/services/persistence/custom-object-records.ts:createRecord
  seam: sync
  on_failure: Roll back on invalid values, references, uniqueness, preparation or concurrency; abandon prepared
    copies.
  left_inconsistent: The earlier record is preserved, with abandoned storage copies outside its transaction.
  observer: The initiating person or service and its operation logs.
  provenance: test
  source: services-platform:modules/custom-objects/tests/integration/services/persistence/custom-object-records.events.integration.spec.js#publishes
    nothing when the tran
- n: 4
  actor_or_service: Database history capture
  action: Deferred database history capture settles the attributed revision at commit.
  anchor: services-platform:modules/custom-objects/db/custom-object-record-change-events.table.ts
  seam: sync
  on_failure: Fail the whole commit if source or actor is missing.
  left_inconsistent: No business mutation commits without its required revision.
  observer: The initiating person or service and its operation logs.
  provenance: documented
  source: services-platform:docs/record-history.md#Rules every writer meets
- n: 5
  actor_or_service: Custom record store
  action: After commit, the store asynchronously nudges event dispatch.
  anchor: services-platform:lib/events/platform-business-events.ts:nudgeBusinessEventsAfterCommit
  seam: event
  on_failure: Ignore a failed nudge and rely on a later successful scheduled run.
  left_inconsistent: Delivery waits in the outbox without a maximum latency guarantee.
  observer: Event delivery monitoring and the retained ledger.
  provenance: documented
  source: services-platform:docs/business-events.md#2. One event per committed change
branches:
- Patch changes supplied properties; replacement clears omitted live properties.
- Unchanged event content and external-identifier-only edits emit no event; unchanged business content produces
  no revision.
- Loads prepare attachment content before opening their batch transactions.
waits: []
evidence:
- 'services-platform at ad3f6a65: modules/custom-objects/services/persistence/custom-object-records.ts (createRecord,
  updateRecord, replaceRecord, saveChanges, requireRecordRules, requireAssetsPrepared, writeUniqueValues); services/assets/custom-object-record-assets.ts;
  record-rules.ts; lib/events/platform-business-events.ts; docs/custom-objects.md §13; docs/assets.md §5 and §7;
  docs/debt/custom-object-asset-cleanup.md'
- 'react-console at baf6dae3: components/console/custom-objects/record-editor.tsx'
controls: [TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CHG-005, TC-CHG-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Save a custom record with its files

## What It Does

A record save prepares attachments before opening its persistence transaction. The committed record then includes validated values, naming, reservations and the corresponding audit, history and supported event.

## The Flow

```mermaid
sequenceDiagram
  participant Keeper as Record keeper
  participant API as Records API
  participant Assets as Asset pipeline
  participant Store as Record store (one transaction)
  participant Hist as History capture
  participant Disp as Events dispatcher
  Keeper->>API: create, patch or replace
  API->>Assets: prepare changed files (outside transaction)
  Assets-->>API: ready copies, or refusal
  API->>Store: validate, number, name, write values, attach files, audit, event
  Store->>Hist: revision at commit
  Hist-->>Store: committed (or commit fails)
  Store-->>Keeper: saved record
  Store-)Disp: nudge
```

## When Something Fails

Reject the save at the property and abandon prepared copies when any preparation fails. Uncommitted copies remain because Platform lacks cleanup deployment. Roll back on invalid values, references, uniqueness, preparation or concurrency; abandon prepared copies. The earlier record is preserved, with abandoned storage copies outside its transaction. Ignore a failed nudge and rely on a later successful scheduled run. Delivery waits in the outbox without a maximum latency guarantee.

## Open Design Questions

Deploying Platform asset cleanup remains necessary to complete the attachment lifecycle.
