---
id: SVC-PFM-MTD-custom-objects
title: Custom Objects Service Module
product_area: platform
module: metadata
repository: services-platform
source_module: modules/custom-objects
revision: 1
status: draft
design_intent: confirmed
origin: Custom objects architecture D-01 to D-17 (approved by Andrew 2026-10-18), D-19 and D-20 (2026-10-18), PLAT-M5
  D1 to D17 and B1 to B5 (services-platform:docs/milestones/PLAT-M5-custom-records.md, Product Accepted 2026-10-20);
  the business events half follows services-platform:docs/business-events.md under PLAT-M7, which is proposed; R2-ARCH-002
  v2.0 (Andrew, 2026-10-22) for the publication column
implementation_evidence: observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owns:
- DOM-PFM-MTD-004
- DOM-PFM-MTD-005
does_not_own:
- Metadata writes property definitions and rules.
- Associations maintains owner types and other record links.
- Security creates object-scoped role assignments.
- Queries owns dataset consumption, saved Explorer views and exports.
- The Data Job Engine owns load orchestration, while this service contributes record rules and writes.
- Eligibility owns referenced portal-access bindings.
- Shared libraries own asset preparation machinery; this module supplies its binding and lifecycle persistence.
operations:
- operation_id: getCustomObjectsHealth
  intent: Return the Custom Objects build identity and health response.
  access: public
  permission: 'none: the shared health route, named explicitly'
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: Operational health checks have no intake binding.'
- operation_id: listCustomObjects
  intent: Return local category definitions in plural-label order with optional status filtering.
  access: console
  permission: platform.custom-objects.read
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: getCustomObjectsNavigation
  intent: Assemble active categories and containing apps within the caller’s scoped record-read reach.
  access: console
  permission: platform.custom-records.read (scoped by custom object, learnt from the rows)
  realises:
  - CAP-PFM-MTD-010
  - CAP-PFM-MTD-011
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: createCustomObject
  intent: Create a draft category with permanent object and record-type identities.
  access: console
  permission: platform.custom-objects.create
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: getCustomObject
  intent: Return one local definition, including inactive lifecycle states.
  access: console
  permission: platform.custom-objects.read
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: updateCustomObject
  intent: Replace the category’s mutable naming, ownership, access, publication, placement and limit settings at
    its version.
  access: console
  permission: platform.custom-objects.update
  realises:
  - CAP-PFM-MTD-009
  - CAP-PFM-MTD-010
  - CAP-PFM-MTD-011
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: activateCustomObject
  intent: Validate draft naming and ownership, activate the category and commit its Explorer recipes.
  access: console
  permission: platform.custom-objects.update
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: archiveCustomObject
  intent: Retain a category outside ordinary use while making its existing records directly read-only.
  access: console
  permission: platform.custom-objects.delete
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: restoreCustomObject
  intent: Reopen an archived category as a draft for another activation.
  access: console
  permission: platform.custom-objects.delete
  realises:
  - CAP-PFM-MTD-009
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: listCustomApps
  intent: Return ordered local apps and their currently active placed objects.
  access: console
  permission: platform.custom-objects.read
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: createCustomApp
  intent: Create a local application-menu grouping with a nonreserved permanent key.
  access: console
  permission: platform.custom-objects.create
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: getCustomApp
  intent: Return the selected app whether or not it is archived.
  access: console
  permission: platform.custom-objects.read
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: updateCustomApp
  intent: Save an app’s complete mutable presentation settings at its current version.
  access: console
  permission: platform.custom-objects.update
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: archiveCustomApp
  intent: Retain an app outside navigation after all live object placements have been moved.
  access: console
  permission: platform.custom-objects.delete
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: restoreCustomApp
  intent: Make an archived app available for placement again.
  access: console
  permission: platform.custom-objects.delete
  realises:
  - CAP-PFM-MTD-010
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: listCustomObjectRecords
  intent: Return a named, paged object register with supported archive, publication and name-prefix filters.
  access: console
  permission: platform.custom-records.read (scoped to the object)
  realises:
  - CAP-PFM-MTD-012
  - CAP-PFM-MTD-011
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: createCustomObjectRecord
  intent: Prepare attachments and create a validated entry under an active category.
  access: console
  permission: platform.custom-records.create (scoped to the object)
  realises:
  - CAP-PFM-MTD-012
  - CAP-PFM-MTD-007
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: getCustomObjectRecord
  intent: Return one entry with grouped current connections and audit the view.
  access: console
  permission: platform.custom-records.read (scoped to the object)
  realises:
  - CAP-PFM-MTD-012
  - CAP-PFM-MTD-018
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: updateCustomObjectRecord
  intent: Apply supplied property changes at the record version the caller read.
  access: console
  permission: platform.custom-records.update (scoped to the object)
  realises:
  - CAP-PFM-MTD-012
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: replaceCustomObjectRecord
  intent: Save the complete live property set at its version, clearing omitted properties.
  access: console
  permission: platform.custom-records.update (scoped to the object)
  realises:
  - CAP-PFM-MTD-012
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: deleteCustomObjectRecord
  intent: Permanently remove an entry and permitted custom dependants under transactional relationship policy.
  access: console
  permission: platform.custom-records.delete (scoped to the object)
  realises:
  - CAP-PFM-MTD-013
  - CAP-PFM-MTD-019
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: publishCustomObjectRecord
  intent: Select a supported publication state on a publishable record, maintaining its publication timestamp.
  access: console
  permission: platform.custom-records.publish (scoped to the object)
  realises:
  - CAP-PFM-MTD-013
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: archiveCustomObjectRecord
  intent: Retain record content outside ordinary lists and edits.
  access: console
  permission: platform.custom-records.delete (scoped to the object)
  realises:
  - CAP-PFM-MTD-013
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: restoreCustomObjectRecord
  intent: Return a retained archived entry to ordinary use under an active object.
  access: console
  permission: platform.custom-records.delete (scoped to the object)
  realises:
  - CAP-PFM-MTD-013
  - CAP-PFM-MTD-015
  published_to_submissions: 'not published: This staff maintenance operation has no Submissions binding.'
