Skip to main content

Code ↔ Instance: Planes and Sync

One sync system for every declared service: what the services tree projects into the database (planes), which hash proves what, the three directions, and the archive that moves the projected configuration as one zip.

TL;DR: Everything this instance serves is declared somewhere in code and enforced from what the process loaded and what the database holds. Three places in the console show that: Configuration (/admin/configuration, the Platform home) lists every kind of configuration the instance loads with its source, hash and — where the database holds a projection — its state; Code sync (/admin/sync) shows the services tree the instance serves and the archive that moves its projections; and each projected plane has a Sync tab on the page that owns it (Access control, Groups, Gateway → Policies) with its drift and the three directions. The one source of declarations on this instance is base, this repository's services/ tree, baked into the image; it has a content hash. No external sources are pinned. A plane is what a source's declarations project into the database — access control, groups and projects, gateway policies — and every plane records the declared hash it last applied, when, by whom and in which mode. Nothing writes on its own after the first seed: the page shows the difference and offers a direction.

The model

word meaning today
source a tree of declarations with a content hash base (this repository) only
plane a projection of declarations into database tables, with drift and three directions access_control (rules.yaml → access_control_rules, access_control_entities); groups (web/config/groups.yaml → groups, projects, group_ad_mappings, project_ad_mappings); gateway_policies (gateway/policies.yaml → ai_gateway_policies)
hash control every source reports what it declares; every plane records what it applied the table below

Planes are a contract, not a list: one impl SyncPlane and one registry line each, and the page grows a card. The three differ in what their loaders were built to do, and the drift table says so on every row:

plane insert only overwrite from code export the other writer
access_control missing rules and entity defaults corrects what differs; deletes undeclared rules on declared entities rules.yaml the access-control console (rows stamped dashboard)
groups missing groups, projects and AD mappings upserts every set, reconciles yaml-sourced mappings. Never deletes a group or project — those own members, usage and rules, and are deleted from their own page. A console-written mapping is kept. groups.yaml, every dashboard-created set and mapping included — never a member the Groups and Projects pages
gateway_policies missing policies upserts every declared policy, deletes undeclared rows policies.yaml, with calendar-month windows declared as calendar: month /admin/gateway/policies — live within a minute, because core reads the table per request
gateway_routes missing routes, appended after the rows already there makes the table equal the file's routes: sequence, order included; deletes undeclared rows the whole ai/gateway.yaml — the settings as the file has them, the routes: sequence from the table /admin/gateway (rows stamped dashboard). Core boots the dispatcher from the file and never reads the table; every console write and every apply by a person regenerates the file's routes: sequence from the rows, so a change is dispatched at the next restart. The boot seed does not rewrite the file (it came from the file).
governance missing policies makes governance_chain and its settings row equal the file: switch, inherited mode, every entry, order governance/config.yaml, rendered from the staged chain none yet — the plane validates through core's own parser and stages. Core builds the chain from the file at boot and never reads the table; a staged change is enforced after export, commit and restart.

Two planes take effect after a restart. gateway_routes and governance project a file core reads once at boot. Their cards carry a notice saying so, and an image built from a tree whose file moved on shows the move as drift on the next boot — the database is the console's ledger, the file is what the process runs, and the page is where the two are reconciled. A route the console wrote on the previous image is In database, not in code until it is applied or exported.

One boot contract

Every plane meets the same rule at boot, in the order groups → access control → gateway policies (the rules name group and project ids, so those must exist first). If the plane's projection is empty, boot seeds it from code — overwrite from code run once, recorded in sync_state with actor boot and mode seed. Otherwise boot compares and writes nothing: the drift is logged and the card on this page shows it. A restart therefore never undoes a console edit, on any plane; reconciling code and database is an administrator's act here, in one of the three directions. A declaration that cannot be read — the file does not parse, or the services tree it validates against does not compose — fails the boot rather than starting a server on half a declaration.

Before the planes run, boot makes the gateway_route catalog equal the routes the profile dispatches: it inserts unseen routes with the default rules.yaml declares and deletes stale ones, but never rewrites an existing row's default_included — that column belongs to the access-control plane, and scripts/check-plane-ownership.sh refuses any other plane that writes it.

People are never declared in code. Group and project membership, manually granted roles and per-person access overrides live only in the database and the directory; no plane declares them, no drift row names them and no export carries them. The role set itself is closed in code, and the directory → role map is sign-in configuration (web/config/adfs.yaml), not a projection.

gateway_policies is the one plane the console edits in place: a save at /admin/gateway/policies is enforced on the next request, and shows here as drift until it is exported and committed or overwritten from code. /admin/governance/quotas reads the same table's windows against ai_quota_buckets to show usage per subject against each ceiling.

The hashes

hash comes from changes when proves
base tree hash SHA-256 over the sorted path\0sha256 of every file under services/, computed on the page this repository deploys which tree the process is serving
plane declared hash SHA-256 over the canonical declaration (access control: every (entity, band, subject, access, why) plus defaults; groups: every set and mapping; gateway policies: every policy's name, switch, priority and normalised spec; gateway routes: every route's fields in file order, the fallback pair included; governance: the chain switch and mode, then every policy's id, switch, mode and canonical-JSON entry in order) the file's meaning changes (not its whitespace) what the database was last made equal to; recorded per apply in sync_state

The configuration page

/admin/configuration is one row per kind of configuration under services/ — the root aggregator, marketplaces, plugins, skills, MCP servers, gateway routes, providers, governance, scheduler, the web and content files, and the three projected declarations. Each row says where the kind comes from (base on this instance), the content hash it declares, and its mode:

  • projected — a sync plane owns it; the database is what is enforced. The row shows in step, drift · n, never applied or unreadable, who last applied which hash, and links to the owner page's Sync tab.
  • served from code — the services tree is read at boot or per request and the database never holds a copy. Its state is the hash. Marketplaces, plugins and skills are always this: a marketplace's content hash is its version, and version control lives in the repository.

The distinction is the honest one. Only the projected kinds have a database to disagree with; for everything else "what is running" is the tree, and the tree hash says which tree.

The three directions

Each projected plane's Sync tab — /admin/access-control?tab=sync, /admin/groups?tab=sync, /admin/gateway/policies?tab=sync — counts its differences by cause (added in code, changed in code, removed from code, written in console), prints a Next: line naming the action that settles the plane, and offers the same three actions, confirmed in words that name what will move. Each difference line carries a Resolves with badge naming its button.

action writes leaves alone
Insert only rows the declaration carries and the database lacks everything that exists, even if it differs
Overwrite from code adds what code added, corrects what code changed, deletes what code removed — including rows the declaration itself once wrote on entities it has since dropped rows the console wrote on entities the declaration does not mention; for access control, every per-person override
Export nothing — renders the database as the declaration file to commit; the only way a console row reaches code —

Every apply runs in one transaction that re-reads the tables first, writes an activity row (who, plane, mode, counts), and updates sync_state with the declared hash it applied. The card then reads declared ab12… · last applied ab12… by an administrator 2 minutes ago (overwrite); if the file changes afterwards the card says declaration changed since last apply. The boot seed — the one write nobody presses — is recorded the same way with actor boot and mode seed.

The archive: export and import as one zip

Code sync's Export & import tab moves every projected plane at once.

Export (GET /api/public/admin/sync/export.zip) renders each plane's database as its file, laid out as services/<file>, plus a MANIFEST.yaml naming the release, the tree hash, and per plane the declared and applied hashes at the moment of export. Extract at the repository root, review the diff, commit: the code is back in step with what the console decided.

Import (POST /api/public/admin/sync/import, the zip as the request body) writes nothing on upload. The archive is unpacked under strict rules — no absolute or parent paths, no symlinks, nothing outside services/<a directory the instance loads>/ but the manifest, every size checked as bytes are read — and staged in memory for thirty minutes. The preview at /admin/sync/import/{stage} shows each plane the archive carries as the same component the owner page renders, with its drift computed from the uploaded file rather than the one on disk; entries the instance serves from code are listed as things to commit; projected planes the archive did not carry are named. An apply from the preview is per plane and per direction, or every plane at once, and is recorded in sync_state with the uploaded declaration's hash — so the next page load, which reads the file on disk, honestly says declaration changed since last apply until that file is committed too.

Where things are

what where
the pages /admin/configuration (Platform home), /admin/sync (Code sync: tree, export & import), /admin/sync/import/{stage} (preview), and each owner page's ?tab=sync; reads for the console tier, actions for administrators
the API /api/public/admin/sync/status, …/planes/{id}/drift, …/planes/{id}/apply, …/planes/{id}/export, …/export.zip, …/import, …/import/{stage}/apply, DELETE …/import/{stage}
the component partials/components/sync-plane.hbs + js/components/sp-sync-plane.js, built by handlers/ssr/sync_plane/ for any plane from disk or from uploaded text
the inventory repositories/sync/inventory.rs — every kind the configuration page lists
the planes the registry in extensions/web/shared/admin-core/src/repositories/sync/; each plane is one impl SyncPlane plus one inventory::submit! in the crate that owns it (repositories/sync_planes/ in identity and governance)
the state table sync_state, one row per plane