recon_gen.common.l2.editor

Editor primitives — server-owned cascade for the X.4.e editor flow.

Three transforms on the in-memory L2Instance:

  • mutate_l2(instance, kind, id, fields) — replace one entity’s fields with the operator-supplied values, return a new L2Instance. Field-level only (no cross-entity ripple — that’s rename’s job).

  • rename_identifier(instance, kind, old, new) — rewrite every reference to old across the model. Symmetric to the strict validator’s reference-resolution pass: where the validator says “this Rail’s source_role MUST resolve to an Account.role”, rename rewrites those very fields when an Account.role changes.

  • delete_l2_entity(instance, kind, id) — remove one entity + run the validator. A structural break (some other entity still referenced the deleted one) raises L2ValidationError; the caller (Studio’s PUT handler) returns 400 with the validator message inline.

All three return a new L2Instance (the L2InstanceCache.replace contract from X.4.a.6) — the original is never mutated. The cache + disk-write pair handle persistence; this module is the pure-Python transform layer.

Severability: pure Python; no DB, no Starlette. Imports the model + validator only.

Functions

attach_rail_to_reconciler(instance, *, ...)

BB.1 — append new_rail_name to an existing reconciler's rail-list field so a freshly-created non-aggregating single-leg rail satisfies S3 / C3 reconciliation atomically with its create.

create_l2_entity(instance, kind, fields)

Append a new entity to the kind's collection.

delete_l2_entity(instance, kind, entity_id)

Remove one entity.

mutate_l2(instance, kind, entity_id, fields)

Replace one entity's fields with new values.

rename_identifier(instance, kind, old, new)

Rename an identifier across every L2 reference.

singleton_save_l2(instance, kind, yaml_text)

X.4.f.12 — write a singleton attribute from a raw YAML block.

recon_gen.common.l2.editor.attach_rail_to_reconciler(instance, *, new_rail_name, reconciler_kind, reconciler_name)[source]

BB.1 — append new_rail_name to an existing reconciler’s rail-list field so a freshly-created non-aggregating single-leg rail satisfies S3 / C3 reconciliation atomically with its create.

Return type:

L2Instance

Parameters:
  • instance (L2Instance)

  • new_rail_name (str)

  • reconciler_kind (str)

  • reconciler_name (str)

reconciler_kind:
  • "transfer_template" — append to TransferTemplate.leg_rails

  • "aggregating_rail" — append to Rail.bundles_activity (the rail must already be aggregating=True)

Returns a new L2Instance with the reconciler’s field updated. Caller composes validate(...) afterward.

Raises:
  • KeyError – no reconciler with that reconciler_name exists.

  • ValueErrorreconciler_kind not recognized, or the matched reconciler isn’t a valid receiver (e.g., a non-aggregating rail picked as aggregating_rail).

Parameters:
  • instance (L2Instance)

  • new_rail_name (str)

  • reconciler_kind (str)

  • reconciler_name (str)

Return type:

L2Instance

recon_gen.common.l2.editor.create_l2_entity(instance, kind, fields)[source]

Append a new entity to the kind’s collection.

Builds the entity from fields (already coerced to dataclass- field types by the caller). Required-but-missing fields raise ValueError from the dataclass constructor; ID collisions raise ValueError here (we’d rather fail loud at construction than let a duplicate slip into the collection and have the validator’s reference-resolution surface it as a confusing indirect error).

Returns a new L2Instance. The caller composes validate(...) afterward to catch L2-graph break (e.g., a Rail referencing roles that don’t exist yet).

Return type:

L2Instance

Parameters:
  • instance (L2Instance)

  • kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance'])

  • fields (Mapping[str, Any])

recon_gen.common.l2.editor.delete_l2_entity(instance, kind, entity_id)[source]

Remove one entity. Caller composes validate() to surface structural breaks.

Per the SPEC: “Structural break = reject, don’t auto-cascade.” Deleting a Rail that some TransferTemplate.leg_rails still references leaves the model in a state the strict validator rejects; the Studio PUT handler catches L2ValidationError and returns 400 with the message inline.

Return type:

L2Instance

Returns:

A new L2Instance with the matched entity removed.

Raises:

KeyError – no entity with that entity_id exists.

Parameters:
  • instance (L2Instance)

  • kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance'])

  • entity_id (str)

recon_gen.common.l2.editor.mutate_l2(instance, kind, entity_id, fields)[source]

Replace one entity’s fields with new values.

Parameters:
  • instance (L2Instance) – The L2 model to mutate (returns a new copy; original untouched).

  • kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance']) – Which collection the entity lives in.

  • entity_id (str) – The entity’s identity key — Account.id, Rail.name, TransferTemplate.name, AccountTemplate.role, Chain’s “<parent>::<sorted-children-csv>” composite, or LimitSchedule’s “<parent_role>::<rail>” composite.

  • fields (Mapping[str, Any]) – New field values, applied via dataclasses.replace. Keys MUST match the dataclass field names; unknown keys raise ValueError.

Return type:

L2Instance

Returns:

A new L2Instance with the matched entity replaced.

Raises:
  • KeyError – no entity with that entity_id exists in the target collection.

  • ValueErrorfields contains keys that aren’t dataclass fields of the target entity.

recon_gen.common.l2.editor.rename_identifier(instance, kind, old, new)[source]

Rename an identifier across every L2 reference.

Per the SPEC’s editor cascade rule: “Rename = auto-rewrite refs. Renaming an identifier walks the model and replaces every field that references the old value.” The reference catalog mirrors the strict validator’s reference-resolution pass — wherever the validator says “this field MUST resolve to old”, the rename rewrites that field to new.

Per kind:

  • account / account_template (ID = role): walks every role / parent_role / source_role / destination_role / leg_role field; rewrites RoleExpression tuples element-wise.

  • rail (ID = name): rewrites leg_rails (TransferTemplate), bundles_activity (Rail), parent / children (Chain).

  • transfer_template (ID = name): rewrites bundles_activity (Rail), parent / children (Chain).

  • chain / limit_schedule: have no incoming references — rename is a no-op (chains/limit_schedules are leaf consumers).

The Account.id / AccountTemplate (no .id, addressed by .role) / LimitSchedule (composite key) are addressing keys, not reference targets inside L2 — renaming Account.id walks the Account itself only (rename via mutate_l2(..., fields={"id": new})).

Returns a new L2Instance; original untouched. Does NOT run validation — caller composes validate(...) if cascade resulted in an invalid model (e.g., renaming to a value that collides with another entity’s identifier).

Return type:

L2Instance

Parameters:
  • instance (L2Instance)

  • kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance'])

  • old (Identifier)

  • new (Identifier)

recon_gen.common.l2.editor.singleton_save_l2(instance, kind, yaml_text)[source]

X.4.f.12 — write a singleton attribute from a raw YAML block.

BXa.1 (2026-05-30) removed the persona singleton — its fields promoted to top-level + nuked. Remaining singletons: theme (still YAML-textarea) + instance (new structured form per BXa.1; the YAML-textarea path stays as a fallback).

Empty / blank YAML clears the attribute back to None (silent-fallback contract — N.4.k for theme; equivalent for instance). Bad YAML / wrong shape raises ValueError (loader’s own exceptions inherit L2LoaderError which carries an actionable message). The studio handler catches both and re-renders the form with the operator’s typed content + the inline error.

Return type:

L2Instance

Parameters:
  • instance (L2Instance)

  • kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance'])

  • yaml_text (str)