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 newL2Instance. Field-level only (no cross-entity ripple — that’s rename’s job).rename_identifier(instance, kind, old, new)— rewrite every reference tooldacross 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) raisesL2ValidationError; 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
|
BB.1 — append |
|
Append a new entity to the kind's collection. |
|
Remove one entity. |
|
Replace one entity's fields with new values. |
|
Rename an identifier across every L2 reference. |
|
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_nameto 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:
- Parameters:
instance (L2Instance)
new_rail_name (str)
reconciler_kind (str)
reconciler_name (str)
reconciler_kind:"transfer_template"— append toTransferTemplate.leg_rails"aggregating_rail"— append toRail.bundles_activity(the rail must already beaggregating=True)
Returns a new
L2Instancewith the reconciler’s field updated. Caller composesvalidate(...)afterward.- Raises:
KeyError – no reconciler with that
reconciler_nameexists.ValueError –
reconciler_kindnot recognized, or the matched reconciler isn’t a valid receiver (e.g., a non-aggregating rail picked asaggregating_rail).
- Parameters:
instance (L2Instance)
new_rail_name (str)
reconciler_kind (str)
reconciler_name (str)
- Return type:
- 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 raiseValueErrorfrom the dataclass constructor; ID collisions raiseValueErrorhere (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 composesvalidate(...)afterward to catch L2-graph break (e.g., a Rail referencing roles that don’t exist yet).- Return type:
- 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
L2ValidationErrorand returns 400 with the message inline.- Return type:
- Returns:
A new
L2Instancewith the matched entity removed.- Raises:
KeyError – no entity with that
entity_idexists.- 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 viadataclasses.replace. Keys MUST match the dataclass field names; unknown keys raiseValueError.
- Return type:
- Returns:
A new
L2Instancewith the matched entity replaced.- Raises:
KeyError – no entity with that
entity_idexists in the target collection.ValueError –
fieldscontains 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 tonew.Per kind:
account / account_template (ID = role): walks every
role/parent_role/source_role/destination_role/leg_rolefield; 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 composesvalidate(...)if cascade resulted in an invalid model (e.g., renaming to a value that collides with another entity’s identifier).- Return type:
- 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
personasingleton — 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 raisesValueError(loader’s own exceptions inheritL2LoaderErrorwhich 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:
- Parameters:
instance (L2Instance)
kind (Literal['account', 'account_template', 'rail', 'transfer_template', 'chain', 'limit_schedule', 'theme', 'instance'])
yaml_text (str)