- operation_id: listCustomObjectRecordHistory
  intent: Return newest-first attributed changes with supported timeline filters.
  access: console
  permission: platform.custom-records.history, with platform.custom-records.read reach while the record exists
  realises:
  - CAP-PFM-MTD-014
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: getCustomObjectRecordRevision
  intent: Return the selected revision using current section and audience projection.
  access: console
  permission: platform.custom-records.history, with platform.custom-records.read reach while the record exists
  realises:
  - CAP-PFM-MTD-014
  published_to_submissions: 'not published: This read has no Submissions binding.'
- operation_id: listCustomObjectRecordHistoryActors
  intent: Return the actors represented in the entry’s retained history.
  access: console
  permission: platform.custom-records.history, with platform.custom-records.read reach while the record exists
  realises:
  - CAP-PFM-MTD-014
  published_to_submissions: 'not published: This read has no Submissions binding.'
events:
- name: objects.record.created
  direction: produced
  when: Creation of the record commits.
  payload_summary: Record category and identity, derived number and name, and publication state.
- name: objects.record.updated
  direction: produced
  when: Changed properties or movement between distinct non-public states commits.
  payload_summary: Record category and identity, derived number and name, and publication state; updates include
    sorted changed-property keys and an empty relationship-type list.
- name: objects.record.archived
  direction: produced
  when: Record archival commits.
  payload_summary: Record category and identity, derived number and name, and publication state.
- name: objects.record.restored
  direction: produced
  when: Restoration from record archival commits.
  payload_summary: Record category and identity, derived number and name, and publication state.
- name: objects.record.published
  direction: produced
  when: The record enters publication.
  payload_summary: Record category and identity, derived number and name, and publication state.
