Skip to content

everything-dev

A consolidated product package for building Module Federation apps with oRPC APIs.

publish-sync

268 linesSource

Publish the authored config (bos.app.ts) to the FastKV registry, sync from upstream, and upgrade workspace packages. Use when deploying, syncing, or managing runtime configuration across projects.

Config form: the authored config is bos.app.ts (canonical, preferred when both exist). A legacy bos.config.json is still supported for older children. Where this doc says bos.config.json for the local authored file, read "the authored config". The published artifact on FastKV keeps the key name bos.config.json.

everything-dev Publish & Sync

Core Workflow

plaintext
Build → Publish
  ↓        ↓
rspack  FastKV

Publish

Publish the resolved config to the configured FastKV registry path for the app account/domain:

shell
bos publish                        # Publish config only
bos publish --deploy               # Build/deploy all workspaces first, then publish
bos publish --deploy --packages local   # Build/deploy only the workspaces owned locally (recommended for CI)
bos publish --dry-run              # Preview without sending
bos publish --network testnet
bos publish --packages ui,api      # Manual subset

--packages accepts a comma-separated key list, all (default), or local. local selects only the entries whose development field starts with local:, so it auto-tracks whatever plugins or apps live in this repo without you editing workflow files when a plugin is added. CI deploy configuration should use --packages local exclusively; remote-only plugins can't be redeployed from this repo and will fail with a missing-local-path error.

The registry transaction is signed in-process via near-kit — key resolution order: explicit key → NEAR_PRIVATE_KEY / BOS_NEAR_PRIVATE_KEY env → ~/.near-credentials/<network>/<account>.json → near-cli-rs OS keychain (sign-with-keychain, interactive only). Publishes are skipped when FastKV already holds an identical config. Reads are indexed by tx signer, so the config resolves at bos://<account>/<gateway> only when signed by <account> itself — see the registry skill for the namespace=signer law and composing other runtimes (bos registry use).

