Configuration and file invariants
A stage with sort is always stored in that order. A stage without it keeps insertion order.
The rule belongs to the file, not to one command or one view.
Declare a sorted file
Add sort to the stage that owns the file. This example also configures identities, validation, recent dates, and lifecycle maps:
active.json and passive.json are sorted; declined.json is deliberately unsorted.
Every operation preserves it
promote and demote remain convenience wrappers for marketing pipelines.
Use move for arbitrary stage names and multi-branch lifecycles. CLI and MCP mutations,
merges, pending updates, and web edits all use the same invariant-preserving writer.
Sort contract
type defaults to string; sorting also supports number,
integer, boolean, and strict YYYY-MM-DD ISO dates.
Direction is ascending by default, nulls go last by default, and identity fields are the default tiebreakers.
A canonical record representation breaks remaining ties, so repeated writes are byte-stable.
shuffle is rejected for sorted stages.
Identity and field rules
Adds and moves reject duplicates using every configured identity field across the pipeline.
Field rules support required, forbidden, enum, and
type (string, number, integer, boolean,
array, object, iso-date). Arbitrary additional fields remain welcome.
validate audits the complete file; mutations reject invalid records before writing.
Queries and transition maps
find supports exact matches plus __lt, __lte, __gt,
__gte, and __ne. __within includes today through N future days.
--fields accepts space- or comma-separated projections. Maps run in
drop, rename, set order; set supports literal values,
{today}, and source fields such as {sol_id}.
Use the same operations from Python
Automation shares the CLI implementation. Operations live in jsoncrm.tool, invariant helpers in
jsoncrm.invariants, and each independent CRMContext owns one CRM's paths and configuration:
Individual files are replaced atomically. Concurrent writers are not locked, so coordinate writers or reconcile their git changes. Moves validate both sides before writing, but two file replacements are not a cross-file transaction.