Feature Transaction Primitives
Pharos provides a bounded set of framework-owned transaction primitives for common record, scope, reservation, and status workflows. Apps declare these primitives in contracts. The shared runtime executes them.
Available primitives
append_recordset_root_fieldsupdate_recordappend_records_from_input_listappend_records_from_related_collectionscoped_reassignmentbulk_updatereplace_ordered_child_setdelete_records_by_scope- generated soft delete and restore
- aggregate guards
reserveconfirmrelease- approval transition
- scoped status transition
- derived or summary upsert
Where to look
- generated soft delete and restore —
ucal - aggregate guards before commit —
commerce_spa - reservation and release —
commerce_spa - parent plus many-child create —
dynamic_app - relation-derived row creation —
dynamic_app - scope-wide reassignment or update —
dynamic_app - ordered child-set replacement —
dynamic_app - status and approval transitions —
dynamic_app
Append one record
Use append_record when one action should create one record through a declared field map.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- one action creates one new record
next_id, input, literal, and anchor-derived values are applied by the shared runtime
Set root fields
Use set_root_fields when an action should update top-level summary state after the main mutation.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Expected result:
- the action updates a predictable top-level summary surface such as
last_action - the app does not need post-mutation glue for that update
Update one matched record
Use update_record when one action should update one matched record by a declared key.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Expected result:
- one matching record is updated
- unmatched ids fail closed instead of mutating the wrong row
Append many records from one input list
Use append_records_from_input_list when one submitted list should create many child rows without an app-owned loop.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- one parent action expands one submitted list into many child rows
- overflow either truncates with explicit skipped work or fails closed, depending on the declared contract
Append many records from a related collection
Use append_records_from_related_collection when one action should derive new rows from a matched related collection.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Expected result:
- matched related rows are copied through a declared record contract
- source-field mapping stays framework-owned and bounded
Scoped reassignment
Use scoped_reassignment when one action should rewrite one target field across a matched scope.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Expected result:
- only rows in the matched scope are rewritten
- overflow handling follows the declared bound
Bounded bulk update
Use bulk_update when one action should apply one declared field-update set across all rows in a matched scope.
Working examples:
apps/dynamic_app/data/contracts/actions.ymlapps/dev_docs/data/contracts/actions.yml
Expected result:
- all allowed matching rows receive the declared updates
- oversized matched scopes fail closed before partial mutation
Scoped delete by matched boundary
Use delete_records_by_scope when one action should remove rows across one declared scope without app-owned loops.
Runtime behavior:
- matches rows by one declared scope field and one submitted input
- counts the full matched set before mutation
- fails closed when the matched scope exceeds
max_recordsandoverflow_policyisfail - removes rows and reports matched, removed, and skipped counts when the contract allows the operation
Working example:
apps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- strict overflow denial returns the declared validation failure before deletion
- bounded purge removes the matched rows and reports final counts
Ordered child-set replace
Use replace_ordered_child_set when one parent-scoped action should replace a full ordered child collection in one bounded operation.
Runtime behavior:
- matches one child collection by parent or scope field
- updates existing rows in deterministic order
- creates missing rows when the input list is longer than the current set
- removes trailing rows when the input list is shorter than the current set
- reports matched, created, updated, removed, and skipped counts
Working example:
apps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- the scoped child set is rewritten in order through one action
- existing rows are updated before new rows are created
- surplus scoped rows are removed
- overflow can be truncated and reported as skipped work
Generated soft delete and restore
Use generated soft delete and restore when the entity is soft-deletable and operators need reversible removal.
Runtime behavior:
- derives delete and restore routes for generated admin and generated API surfaces
- hides inactive records where the generated surface contract requires it
- restores the record through the shared runtime instead of app-local routes
Working examples:
apps/ucal/data/contracts/routes.ymlapps/ucal/data/contracts/actions.yml
Validation:
bin/pharos build app ucal --build-dir dist
sh scripts/check.sh ucal tenant-api
Expected result:
- generated delete works
- generated restore works
- restored records become active again
Negative-path rule:
- hard-delete entities do not receive restore behavior
Aggregate guards
Use aggregate guards when the app should reject a mutation based on bounded counts, sums, or existence checks before commit.
Working example:
apps/commerce_spa/data/features/cart.yml
Validation:
bin/pharos build app commerce_spa --build-dir dist
sh scripts/check.sh commerce-spa postgresql-runtime
Expected result:
- over-cap mutations are rejected
- the message comes from the declared contract
- valid checkout and download flows still succeed
Reservation, confirm, and release
Use reserve, confirm, and release when the app needs a bounded hold before final commit.
Typical uses:
- inventory reservation
- booking slot holds
- temporary entitlement claims
- temporary capacity claims
Working examples:
apps/commerce_spa/data/features/cart.ymlapps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app commerce_spa --build-dir dist
sh scripts/check.sh commerce-spa postgresql-runtime
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- reserve succeeds when capacity is available
- reserve fails with a structured conflict when capacity is exhausted
- release clears the held row
- confirm advances the held row without app-local transaction code
Scoped status transition
Use scoped status transition when one action should move a bounded set of matching records from one declared status family to another.
Runtime behavior:
- matches one declared scope
- checks allowed
from_statuses - writes the target status
- can also write workflow-state or timestamp fields when the contract declares them
Working example:
apps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- all matching records move through the declared transition
- the action report distinguishes matched, updated, and skipped work
Approval transition
Use approval transition when the app needs a submit, approve, or reject flow with consistent approver capture and reporting.
Working example:
apps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- approval requests can be submitted
- approval requests can be approved
- rejection captures the declared reason and actor fields
Derived or summary upsert
Use derived or summary upsert when an action should materialize one framework-owned summary or snapshot row from matched runtime data.
Working example:
apps/dynamic_app/data/contracts/actions.yml
Validation:
bin/pharos build app dynamic_app --build-dir dist
sh scripts/check.sh dynamic-app native-runtime
Expected result:
- the shared runtime computes and persists the summary row
- the app does not need bespoke glue code for the repeated pattern
When not to use these primitives
Do not use these primitives when the behavior:
- depends on hidden app-owned imperative code
- needs arbitrary database logic outside the bounded contract
- cannot be explained with one app-owned declaration and one shared runtime rule
At that point the framework contract should be widened deliberately instead of hiding app logic behind a nominally shared primitive.
Related references
Relative server timestamps
Declared query filter values can use {server_time_offset_seconds: -2592000} to select a UTC timestamp thirty days before the current server time. The expression accepts exactly one integer offset between minus and plus 366 days. It cannot execute code or select a browser-provided clock. Filters continue through the generic database predicate interface. For example, an app can combine operator: gte with this value to display upcoming records and the previous thirty days while retaining older records for administration.