Skip to content

Import and export

The other way a community arrives: a YAML or JSON bundle, imported wholesale. This is how a community is seeded and how it is restored from a backup, and it is the only place in the service where a write may delete what it was not given.

The procedure and the file format are in import & export. What follows is what the behaviour must be.


REQ-0032 — an import is a full replace, and it is atomic

Importing a community deletes the existing community with every member and asset, then recreates the whole graph from the bundle. Entities missing from the new bundle are gone; changed ones are recreated; new ones are added.

The work is one transaction: if any step fails, nothing is applied. That is not a convenience — a half-applied replacement is a community whose members have been deleted and not recreated, and there is no state to roll back to except the transaction.

Rows are built by src/celine/rec_registry/services/members.py, the same code the admin write API uses. Two implementations of "what a member row looks like" would drift on the first schema change; REQ-0037 is what would notice.

REQ-0033 — overwriting an existing community must be asked for

An import naming a community that already exists is refused unless force is set: 409 over HTTP, and a non-zero exit from the CLI, in both cases naming the members and assets that would have been deleted.

With force, the replacement proceeds and the loss is accepted. Creating a community that does not exist yet needs no force.

This guard exists because the premise changed. Full replacement was safe while a YAML file was the only source of members; now that members arrive at runtime through the write API, restoring a stale export is the likeliest way to lose weeks of approvals. The counts in the refusal are the point — they are how a caller judges whether forcing is warranted.

REQ-0034 — a dry run reports and never writes, and is never blocked by the guard

dry_run returns the same report — what would be deleted, what would be inserted, with what warnings — and performs no database work at all: nothing is added and nothing is deleted.

A dry run against an existing community reports instead of refusing, even without force. Seeing the counts is exactly how a caller decides whether forcing is warranted, so the guard must not block the request that informs it.

REQ-0035 — a meter with no sensor id is skipped, with a warning naming it

The import continues and the report carries a warning naming the asset key and the missing field. Everything else in the bundle is applied.

A meter is identified by its sensor_id throughout the platform — it is how a reading finds its owner (REQ-0039). One stored without it is unreachable rather than merely incomplete, so it is not stored; and one bad meter is not a reason to refuse a community of two hundred members, so the import is not failed either.

REQ-0036 — the report names what was deleted, what was inserted, and what was warned about

Every import answers an ImportReport carrying the community key, deleted and inserted counts by entity type, and the list of warnings. The YAML route answers a MultiImportReport, one entry per document.

A malformed request — no bundle field, or a body that is not JSON — is 422 before any of that.

The counts are what makes a destructive operation reviewable after the fact. deleted being non-zero is the caller's evidence that a replacement rather than a creation happened.

REQ-0037 — a community exports the same whether its members arrived by API or by bundle

Create a member through the write API, export the community, re-import the export: the member is still there, unchanged, with its delivery points and its assets.

This is the property the two write paths exist to preserve, and the one that is invisible from reading either path alone. If it breaks, the symptom is a community that exports differently depending on how its members arrived — which nobody notices until a restore produces something subtly unlike the original, and by then the original is gone.

Practically: the file is a seed, GET /admin/export is a backup, and once members arrive at runtime the database is the source of truth.

One field is knowingly excluded from this guarantee: the declared schema version does not survive the round trip (REQ-0018).