Skip to content

Save objects independently and create nested resources atomically #152

Description

@dahlia

Part of #138. Separate object persistence from activity creation and provide the common write operations that #140 and #141 will call.

Currently createObject also stores a Create and adds it to the actor's outbox. A developer must be able to save an object alone, reuse it in an activity, or save an activity together with a new embedded local resource.

Scope

  • Implement object-only persistence without creating an unwanted activity or dispatch. Make the distinction between private editor drafts and committed federation resources explicit; draft recovery belongs to Reuse drafts, documents, presets, and recipient sets #144.
  • Save an activity that refers to an existing resource or an external IRI, using the shared representation and validator. Do not clone or modify the referenced resource merely because it is selected as a target.
  • Save an activity with newly created local objects or nested activity resources as one consistent operation. Apply Define Activity Vocabulary coverage and the shared document contract #149's rules for identity allocation, ownership checks, and publication/outbox behavior when saving new resources.
  • Support preparing an Update of an existing local object without rewriting previous activity documents. Implement the agreed rules for when a committed operation updates current state; opening an editor or validating input has no such side effect.
  • Roll back all visible resource and relationship changes when validation or persistence fails. Recheck write permissions on the server, including nested local identities.
  • Adapt the existing createObject path without silently changing its established behavior for callers. Preserve it through an adapter or provide an explicit migration path with updated callers and documentation.

Completion criteria

  • Tests save an object alone, create an activity referencing it, and atomically create a Create with a new Note. Nested activity input uses the same common write path.
  • A failure inside nested creation leaves no orphaned public object, activity, addressing entry, or outbox item. Retrying a failed transaction does not expose a partially saved document.
  • Editing current object state leaves earlier activity snapshots intact. References keep their intended identities, and unauthorized cross-instance writes fail through direct API calls too.
  • Existing creation tests and callers either continue to work or exercise the documented transition. Saving never schedules an outgoing delivery.

Ownership and starting points

This issue owns shared persistence operations and adaptation of existing creation behavior. #140 and #141 own their new editor-facing mutation shapes and UI; #145 owns dispatch creation, freezing, and delivery. Coordinate the common operation's inputs and failures with those owners before they implement separate write paths.

Inspect packages/graphql/src/object.ts, packages/models/src/resource.ts, and packages/federation/src/object.ts. Verify transaction behavior on both supported database backends where it differs.

Activity

  1. added this to the DrFed 0.2 milestone on Oct 20, 2026
  2. added theissue type on Oct 20, 2026
  3. sij411 commented on Oct 30, 2026

    @sij411
    Member

    The shared preparation API in #166 exposes the following creation-field contract for integration with #152. This records the handoff here rather than requesting an update to closed #149.

    prepareDocument(document, options) accepts a parsed document plus trusted server-reported authority outcomes and server-owned creation values. #152 decides permissions, allocates IDs/timestamps, and chooses which id, actor, attributedTo, published, or updated fields it owns for each creation. These options must not come from editor input; they do not make those fields
    universally required on arbitrary documents.

    Missing owned values receive proposed additions. Supported null absence receives proposed replacements; explicit null IDs remain invalid under the identifier rule. A matching single value is retained. Conflicting claims receive server_owned_field_conflict, without silent replacement. Changes prefer the requested existing alias, and array conflicts retain source indexes.
    Denied outcomes receive unauthorized_local_identity at the supplied diagnostic location, including [] for an operation-level denial. Authority paths are diagnostic locations, not writable target declarations.

    Caller configuration is checked before document errors or authority denials stop preparation. Invalid supported-field names, trusted values, duplicate semantic declarations, and missing parents throw TypeError. When document errors or unsupported contexts/types prevent resolving a target or alias, the API retains document diagnostics rather than guessing a caller mistake.

    The API preserves input and returns explicit changes with an independent, revalidated preview only when checking completes without errors. It performs no permission queries, remote lookups, allocation, writes, or delivery.

    #152 owns preview-to-commit coordination, transaction-time permission rechecks, persistence, and adapting write operations. A successful preparation result is not an authorization token. Commit the server values represented in the approved preview, or obtain a new preview if those values change.

  4. self-assigned this
    on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

Priority

None yet

Effort

None yet

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions