inmake DOCS
Docs / Modules / Where things live

Where things live

The project fences (main vs the store's cloud), custody, metering and sweepers.

A store's data may live in its organization's own Firebase project (the "space cloud", docs/space-cloud-ops.md); module code obeys these fences from the start. They are code rules, not conventions.

Concern Where Rule
Install doc, state/, outbox/, logs/, the records collections (modules/{id}/{collection}/…) store/{sid}/modules/** → the store's cloud (any ≥ 3-segment store/{sid}/… path follows the store) read / written only through Repository.* with Ref.store.id(sid).modules… paths; an unreachable cloud is a typed 503 org_unreachable in a request and is caught per store in a sweeper
Install token, module_key, session_key, inbound_key, wasm_hash, object, limits, egress the platform's secret store (admin-eur3, system/{sid}) → main the gateway trusts custody, never the install doc, for anything that decides what runs with what budget; a client editing store/{sid}/modules/{id} in their own console can change enabled, config, endpoint — all of which affect only their own module
Catalog + registry modules/{key} (Ref.modules) → main by the resolver (bindingFor's modules case) publish is /system/** and the root is not reachable through /admin/* crud; a client project can never edit a registry record — which is why custody may copy hashes and limits from it. Until 2026-10 the registry lived at store/incore/modules/{key} — incore's own install path, so incore could not install a module; the one-off POST /system/module/registry/migrate moved both envs' records in 2026-10 and was deleted on 2026-10-27 (plans/console/module-registry-path.md), and install readers keep skipping a registry-shaped doc (installsOnly) until every env is migrated
Package objects (platform and organization) modules/{platform|org/{org}}/{id}/{version}/… in the env's core bucket (mainBinding().bucket) Registry.* never writes to the store's (organization's) bucket; a store delete wipes store/{sid}/ in the org bucket and never touches modules/; the runner SA (later) reads modules/ in one core bucket per env
Module-owned elements + the runtime doc store/{sid}/elements/{moduleId}-… and elements/{moduleId}_runtime → the store's cloud, beside the store's own elements written by the mount through Repository (module_elements.ts), never through crud; API writes pass guardElementWrite. The runtime doc is what the renderers read for the module's state (public mask ELEMENT_RENDER_MASK = html, css, js, module, disabled; origin / forked / runtime stay private) — an element-doc write, so it reaches the fleet as an ordinary elements hook, shim included
Metering drop.stat rows (module_call, module_web) → BigQuery, central anything billed from stays in main — the meter of record is the stat stream, not a doc the client can edit; daily counters under state/_calls_* are advisory
module_records event; setup/hooks invalidation cases in the store write dispatcher (triggers/store_dispatch.ts) fire in main and, through the shim, in every connected cloud with no shim release; the case receives both bodies and never re-reads. Every outbox/*, state/*, logs/* write in a bound cloud is one shim invocation the dispatcher answers with nothing — acceptable at v1 volumes
Transactions and batches one project each install / update / uninstall are sequences: registry read (main) → mint + custody (admin-eur3) → hook entries, collections, kits, install doc (the store's cloud) → the registry install index modules/{key}/installs/{sid} + installs_count (main, one transaction). /admin/commit is one project by construction
Sweepers (the outbox sweep, the schedule tick, auto-updates, the GC — by hand since 2026-10-24) iterate stores inside withStore(sid) + a per-store try/catch no collection-group query across projects: update_available stamping and yank walk the registry's install index modules/{key}/installs/{sid} ({sid, version, source, installed_at}; the record keeps only installs_count), one store at a time
Private module ownership the registry record {org}~{id} with owner: {kind: 'organization', id} and visibility: {organizations: [org]} the server takes the org from store/{sid}.organization, never from the request; every store of the org sees the package
Hosting a connected module for an organization the org's own cloud, or any HTTPS host of theirs self-service (firebase deploy with expressModuleRouter and the function URL in manifest.endpoint), or deploy: 'managed' — the platform deploys mod-{id}-{sid} into the store's cloud through the shim deploy queue with the org's custodied key (variant K only) and keeps its state in state/_deploy (§8.7)
Demo custody is per core project and one Firebase project serves one environment an install on a Demo store of an org without a Demo cloud lands in drop-core-demo main, exactly like its data
Indexes + TTL firestore.indexes.json, deployed Demo first, picked up by each connected cloud on its next Re-check (/system/organization/verify) the logs.expires TTL is a field override; a module's collection.keys need no per-cloud index — _k.{name} is a single field Firestore indexes on its own in every project, so records queries work in a connected cloud the moment the install backfills them (data.indexes is deprecated); nothing at install touches a cloud's indexes
/hook/module/* legs outside the request-store loop each handler wraps its body in withStore(sid, …), so any stock / payments access follows the store instead of silently reading main
Deletion order deleteStore Modules.uninstallAll(sid) runs after the sid resolves and before the subtree delete — it reads the install docs while they exist and deletes what a recursive delete never touches: tokens, custody, the registry installs entries (all main)

Nothing here writes to the console's own database (drop-core-admin) except the menu / overview docs the SPA already owns; who may manage a module is the store's roster and the organization role, unchanged.