- name: objects.record.unpublished
  direction: produced
  when: The record leaves publication for another supported state.
  payload_summary: Record category and identity, derived number and name, and publication state.
- name: objects.record.deleted
  direction: produced
  when: Direct or policy-driven removal of the record commits.
  payload_summary: Record category and identity, derived number and name, and publication state.
jobs:
- name: platform_business_events_dispatcher
  kind: Invoke delivery every minute and after supported commit nudges.
- name: platform_business_events_dispatcher (sweep action)
  kind: Invoke the retention sweep nightly at 05:20 UTC.
- name: platform_business_events_consumer
  kind: Consume destination messages with failed-message redelivery and a five-receive dead-letter limit.
- name: platform_event_intake
  kind: Accept invoked envelopes for the declared native subscribers, currently an empty set.
integrations:
- system: Integration Platform event router
  direction: outbound
  contract: Dispatch uses the versioned event catalogue and resolves the external router through runtime parameter
    configuration.
- system: Queries
  direction: outbound
  contract: Active categories appear as datasets; activation supplies shared recipes and granted readers consume
    the typed store.
- system: Security
  direction: inbound
  contract: Security scopes record-operation grants to the custom object identified by the route.
consistency:
- rule: Record persistence commits values, reservations, attached references, audit, history and supported event
    content together.
  kind: transactional
  compensation: Transaction failure retains the previous record and abandons prepared copies.
- rule: Attachment copies are prepared before their references are committed.
  kind: transactional
  compensation: The intended disposal binding is absent, leaving abandoned and detached copies in storage.
- rule: Endpoint deletion and every reached custom dependant share a transaction.
  kind: transactional
  compensation: Any refusing member rolls back the whole removal.
- rule: Committed event messages are retried toward resolved destinations.
  kind: eventual
  compensation: Persistent failure dead-letters; no-destination work is removed and old unreferenced facts are swept
    without replay.
- rule: Activation stores Explorer recipes with the status change.
  kind: transactional
  compensation: Stored recipes remain after archival, while category availability governs their use.
evidence:
- 'services-platform at ad3f6a65: modules/custom-objects/aws/api/*.routes.ts, *.schemas.ts, *-api-mapper.ts; aws/lambda/*.handler.ts;
  aws/logic/*.lambda.ts; aws/resources/business-events.sqs.ts; services/**; db/**; record-events.ts; aws/platform.events.ts;
  aws/platform.service.ts (CUSTOM_OBJECTS_PERMISSIONS, CUSTOM_RECORDS_PERMISSIONS); docs/custom-objects.md; docs/business-events.md;
  docs/record-history.md; docs/standards/submission-compatible-operations.md'
controls: [TC-AI-001, TC-AI-002, TC-AI-003, TC-AI-004, TC-AI-006, TC-AI-007, TC-AVL-001, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, 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-DAT-005, TC-IAM-001, TC-IAM-002, TC-IAM-003, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-MON-003, 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]
---

# Custom Objects Service Module

## What This Service Is For

Custom Objects lets a workspace define local categories and maintain their entries. It owns object and app definitions, shared record values, attachment lifecycle, revisions and the supported event-delivery pipeline.

## Boundary

Metadata owns property contracts and Associations owns links. Security supplies scoped grants, Queries supplies datasets and Explorer consumption, and the Data Job Engine orchestrates imports through this service’s rules.

## Operations in Words

Definition operations configure and control object lifecycle; app and navigation reads place active categories. Record operations maintain content and publication, while history reads explain retained changes. Binding publication is deferred for custom-record intake.

## Events and Jobs

Seven supported record event types feed the dispatcher, consumer and nightly sweep. The same pipeline carries link events; the native subscriber declaration is empty, and external destinations depend on runtime router configuration.

## Consistency Guarantees

Supported record changes commit their database effects together. Files are prepared beforehand and delivery occurs afterwards. Missing asset cleanup and no-destination dispatch limit completion beyond the record transaction.