After bos publish --deploy:

  1. Each workspace builds; the publish writes deterministic bundle URLs (https://<domain>/bundles/<account>/<gateway>/<workspace>/)
  2. Pipeline state (production URLs, integrity) goes to FastKV. For legacy JSON-form projects, bos.config.json is also updated locally; for the authored bos.app.ts form, nothing is written back — the authoring surface never holds pipeline state
  3. Config is published to the FastKV registry at {account}/bos/gateways/{gateway}/bos.config.json
  4. On a full deploy train (production build with upload — config-only publishes and staging skip this), the platform release manifest (ADR 0034) rides the same atomic write as a third registryEntries key at apps/<account>/<gateway>/platform.json, with its own SRI recorded in the per-deploy audit manifest. A registry failure reading the previous release skips the manifest for that deploy — a silently-wrong compat window is never published; the next deploy lands it.
  5. On the same train, the truth gate (#546) cross-checks every uploaded workspace's version manifest — which records the shared-singleton versions the build actually embedded — against the release manifest's sharedDeps. A workspace built against a stale install publishes a manifest that lies about its bytes; the gate refuses the publish (platform-truth failure) and tells you to pnpm install and redeploy.

The --network flag controls the NEAR network (mainnet by default). With --network testnet, publishes go to the NEAR testnet chain under the testnet account specified in config.

Rollback

To revert a publish, restore the previous authored config from git and re-publish:

shell
git checkout HEAD~1 -- bos.app.ts
bos publish

A config-only publish merges the freshly authored config over the currently published one — authored edits win, and the live pipeline state (bundle URLs, integrity) carries over. Removing a slot from the authored config requires bos deploy, which regenerates the payload from scratch. Legacy JSON-form children re-publish their bos.config.json as-is.

Or cherry-pick a specific version of the authored config and publish that snapshot. Prefer bos rollback --previous (or --version <height>) — it republishes a verified FastKV snapshot without touching git.

Lineage model:

  • extends is the canonical parent edge between published runtimes
  • account is the tenant namespace root for that runtime
  • domain is the public ingress for that runtime
  • a child runtime can extend a parent and still become a new tenant root on its own domain

Self-Deployed / Tenant Publishing

You don't need to wait for CI/CD to see changes in production. Publish your own config on-chain under your own NEAR account and run your own host instance, inheriting the base platform via extends.

Same gateway, own account:

BOS_GATEWAY (domain in the authored config) is the FastKV lookup key, not the DNS domain your Railway instance serves on. By keeping BOS_GATEWAY the same as the parent while using your own BOS_ACCOUNT, your config lives at a separate FastKV path (bos://<your-account>/<gateway>) that extends the base runtime. You inherit the full platform — host, API, auth, plugins — and override only what you change. Your Railway URL is the ingress.

Step-by-step:

  1. Install near-cli-rs (v0.23.5) — only needed for account creation and bos key generate; bos publish signs transactions in-process via near-kit
  2. Create a NEAR account via near-cli-rs (testnet or mainnet; named accounts can own subaccounts, implicit hex accounts cannot)
  3. bos key generate — generates a function-call key scoped to the FastKV registry contract; set the output as NEAR_PRIVATE_KEY
  4. Update bos.config.json: set account to your NEAR account, add "extends": "bos://<parent-account>/<parent-gateway>", keep domain as the parent gateway
  5. bos publish --deploy — builds workspaces, writes deterministic bundle URLs, publishes config to FastKV at bos://<your-account>/<gateway>
  6. Deploy to Railway (one-click template or railway up), set BOS_ACCOUNT, BOS_GATEWAY (same as parent), BETTER_AUTH_SECRET — the host fetches your config from FastKV and serves live

After that one-time setup, bos deploy (with RAILWAY_TOKEN + ci.railway.service) redeploys the service on every publish — a child runtime needs no image digest; railway redeploy --from-source reloads the universal image against the freshly published config.

Subaccount creation (unused by the tenant wizard today — tenants are owned by the connected sputnik-dao account) requires an explicit relayer with a named account: set siwn.relayer.{mainnet,testnet}.accountId + a private key, and point siwn.subAccount.parentAccount, siwn.recipients, and siwn.relayer.*.whitelistedContracts to your account.

Upgrade (ADR 0034)

Template updates arrive only through bos upgrade, which re-pins the child's platform field to the parent's latest release and reconciles framework-owned files at that release tag (not main HEAD). Conflicting local edits abort the run with the upstream-issue URL; --force backs them up under .bos/sync-backup/ and continues. See everything-dev#init-upgrade for the full flow.

What the upgrade reconciles from the parent template:

  • framework-owned files (build configs, workflow wrappers, db/lib plumbing — ADR 0032/0033)
  • package.json deps/scripts/workspaces inside the template's override scopes
  • the platform pin and the catalog behind it (ADR 0034)

What stays local:

  • account, testnet — your NEAR accounts
  • app.*.development — local dev URLs

Publish still merges what the parent publishes with the child's authored surface:

  • app.*.secrets — union of remote + local
  • app.*.variables — merged (local overrides remote)

Upgrade

Bump every-plugin and everything-dev across all workspaces:

shell
bos upgrade              # Check for new versions, update, then sync
bos upgrade --dry-run    # Preview without making changes
bos upgrade --check      # Manual drift alarm (ADR 0034): dry-run + exit 1 when updates are available

When a platform release manifest resolves, its catalog is the version source (one registry read — no parent-repo tarball walk). --check diffs the recorded platform pin against the latest published manifest and exits 1 when updates are available, printing what drifted. Note the pointer-slot semantics (#546): a root republish overwrites platform.json, so a stale pin only resolves through the fresh 24h .bos/platform-manifest.json cache before failing loud — bos upgrade is always the repair.

bos upgrade updates all workspace package.jsons, not just root. Also updates peerDependencies and workspaces.catalog. Correctly skips workspace:* and catalog: references.

Build

shell
bos build                # Build all packages (skips missing)
bos build ui             # Build specific package
bos build ui,api         # Build multiple
bos build --force        # Force rebuild

Config Integrity

bos.config.json entries can include integrity fields with SRI hashes:

json
{
  "app": {
    "ui": {
      "production": "https://cdn.example.com/ui/remoteEntry.js",
      "integrity": "sha384-abc123..."
    }
  }
}

These are auto-generated during bos publish --deploy and verified at runtime by the host.

Configuration

All runtime config lives in the authored config. Key sections:

  • account — NEAR mainnet account
  • testnet — NEAR testnet account
  • staging.domain — Staging domain
  • platform — the framework release pin (ADR 0034, e.g. "2.0.0-rc.14"); absent = latest via extends
  • app.host, app.ui, app.api, app.auth — Module configs with development/production URLs
  • plugins.{key} — Plugin configs with variables, secrets, routes
  • app.api.shared, app.auth.shared, plugins.{key}.shared — Module Federation shared dependency versions

extends

Config can inherit from a parent via extends:

json
{ "extends": "bos://dev.everything.near/everything.dev" }

Or per-environment:

json
{
  "extends": {
    "development": "bos://dev.everything.near/everything.dev",
    "production": "bos://dev.everything.near/everything.dev",
    "staging": "bos://staging.everything.near/everything.dev"
  }
}

Deep merge: child overrides parent. Plugins are deep-merged (set to null to remove). secrets arrays are unioned. See the extends-config skill for full details.

Registry and discovery should treat extends as runtime lineage. That keeps remix ancestry, tenant roots, and published BOS refs aligned without adding a second parent field.

For remix-host browsing with the apps plugin:

  • use parent when you want only direct children of a runtime
  • use ancestor when you want all descendants of a runtime, even when that runtime is not the lineage root
  • use root when you want the whole tree from the topmost ancestor
  • prefer querying by canonical BOS ref like bos://account/gateway, not by host URL, because shared-host descendants can reuse the same host

What bos dev writes vs bos publish writes

See everything-dev#extends-config for the full table. In short: bos dev and bos build write to .bos/bos.resolved-config.json (gitignored); bos publish --deploy, bos plugin publish, and bos upgrade (pin stamping) write to the authored config — bos.app.ts for TS-form projects, bos.config.json for legacy JSON-form children. Pipeline state (deploy-injected URLs) is written back only for JSON-form.

Troubleshooting

shell
bos info              # Show current configuration
bos status            # Check remote health

FastKV publish failures

  • NEAR RPC error: Verify the configured NEAR account has sufficient gas and the FastKV contract is deployed at {account}/bos/gateways/{gateway}/bos.config.json
  • Account mismatch: The authored config's account field must match the on-chain account that owns the FastKV path
  • Network mismatch: Ensure --network matches where the account is deployed (mainnet vs testnet)
  • Dry-run first: Use bos publish --dry-run to preview before sending

Integrity verification

During bos publish --deploy, integrity SRI hashes are auto-generated for each remote entry and stored in the published config (FastKV; also written back to bos.config.json for legacy JSON-form children). At runtime, the host:

  • Verifies integrity on first load using bounded streaming (not full-response buffering)
  • Uses stale-while-revalidate for asset requests to avoid latency spikes
  • Blocks HTML and SSR requests on integrity mismatch
  • Identifies SSR modules by both URL and ssrIntegrity hash in the cache

If a remote entry fails integrity check, the host rejects it and falls back to client-rendered output without that remote.

Federation runtime compatibility (bos mf check)

Each deployed bundle's mf-manifest.json reports metaData.pluginVersion — the version of @module-federation/runtime baked into the bundle at build time. The host is the federation consumer; each plugin is a provider loaded over mf-manifest.json and remoteEntry.js. When the host's runtime version drifts ahead of a plugin's runtime version, the plugin's remoteEntry.js throws:

plaintext
undefined is not an object (evaluating '__webpack_modules__[e].call')

The host treats this as a soft failure and continues serving with the plugin missing (⚠ N plugin(s) failed to load). Production stays live but the plugin is dead.

shell
bos mf check                          # exit 0 = all manifests compatible
bos publish --deploy --packages local # ship a fresh bundle + republish to FastKV

The check fetches <host>/mf-manifest.json and every <plugin>/mf-manifest.json, asserting:

  • metaData.pluginVersion matches the host's
  • every shared[] dep the host requires (requiredVersion: ^X.Y.Z) is provided by the plugin at a compatible version
  • UI slots agree on strict singletons (#546) — the UI composition gate (checkUiCompositionCompat) cross-checks the core-UI remote against plugin-UI remotes; a slot pair reporting different versions of a strict singleton fails with a shared singleton skew across UI slots: <name> is <slot>=<ver>, <slot>=<ver> reason. This is the mirror of the host boot guard, which raises SharedSingletonSkewError at SSR composition for the same skew.

If bos mf check fails for one plugin, redeploy only that plugin from this repo:

shell
cd plugins/<key>
bos plugin publish <key>             # rebuild + pin the workspace's production URL, then publish config

Then from the repo root: bos publish --deploy --packages local and bos mf check to confirm.

In CI, this runs as a bos mf check step on every push/PR (packages/everything-dev/src/cli.ts:mfCheck). Treat a failed run as "this bundle is stale; a plugin needs to be redeployed" — never disable.

Process issues

shell
bos kill               # Kill all tracked processes
pnpm install           # Reinstall deps
bos dev                # Restart

The tasks you will actually be given

"Ship one plugin only." bos plugin publish <key> (builds + pins the deterministic URL + publishes config), then bos mf check from the repo root to confirm federation compatibility.

"Roll back a bad publish." bos rollback --previous — republishes a verified FastKV snapshot without touching git; the manual path is git checkout <ref> -- bos.app.ts + bos publish.

"Deploy my own runtime under my own account." NEAR account + bos key generate → NEAR_PRIVATE_KEY; authored config sets your account + extends the parent and keeps domain as the parent gateway; bos publish --deploy; run the image with BOS_ACCOUNT / BOS_GATEWAY.

What comes back when it refuses

worddo
publish returns status: "error" before signingpreflight failed — key resolution (no NEAR_PRIVATE_KEY / credentials / keychain) or the registry read; run bos publish --dry-run to see the plan
config resolves to nothing at bos://<account>/<gateway>namespace = signer — the tx was signed by a different account than the config's account; sign with the publisher's key (see the registry skill)
integrity mismatch for <url>the remote entry's SRI hash diverges from the published config — redeploy that workspace and republish; the host blocks HTML/SSR on mismatch and client-renders without the remote
N plugin(s) failed to load at host startupbos mf check — a plugin's metaData.pluginVersion lags the host; republish that plugin
missing-local-path error on --packages locala selected entry has no development: local: (remote-only) — it can't be rebuilt from this repo; drop it from the selection
publish prints nothing / skipsFastKV already holds an identical config — a free no-op, nothing to fix