Upgrade v2 - #292
Merged
Merged
Upgrade v2#292
Conversation
… to dashboard - add _admin pathless layout gating on admin role; tenant admin dashboard and system pages render as children via Outlet - rename authenticated /home route to /dashboard; update sidebar, mobile tabs, user nav, and login redirect fallbacks - move apps and things routes under the public layout - changeset: ui-layout-mounts
…, remove nostr - move login from public layout into new _anon pathless layout that redirects authed users to /dashboard and provides theme toggle header - rename organization route group from /organizations to /orgs; move invitation acceptance to /orgs/invites/$id - remove stale nostr entry from authenticated sidebar - changeset: ui-anon-orgs
…n origin - Add per-account/gateway FastKV deploy lock (apps/<account>/<gateway>/lock/deploy.json) acquired before publish and released in a finally block. Stale or concurrent dispatches fail fast with status "locked" and a conflict payload listing owner, nonce, expires, and txHash. opt out with --no-deploy-lock. - bos publish holds the lock for 10 minutes by default; bos deploy holds it for 25 minutes to cover publish + Railway redeploy. Override with BOS_DEPLOY_LOCK_TTL_MS. - Add bos deploy lock inspect|release for ops visibility and stuck-lock recovery. - Add bos infra export that emits the resolved CI infra plan (env + services + account/gateway/project/generatedAt) so CI consumers stop duplicating port and DATABASE_URL knowledge from cli/infra.ts. - Update .github/workflows/deploy.yml to consume the export and drop the hardcoded API_DATABASE_URL / AUTH_DATABASE_URL / CORS_ORIGIN env block. - buildOriginMap now reads runtimeConfig.plugins[id].extendsRef and runtimeConfig.auth?.extendsRef instead of re-parsing raw bos.config.json. - Tests for the lock helpers, the CI plan builder, the resolved-config origin lookup, and the per-command TTL resolution. Co-authored-by: opencode <opencode@local>
Remove FastKV-backed deploy lock feature:
- Delete bos deploy lock acquire/release/inspect commands
- Remove lock logic from publishToFastKv
- Remove lockConflict from PublishResult and DeployResult schemas
- Concurrent deploys now follow last-write-wins semantics (harmless for Railway redeploy)
Keep bos infra export command:
- Emits {env, services, account, gateway, project, generatedAt} JSON
- buildOriginMap reads runtimeConfig.*.extendsRef (no raw JSON re-parse)
Tests: 263 pass, failures are pre-existing (auth types, template property)
Replace the legacy citynode model with three orthogonal services and a matching UI surface. API - New NodesService: hierarchical node management with a recursive CTE for subtree lookup, parent-cycle prevention on create/update, and per-node validator scoping. - New ValidatorsService (replaces CityNodesService): multi-validator per node, exact-one default enforcement via db.transaction, and resolveForStaking which descends the subtree first and walks ancestors as a fallback. resolveByAccountId looks up a single validator by its NEAR account id. - Rewritten TenantsService: createBinding, verifyCustomDomain via HTTP HEAD, and setPrimaryBinding wrapped in db.transaction. authorizedTenant helper centralizes personal vs org tenant checks. Robustness - Shared toOrpcError extracted into api/src/lib/errors.ts so route handlers stop hand-typing error maps. - isUniqueViolation(error) detects Postgres code 23505 instead of string-sniffing the message. - HOSTNAME_REGEX validates DNS hostnames independently of NEAR account ids in createBinding and the binding preflight. Schema - Drop node_tags (orphaned by the citynode removal) and regenerate migrations as a single clean snapshot (4 tables: tenants, nodes, validators, domain_bindings). The self-referencing nodes.parent_id FK uses AnyPgColumn for the typed reference. Host - BindingResolver's TenantBinding interface gained tenantId so resolve flows can tie a hostname back to a tenant record. Mock updated to keep the integration test green. UI - Public landing directory reads listValidators instead of listLegacyCityNodes. - Stake page: CityNodeCard -> ValidatorCard, query keys -> validators, resolver hooks -> resolveValidatorByAccountId. - Admin tenants wizard rebuilt around nodes / validators / bindings, and tenant header affordances reference tenant.id instead of the now-defunct subdomain field.
- Landing page queries listRootNodes, renders root-node directory cards (name, kind badge, slug) with subdomain links + skeleton/empty states - New public country page at /n/$slug aggregates children via listChildren and shows stake guidance from resolveStakingValidators (own validator CTA, or stake-to-city links for children with validators) - Stake page rewritten around resolveStakingValidators: selectable validator list with isDefault pre-selected, role/protocol badges, community secondary styling, inherited-validator banner, no-validator fallback with child links. Node resolved from ?node= param or hostname. Stake transaction + onramp flows unchanged; broken admin CRUD removed.
- Rewrite admin tenant creation page from placeholder into full wizard: inline org creation (if no active org), node details (kind, cascading parent dropdown via listRootNodes + listChildren, slug, name), tenant + binding form with auto-generated hostname and live bindingPreflight - On submit: createTenant → createNode → createBinding (blocking, with rollback via deleteNode + deleteTenant on failure), then non-blocking deploy steps for NEAR subaccount and registry config publish - Export StepList + useStepper from @/components barrel - Extend api/tests/setup.ts: optional plugins map param on getPluginClient, role param on orgContext (enables requireOrgRole in integration tests) - Add api/tests/integration/wizard.test.ts: 5 tests covering full chain, rollback on duplicate hostname, nested hierarchy, preflight availability
…Nodes product - Landing: 'What are CityNodes?' hero + root-node directory + Apply button - New /apply route (external redirect to citynode.app/apply) - Remove apps browser cruft (4 routes deleted, apps tab stripped from profile) - Things index → typed DataTable demo - Mobile responsive fixes (auth-shell double-padding, simple-header overflow) - README + skill.md rewritten for CityNodes product surface
Update bos.config.json repository to point to NEARBuilders/citynode.app. Align docs (AGENTS.md, CONTRIBUTING.md, LLM.txt, host/README.md) with the remote auth plugin architecture and PostgreSQL migration. Clean up CI postgres-template service and .env.example for new plugin databases. Remove docker-compose.yml in favor of the Railway-backed deploy flow.
…ix contradictions - Move 5 beta-v2 docs into plans/beta-v2/ (overview, composable, ui, tenants, native) - Move extension plans into plans/extensions/ (ui-extends-ui-federation, client-runtime-plugins) - Move infra plans into plans/infra/ (toml-infra-alchemy, orpc-v2-effect-migration) - Move offline plans into plans/offline/ (shell-sw-caching, data-sync-queue) - Move v1 plan into plans/v1-current/ (tenant-feature-completeness) - Fetch full prototype source (85 files) from prototype/route-merging branch - Consolidate prototypes under plans/prototypes/beta-v2/ and beta-v2-override/ - Fold react-native-migration.md into beta-v2/native.md, delete original - Fix TOML/JSON publishing contradiction in composable.md - Mark wayfinder tickets 01, 02 as RESOLVED; 06 as PARTIALLY RESOLVED - Rewrite plans/README.md with new directory structure and ticket status table - Update ~45 cross-references across all plan docs - Add .gitignore exceptions for prototype source files (*.css, *.gen.ts)
…from source, drop postinstall - Commit docker-compose.yml (was gitignored and generated after preflight, creating a chicken-and-egg where bun run dev exited before the file was written). Provisions postgres-api (5432/api_db) and postgres-auth (5433/auth_db); plugins isolate via plugin_<pluginId> schemas. - Add paths to packages/everything-dev/tsconfig.json mapping every-plugin and subpath exports to source so bun's runtime resolver finds them without pre-built dist. - Remove postinstall: bun run types:gen (dead code under ignore-scripts). - Update AGENTS.md Quick Reference with docker compose step and plugin schema isolation docs.
- Add optional to BosStagingSchema, use staging.account in publishToFastKv when --env staging (enables testnet account switching) - Add staging block to bos.config.json: v1.citynode.testnet on testnet.citynode.app - Deploy workflow: trigger from ci-main-success (not release-completed), exclude host via --packages, remove Postgres services and infra plan - Staging workflow: use NEAR_TESTNET_PRIVATE_KEY, exclude host - Release workflow: manual-only (remove ci-main-success trigger, remove docker and notify-deploy jobs) - Docker workflow: remove push triggers on main/staging - Update workflow README to document downstream flow
#198) * feat(auth): event-linked Onboarding Codes with Organizers and a station read Onboarding Codes now carry the Node Event id, keep the event name as a display snapshot, and store the raw code encrypted with a key derived from the auth secret. Codes for the same event feed one Event Team; team names no longer take part in lookup. Owners, admins and members of a Team granted the events Feature Area can create, list and revoke codes, and getOnboardingStation returns the decrypted code of an active code to them. Redemption sets the active organization without touching the Active Team, and fails with an "organization is full" message at the membership limit, which citynode sets to 1000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(api): create onboarding codes for a Node Event createEventOnboardingCode loads the event, refuses non-event activities, nodes whose tenant has no organization and organizations other than the caller's active one, then creates the code through the auth plugin in-process with the caller's headers and a default expiry of event end + 48h. Adds the events Feature Area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): start onboarding from an event and run a fullscreen station Event rows in the activity editor get "Start onboarding", which opens a fullscreen station with a large QR on the Gateway Origin, a live joined count and recent joiners. The org Onboard tab drops the free-text event form, shows each code's state and reopens the station for active codes, and is visible to Organizers. The onboarding page explains used-up codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(regression): onboarding page states for valid, expired, revoked and used-up codes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: address review findings on event onboarding Fail createEventOnboardingCode with the contract's typed errors and a typed failure for the event lookup, derive the public code flags from one code state, and let an Organizer set the max joins when starting onboarding from an event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): upcoming and past event timeline in the activity editor Events on the node content page are split into Upcoming and Past tabs and grouped by the viewer's local day, with a sticky date header, a subtle timeline rail, and cards showing start time (plus event-local time when the offset differs), organizer, venue, status and actions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): exit the onboarding station back to where it was opened Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(api): typecheck event onboarding route in scaffolded child projects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… a strict MF singleton (#189) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion.
…iene (#197) * refactor(everything-dev): static docker-compose — drop infra auto-provisioning Closes #180 * refactor(ui): brand assets move to assets/brands/near with light/dark naming Closes #181 * refactor(ui): non-shadcn components out of components/ui components/ui is pure shadcn primitives now; app components live in components/ (app-detail family in components/app-detail/). Closes #182 * feat(everything-dev): init prunes unreferenced ui sources for ui-override children Closes #183 * style: biome organize imports + format * chore(lint): remove dead code flagged by biome Deletes unused imports (schema.ts, build.ts), the unused pluginDisplayName function, and flattenParent's never-read seen param; auth-login gets the optional chain.
* add claude to gitignore * chore(lint): register @shadcn/lint oxlint plugin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): adopt shadcn base-maia preset (b3ZN5L2h44) on Base UI Reinstall all ui primitives from the base-maia registry (Base UI, Phosphor icons), self-host Inter/Geist/Geist Mono, adopt the preset radius scale, and keep the local Badge success/warning variants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): citynode design tokens on oklch (ink primary, brand accent, status set) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(ui): CityNode design system guide Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): migrate admin routes to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): restore __root.tsx sync header stripped by icon codemod Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(auth-ui): move plugin primitives to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(auth-ui): components.json so shadcn lint resolves the host theme Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): migrate public routes to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): migrate authenticated dashboard routes to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): migrate org and tenant routes to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): migrate shared components to Base UI + Phosphor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): Button rendered as a link keeps link semantics Base UI adds role=button when nativeButton is false; render non-button elements through useRender with the button styles instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(ui): drop Radix and lucide, keep link content inside anchors Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): larger controls and calmer page primitives Controls default to 44px (sm 36, lg 48); PageContainer, PageHeader, SectionHeader, EmptyState and InfoRow follow ui/DESIGN.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(ui): remove hover scale on header avatar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): add Switch and InputGroup primitives (base-maia) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): input-group lint suppressions and 44px height Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): clear shadcn lint in admin routes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(auth-ui): clear shadcn lint, sync primitives with core ui Sync plugin Button/Input/InfoRow/EmptyState with core ui (44px controls, ButtonLink). Replace arbitrary radii/text sizes, retro outset borders and uppercase micro-labels with scale values and semantic tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): public routes and root on design tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): stake route on design tokens and primitives Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): dashboard, nodes and onboarding drop retro styling Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): things routes on design tokens and primitives Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): clear shadcn lint in org, tenant and staking POC routes Remove retro borders, hover shadows and uppercase micro-labels; use scale values, semantic tokens and component variants. Native selects keep their test ids and now match the Input primitive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): use shadcn's cn package instead of clsx + tailwind-merge Primitives import cn from "cn" as the registry ships them; @/lib/utils re-exports it for app code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(ui): document restyle allowances Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(auth-ui): native controls to design-system primitives Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): native controls to primitives in node selector, stake, things, onboarding Managed-node select becomes Select, validator list becomes a RadioGroup of choice cards, things/new uses Field, stray buttons become Button, and ids, slugs and JSON payload inputs get font-mono. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): admin routes use design-system form primitives Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): org, tenant and staking POC controls on shadcn primitives Replace native selects, buttons and the validator table with Select, Button and Table; wrap form inputs in Field/FieldLabel; restore font-mono on inputs holding account ids, URLs and hashes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(ui): shared components pass shadcn lint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): shared components use design-system primitives for native controls Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): Item primitive; explorer rows use Item instead of a native button Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(orgs): keep org page state when leaving the active team Make the org page tab URL-addressable (validated ?tab= search param, default members, replace navigation) so tab state no longer lives in component state that a client re-render discards. Replace the native team add-member select with the Base UI Select and clear shadcn lint in the teams tab and team card. The regression spec opens the Teams tab via ?tab=teams instead of racing a click against hydration, and asserts the tab survives removing yourself from the active team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(lint): enforce @shadcn/lint rules on the UI no-restyle (layout only; containers may add spacing, inputs/badges may use font-mono), no-raw-colors, no-arbitrary-values and no-unknown-classes as errors; no-inline-styles and require-static-classes as warnings. Vendored primitives in components/ui are exempt from restyle, arbitrary-value and unknown-class checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(discover): Manage button hover matches its size The button stretched to fill its grid cell; align the button itself instead of its content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): hydration-safe LocalDate Dates format only on the client so SSR (UTC) and the browser's timezone never disagree during hydration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): replace Built on NEAR badge with a plain footer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /apply as a 4-step stepper Organization, NEAR account, DAO and Details as one guided flow: completed steps collapse to a check row, the next step is the only open one, and the success state links to Proposals. ConnectDao becomes a single row with purpose-specific copy (optional purpose/variant props; onVerified unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /things/$thingId Status badge, payload and details sections, admin delete in a confirmed danger zone, proper not-found state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /things/live Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /things Searchable rows with type, upvotes and relative update time; empty and error states with a next action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /things/new Inline JSON validation and format; only offer sign-in when the API actually rejects the session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild admin layout Drop the in-page tenant header, stat strip and AdminNav button bar (the sidebar carries admin sub-nav); keep a one-line relayer funding notice and shared admin row, stat and menu helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin overview Big figures (pending, nodes, tenants, relayer), a review work queue, manage rows with the existing admin.heading.* ids and a quiet runtime context list carrying the admin.stat.* ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild landing page and community directory Hero with one sentence and two actions, live community counts, a directory preview that opens public community pages, and a three-step how it works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /login and ship the auth plugin stylesheet Sign in to CityNode with passkey primary, NEAR secondary, phone tertiary and passkey sign-up; phone pairing as a focused QR view. The plugin styles.css is now imported from the route root and layered under the core utilities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild Home as next steps Home (/dashboard) now leads with state-driven next steps (invitations to accept inline, create/choose an organization, start or open a community, community settings for owners, admin queue, stake) and a compact identity card linking to Settings, replacing the identity dump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /n/$slug community page Header with kind, location and summary, a same-origin Stake NEAR action, key stats, upcoming events, local communities as cards, staking pools, and a not-found state that leads back to Explore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/proposals list and review List: labelled status tabs, count, proposal titles from the payload, relative dates, stacked rows on mobile. Review: a focused decision page with the application first, a sticky decision panel (ConnectDao-gated approve, reject behind a reason dialog), outcome state and a compact recent-decisions list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /explore Search first, then filters and a list/map switch (?view=map). Communities are cards with location, status and next event; the preview sheet leads with Open community and lists events as dated rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /orgs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /orgs/new Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /orgs/invites/$id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /orgs/$slug with row menus, inline invite and URL tabs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(ui): split node lifecycle POC logic into -poc-* modules Queries, model and signed actions move verbatim into usePocLifecycle; the pre-sign chain checks and the step runner become factories. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /activity/$activityId event page Date tile and title up front, date/time and venue as a quiet list, one outbound action (Register on Luma / Event details / Read original post), a Hosted by link to the community page, and not-found and error states. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /$accountId profile Avatar, name and account id as the header, description and links as outline buttons, and a clean No profile yet state with Explore and NearBlocks actions for accounts without a NEAR profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/nodes list and detail List: page header, scope tabs, kind filter and name/slug search, tenant status badges, stacked rows on mobile. Detail: header with parent link and Edit details, big counts, URL tabs (overview, validators, domains, profile); validators and domains as rows with confirmed row-menu actions; network/protocol behind a disclosure; raw metadata collapsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild the app shell around task-first navigation One shell for signed-in users on every page (public pages and settings included), sidebar ordered Home, Explore, Stake, My community (with Overview, Events & profile, Onboarding, Proposals, Community settings), Organization, Things, Curate (curators/admins), Admin (with sections). Settings, Docs and the theme toggle live in the sidebar footer; the account menu is identity, profile, Settings, sign out. Named breadcrumbs replace raw path segments. Anonymous header: logo, Explore, Stake, Docs, Sign in. Error and not-found pages offer a way home. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /about as a docs page Docs header with Open agent skill as the one primary action, the README as readable prose, and a Build on it side column with skill, skill.md, repository and runtime details. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /skill as a docs page Copy prompt as the primary action, skill.md secondary, the Intent load command as a copyable line, then the prompt rendered as prose. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): simplify dashboard layout and replace the under-construction GIF The dashboard layout now only guards feature areas (the authenticated layout owns the shell). The under-construction badge becomes a quiet dashed tile. The navigation progress bar uses the brand color, and the theme-color meta follows the color scheme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ui): cover explore cards and list/map view switch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /onboard as a focused 3-step join Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /login/device and device approval Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /settings as a left-nav settings layout Profile, sign-in methods, API keys and security as rows with dialogs and confirmed destructive actions; API keys created and revealed in dialogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild the onboarding station Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /tenant/$tenantId as Community settings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): community-settings DAO copy on the org Community tab Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): check curator access once per session in the sidebar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/tenants list Page header with Create tenant, status tabs and search, success/warning status badges, local dates, stacked rows on mobile, filtered-empty state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/tenants/new as a stepper Four steps (organization, owning DAO, community, review) with completed steps collapsed to a check row and Change actions; the deploy phase is a three-step progress (records, DAO publish, Trezu approval) ending in an Open community settings action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): chips use the new brand and muted tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild node lifecycle POC as a guided walkthrough Phase stepper, one station at a time with a single primary action, acting lens as a segmented control, collapsible setup, chain state in tabs and the chain log in a side sheet. All poc-* test ids, mutations and on-chain calls unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(auth-ui): compile the plugin stylesheet with Tailwind and load it per route The folder-form ui build runs from the plugin root, so ui/postcss.config.mjs was never picked up and styles.css shipped raw @tailwind directives. Add a root postcss config, reference the default theme inline so spacing/size utilities resolve, and import the sheet from the route layouts (async CSS) instead of the root, which collided with the entry style.css. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /stake as directory, pool and amount Community cards lead to a two-column stake page: pick a pool (with live fee and total staked), enter an amount with quick picks, and connect a wallet inline. Protocol jargon removed; not-found and no-validator states give a way forward. Pool cards show big figures; the public node stake section links to same-origin /stake?nodeId=. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/relayer Status page: header with refresh, status badge, big balance figures, funding prompt with the account when empty, an Add funds form with an amount + NEAR input group and presets, recent relays as rows with relative times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(auth-ui): cover the login methods, passkey sign-up and onboard join steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild /admin/system Status page with env/network badges in the header and runtime, deployment and endpoint sections as quiet rows (admin.heading.* kept). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): round relayer NEAR figures on admin pages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(admin): remove AdminNav and StatCard (replaced by sidebar sub-nav) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild My community overview and proposals My community gets a named header with the community site link, a URL tab nav (Overview, Events & profile, Onboarding, Proposals, Community settings for owners), big stats, a Coming up list with onboarding entry, team stake as a figure, validators as rows, and sub-communities as rows. Proposals are rows with status badges, relative dates, details in a sheet, approve as the one primary action and reject behind a row menu with confirmation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(orgs): confirm team deletion in-app instead of window.confirm ConfirmDialog gets sentence-case labels, a clear primary/ghost pair and test ids; the team-workspace spec confirms through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild Events & profile with an Onboarding tab /nodes/$nodeId/content validates ?tab=events|profile|onboarding and drives its tabs from it (replace navigation). It shares the My community tab nav; Events is an action row (Add event, Share a post, Luma behind a dialog), Upcoming/Past line tabs and posts as rows, with edit in a grouped sheet and Start onboarding in a row menu. Profile is a sectioned form with the Explore switch first. The new Onboarding tab lists live stations (open, close with confirmation) and upcoming events with Start onboarding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): let long account ids wrap on the profile header Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): one primary stake action on the community page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): rebuild Curate /discover becomes Curate: big stats, line tabs, communities as rows with Featured / Needs attention badges and a Manage sheet (visitor checklist, compact feature form, stop featuring behind a confirm, recent changes). Reports are rows with the reason and age; resolving happens in a dialog with a keep/hide choice. Team lists curators as people (avatar, name, muted id) with a people search + inline Add, and Remove access lives in a row menu with confirmation. DiscoveryAction buttons size to their label and report success with a toast; Report a problem opens a dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): format activity card times on the client only Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): left-align settings nav and hide temporary emails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): breadcrumb names the Events & profile tab you're on Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): consistent page titles from the runtime app name Replace leftover "| app" titles with pageTitle(label, runtimeConfig). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(admin): tell non-admins why they landed on Home Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: changeset for the CityNode design system rebuild Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(stake): browse staking pools without signing in Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dao): show a readable message when Trezu can't be reached connectDaoAccount stored the raw connector error ("Wallet not found"); route it through describeDaoError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): event date headings stick below the app header Sticky date groups used top-0/z-10 inside the app shell's scroller and slid over the sticky header. The shell sets --sticky-offset; headings stick at top-sticky-offset beneath it, and the stuck observer uses the same offset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): theme toggle moves from the sidebar to the top bar Sits just left of the account menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ui): core stylesheet scans the auth plugin's sources Plugin-only responsive classes (settings layout) lived only in the plugin's utilities.auth sublayer, where they lose to core utilities. Generating them in the core sheet keeps a single, correctly ordered utilities layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): sticky, polished public header with a wordmark logo The header pins to the top with a blurred translucent background, the current section gets an active state, and the logo drops its icon. Public pages set their own --sticky-offset so sticky content sits below. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): improve and fix crashes * fix(ui): always render the landing page — drop signed-in redirect from / * refactor(ui): address review — restore NEAR branding, move Chip to ui, drop dead app-detail components --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Elliot Braem <elliot@ejlbraem.com>
…i/tenant subpath (#221) * feat(everything-dev,ui): tenant draft/url helpers as everything-dev/ui/tenant subpath Closes #184. The tenant origin construction (buildTenantUrl, tenantLabel, isLocalHostname), the node-config draft helpers (schema, diff, bundle entry resolution, sha384 integrity preflight), and the new gatewayForAccount merge into one framework module exposed as everything-dev/ui/tenant. The app-owned ui/src/lib/tenant-url.ts and tenant-config-draft.ts copies are deleted; call sites import from the package, so children stop receiving the copies via bos init and versions flow through the catalog / changeset release. gatewayForAccount derives the gateway for an owner account from the runtime config — the runtime's gateway when the account is on the runtime's network, null otherwise — replacing the hardcoded per-network domain checks the PR 177 review flagged in fresh's spawn flow. * fix(everything-dev): keep the ui dist free of node builtins — external react + dead createRequire strip The everything-dev dist's shared rolldown runtime chunk retained a dead `import { createRequire } from "node:module" ` (injected for CLI-side interop, unused after treeshake) — and the new ui-surface dist entries (hydrate/router/tenant) import that chunk, so client builds of the ui subpaths failed with "node:* is a built-in Node.js module and cannot be imported in client-side code". react/react-dom join neverBundle (they are MF shared singletons; bundling them dragged CJS-interop helpers into the dist), and the build strips the dead import from the shared chunk when it is genuinely unused. * refactor(ui,everything-dev): tenant surface through @/app; createRequire conversions at source Review rework (PR 221): app call sites import the tenant surface from @/app (app.ts re-exports everything-dev/ui/tenant) instead of reaching past the app surface; dao-policy/dao-connect's TenantUiOverride re-export chain is cut. The dist's dead createRequire import is eliminated at source — banner.ts reads package.json via readFileSync(new URL(...)) and init.ts statically imports tar (committed dep) instead of a dynamic require — so the rolldown runtime chunk never receives node:module and the emitted-chunk stripper is deleted. * fix(everything-dev): tar ships an ESM build — named extract import * style: biome import-name sort in the ported tenant-url call sites
* feat(everything-dev,ui): tenant draft/url helpers as everything-dev/ui/tenant subpath Closes #184. The tenant origin construction (buildTenantUrl, tenantLabel, isLocalHostname), the node-config draft helpers (schema, diff, bundle entry resolution, sha384 integrity preflight), and the new gatewayForAccount merge into one framework module exposed as everything-dev/ui/tenant. The app-owned ui/src/lib/tenant-url.ts and tenant-config-draft.ts copies are deleted; call sites import from the package, so children stop receiving the copies via bos init and versions flow through the catalog / changeset release. gatewayForAccount derives the gateway for an owner account from the runtime config — the runtime's gateway when the account is on the runtime's network, null otherwise — replacing the hardcoded per-network domain checks the PR 177 review flagged in fresh's spawn flow. * fix(everything-dev): keep the ui dist free of node builtins — external react + dead createRequire strip The everything-dev dist's shared rolldown runtime chunk retained a dead `import { createRequire } from "node:module" ` (injected for CLI-side interop, unused after treeshake) — and the new ui-surface dist entries (hydrate/router/tenant) import that chunk, so client builds of the ui subpaths failed with "node:* is a built-in Node.js module and cannot be imported in client-side code". react/react-dom join neverBundle (they are MF shared singletons; bundling them dragged CJS-interop helpers into the dist), and the build strips the dead import from the shared chunk when it is genuinely unused. * feat(ui): read stake-pool contracts through near-kit view calls Closes #185. The hand-rolled JSON-RPC view-function wrapper (raw fetch against the public RPC endpoints, base64 args, manual Zod byte-array decoding — near-rpc.ts) is deleted. The stake-pool query call sites use near-kit's Near.view per network instead, with failure parity: unsupported networks, timeouts, and malformed results resolve null and fall into the same clean query error state. Test mocks move to the near-kit boundary. * style(ui): biome format stake-pool test mock chain * refactor(ui,everything-dev): tenant surface through @/app; createRequire conversions at source Review rework (PR 221): app call sites import the tenant surface from @/app (app.ts re-exports everything-dev/ui/tenant) instead of reaching past the app surface; dao-policy/dao-connect's TenantUiOverride re-export chain is cut. The dist's dead createRequire import is eliminated at source — banner.ts reads package.json via readFileSync(new URL(...)) and init.ts statically imports tar (committed dep) instead of a dynamic require — so the rolldown runtime chunk never receives node:module and the emitted-chunk stripper is deleted. * fix(everything-dev): tar ships an ESM build — named extract import * refactor(ui,better-near-auth): per-network near clients through the auth client Review rework (PR 222): better-near-auth's near atom gains getNearClient(network?) — a network whose connector-bound client isn't initialized gets a wallet-less Near cached on demand (public read-only view calls need no wallet; initClientForNetwork upgrades the slot when that network initializes). The stake-pool factories take the app auth client and read contracts through authClient.near.getNearClient(network); the module-local viewClients Map is deleted; pool networks narrow through toNetwork. Timeouts follow the transport defaults (near-kit view has no AbortSignal option — parity on failure-to-null holds). * style: biome organize imports * style: biome import-name sort in the ported tenant-url call sites * fix(ui): PoolSummary reads pools through the near-kit view client — toNetwork narrowing + authClient
…v/ui subpaths (#224) * feat(everything-dev,ui): tenant draft/url helpers as everything-dev/ui/tenant subpath Closes #184. The tenant origin construction (buildTenantUrl, tenantLabel, isLocalHostname), the node-config draft helpers (schema, diff, bundle entry resolution, sha384 integrity preflight), and the new gatewayForAccount merge into one framework module exposed as everything-dev/ui/tenant. The app-owned ui/src/lib/tenant-url.ts and tenant-config-draft.ts copies are deleted; call sites import from the package, so children stop receiving the copies via bos init and versions flow through the catalog / changeset release. gatewayForAccount derives the gateway for an owner account from the runtime config — the runtime's gateway when the account is on the runtime's network, null otherwise — replacing the hardcoded per-network domain checks the PR 177 review flagged in fresh's spawn flow. * fix(everything-dev): keep the ui dist free of node builtins — external react + dead createRequire strip The everything-dev dist's shared rolldown runtime chunk retained a dead `import { createRequire } from "node:module" ` (injected for CLI-side interop, unused after treeshake) — and the new ui-surface dist entries (hydrate/router/tenant) import that chunk, so client builds of the ui subpaths failed with "node:* is a built-in Node.js module and cannot be imported in client-side code". react/react-dom join neverBundle (they are MF shared singletons; bundling them dragged CJS-interop helpers into the dist), and the build strips the dead import from the shared chunk when it is genuinely unused. * feat(ui): read stake-pool contracts through near-kit view calls Closes #185. The hand-rolled JSON-RPC view-function wrapper (raw fetch against the public RPC endpoints, base64 args, manual Zod byte-array decoding — near-rpc.ts) is deleted. The stake-pool query call sites use near-kit's Near.view per network instead, with failure parity: unsupported networks, timeouts, and malformed results resolve null and fall into the same clean query error state. Test mocks move to the near-kit boundary. * style(ui): biome format stake-pool test mock chain * feat(everything-dev,ui): absorb framework UI files into everything-dev/ui subpaths Closes #186. hydrate (client bootstrap), router-client (client router factory), router-server (SSR router module), entry (web entry runner), and router-error (generic error boundary) move into the framework package as new ui subpaths. Child copies shrink to thin wiring stubs that inject only the app's generated artifacts (routeTree.gen, routeConfig.gen, styles.css) and stay framework-owned for sync. The framework modules import the package's own api/auth/runtime/manifest surfaces; the hydrate suite ports to the package keeping the compose digest-parity fallback coverage. globals.d.ts remains a sync-owned copy — ambient declarations cannot be re-exported through stubs. RouterContextWithApi gains the optional authClient the routers already threaded. * refactor(ui,everything-dev): tenant surface through @/app; createRequire conversions at source Review rework (PR 221): app call sites import the tenant surface from @/app (app.ts re-exports everything-dev/ui/tenant) instead of reaching past the app surface; dao-policy/dao-connect's TenantUiOverride re-export chain is cut. The dist's dead createRequire import is eliminated at source — banner.ts reads package.json via readFileSync(new URL(...)) and init.ts statically imports tar (committed dep) instead of a dynamic require — so the rolldown runtime chunk never receives node:module and the emitted-chunk stripper is deleted. * fix(everything-dev): tar ships an ESM build — named extract import * refactor(ui,better-near-auth): per-network near clients through the auth client Review rework (PR 222): better-near-auth's near atom gains getNearClient(network?) — a network whose connector-bound client isn't initialized gets a wallet-less Near cached on demand (public read-only view calls need no wallet; initClientForNetwork upgrades the slot when that network initializes). The stake-pool factories take the app auth client and read contracts through authClient.near.getNearClient(network); the module-local viewClients Map is deleted; pool networks narrow through toNetwork. Timeouts follow the transport defaults (near-kit view has no AbortSignal option — parity on failure-to-null holds). * style: biome organize imports * refactor(everything-dev): SSR router goes generic over the app route tree — shared router defaults Review rework (PR 224): createServerRouterModule is generic over TRouteTree extends AnyRoute — the app stub passes typeof routeTree, the composed-tree casts narrow through the app's tree type, and the 'as unknown as AnyRouter' boundary cast is gone (a concrete tree satisfies the contract directly). The unused errorComponent override param is dropped (RouterError is the framework-owned boundary). The fallback components and QueryClient defaults dedupe into ui/router-defaults, shared by the client factory, the SSR module, and the hydrator. * style: biome import-name sort in the ported tenant-url call sites * fix(ui): PoolSummary reads pools through the near-kit view client — toNetwork narrowing + authClient * style: biome format the framework router-error
…011) (#228) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion. * docs: clean up plan archives, wayfinder tickets, and phantom references - delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced) - plans/README.md: drop phantom ui-route-grafting-migration file link (kept as plain text + issue #108), prototype count 3→4, fix 03-plugin-type-deps typo - recreate wayfinder decision tickets 08–12 as decision-record stubs (decisions already recorded in README/map) and refresh ticket counts - ADR 0007: replace dead ui-route-grafting-migration links with inline supersession note (ADR 0008); fix stale plan-035 link in ADR 0007 platform-bundle-storage; point ADR 0006 at the archived plan 022 - advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029 and 028 collisions), add README rows for 044–048 (DONE via PR #136) and 030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to done/ with a slim done/README.md preserving status notes, prune dependency notes that only concern archived plans * docs: fix plans/README.md staging miss from the cleanup commit * fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011) A self-contained runtime image fetched its own plugin manifests through its own public origin at boot; when that origin 502'd (Railway restart loop, gateway hiccup, cold start ordering) nothing could ever come up — the boot depended on the thing it was booting. Registry claims survived container restarts via PID reuse and wedged the pinned host port, making the loop permanent. - BundleResolver service (Effect v4, Context.Service + Layer) resolves own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR; foreign namespaces and unset dirs fall through to the network fetch, so registry-tier children are unchanged - One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot covers every boot-time consumer: config manifest discovery, contract types, orchestrator host loading, MF remoteEntry probes - bos start resolves configs with production env when NODE_ENV=production (kills the dev-mode raw-config load against the stripped image) - Remote-source services no longer allocate ports; stale claims are pruned via recorded process generation; pinned-port conflicts verify a real listener; InfraError/DevStepError report instead of escaping - bos.config.json carries the deterministic post-publish bundle URLs - Dockerfile healthcheck start-periods cover cold-boot duration Verified: runtime image boots healthy with citynode.app blackholed and recovers across docker restart; typecheck 9/9, lint clean, 631 framework tests green. * ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4) runtime-remote.test.ts is a live-network smoke: it reads the branch's raw bos.config.json and proxies real traffic (/skill.md, /llms.txt) to config.app.ui.production. Once #228 commits the deterministic bundle URLs, that origin is the deployment this PR ships — a PR CI suite that depends on production's uptime fails whenever the gateway blips, and before the deploy it validates URLs that cannot serve yet (Host tests red: "expected 502 to be 200", 60s hook timeouts). The split was already prepared but never wired: vitest.remote.config.ts owns exactly this file and host has a test:integration:remote script with no consumer, while the default vitest.config.ts include swept it into PR CI. - Default host suite excludes tests/integration/runtime-remote.test.ts (configDefaults preserved) — PR CI no longer touches live origins - deploy.yml runs the smoke after mf check: boots the real host program against the live production URLs — SSR compose from the deployed ui, doc-asset proxy, api ping, root document contract — the functional gate beyond mf check's identity-only check No helper changes, deliberately: in the Deploy context, non-2xx origins make the smoke fail loudly — correct for a post-deploy gate; a down site is already caught by mf check's retry loop. * fix(host-tests): pin the runtime-config fixture to development env loadTestRuntimeConfig called loadResolvedConfig() with no env — with the NODE_ENV-honoring default (hermetic boot), the host test script's NODE_ENV=production resolved production env in the fixture: api source remote → manifest discovery fetched the deployed origin (down, 10s×3 retries) → every beforeAll hook blew the 10s timeout → 6 suites failed at collection (csp-compose, csp-nonce, security, seo, ssr-metadata, ssr). Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the host test script exists for the host's production code paths, not for config resolution. Pin env: "development" — live-origin coverage belongs to the Deploy workflow's smoke (relocated earlier in this PR).
…listener authority) (#229) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion. * docs: clean up plan archives, wayfinder tickets, and phantom references - delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced) - plans/README.md: drop phantom ui-route-grafting-migration file link (kept as plain text + issue #108), prototype count 3→4, fix 03-plugin-type-deps typo - recreate wayfinder decision tickets 08–12 as decision-record stubs (decisions already recorded in README/map) and refresh ticket counts - ADR 0007: replace dead ui-route-grafting-migration links with inline supersession note (ADR 0008); fix stale plan-035 link in ADR 0007 platform-bundle-storage; point ADR 0006 at the archived plan 022 - advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029 and 028 collisions), add README rows for 044–048 (DONE via PR #136) and 030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to done/ with a slim done/README.md preserving status notes, prune dependency notes that only concern archived plans * docs: fix plans/README.md staging miss from the cleanup commit * fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011) A self-contained runtime image fetched its own plugin manifests through its own public origin at boot; when that origin 502'd (Railway restart loop, gateway hiccup, cold start ordering) nothing could ever come up — the boot depended on the thing it was booting. Registry claims survived container restarts via PID reuse and wedged the pinned host port, making the loop permanent. - BundleResolver service (Effect v4, Context.Service + Layer) resolves own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR; foreign namespaces and unset dirs fall through to the network fetch, so registry-tier children are unchanged - One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot covers every boot-time consumer: config manifest discovery, contract types, orchestrator host loading, MF remoteEntry probes - bos start resolves configs with production env when NODE_ENV=production (kills the dev-mode raw-config load against the stripped image) - Remote-source services no longer allocate ports; stale claims are pruned via recorded process generation; pinned-port conflicts verify a real listener; InfraError/DevStepError report instead of escaping - bos.config.json carries the deterministic post-publish bundle URLs - Dockerfile healthcheck start-periods cover cold-boot duration Verified: runtime image boots healthy with citynode.app blackholed and recovers across docker restart; typecheck 9/9, lint clean, 631 framework tests green. * docs(adr): amend 0011 (outbound local-first) and 0012 (claim identity, listener authority) * ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4) runtime-remote.test.ts is a live-network smoke: it reads the branch's raw bos.config.json and proxies real traffic (/skill.md, /llms.txt) to config.app.ui.production. Once #228 commits the deterministic bundle URLs, that origin is the deployment this PR ships — a PR CI suite that depends on production's uptime fails whenever the gateway blips, and before the deploy it validates URLs that cannot serve yet (Host tests red: "expected 502 to be 200", 60s hook timeouts). The split was already prepared but never wired: vitest.remote.config.ts owns exactly this file and host has a test:integration:remote script with no consumer, while the default vitest.config.ts include swept it into PR CI. - Default host suite excludes tests/integration/runtime-remote.test.ts (configDefaults preserved) — PR CI no longer touches live origins - deploy.yml runs the smoke after mf check: boots the real host program against the live production URLs — SSR compose from the deployed ui, doc-asset proxy, api ping, root document contract — the functional gate beyond mf check's identity-only check No helper changes, deliberately: in the Deploy context, non-2xx origins make the smoke fail loudly — correct for a post-deploy gate; a down site is already caught by mf check's retry loop. * fix(host-tests): pin the runtime-config fixture to development env loadTestRuntimeConfig called loadResolvedConfig() with no env — with the NODE_ENV-honoring default (hermetic boot), the host test script's NODE_ENV=production resolved production env in the fixture: api source remote → manifest discovery fetched the deployed origin (down, 10s×3 retries) → every beforeAll hook blew the 10s timeout → 6 suites failed at collection (csp-compose, csp-nonce, security, seo, ssr-metadata, ssr). Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the host test script exists for the host's production code paths, not for config resolution. Pin env: "development" — live-origin coverage belongs to the Deploy workflow's smoke (relocated earlier in this PR).
…ct service (#230) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion. * docs: clean up plan archives, wayfinder tickets, and phantom references - delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced) - plans/README.md: drop phantom ui-route-grafting-migration file link (kept as plain text + issue #108), prototype count 3→4, fix 03-plugin-type-deps typo - recreate wayfinder decision tickets 08–12 as decision-record stubs (decisions already recorded in README/map) and refresh ticket counts - ADR 0007: replace dead ui-route-grafting-migration links with inline supersession note (ADR 0008); fix stale plan-035 link in ADR 0007 platform-bundle-storage; point ADR 0006 at the archived plan 022 - advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029 and 028 collisions), add README rows for 044–048 (DONE via PR #136) and 030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to done/ with a slim done/README.md preserving status notes, prune dependency notes that only concern archived plans * docs: fix plans/README.md staging miss from the cleanup commit * fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011) A self-contained runtime image fetched its own plugin manifests through its own public origin at boot; when that origin 502'd (Railway restart loop, gateway hiccup, cold start ordering) nothing could ever come up — the boot depended on the thing it was booting. Registry claims survived container restarts via PID reuse and wedged the pinned host port, making the loop permanent. - BundleResolver service (Effect v4, Context.Service + Layer) resolves own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR; foreign namespaces and unset dirs fall through to the network fetch, so registry-tier children are unchanged - One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot covers every boot-time consumer: config manifest discovery, contract types, orchestrator host loading, MF remoteEntry probes - bos start resolves configs with production env when NODE_ENV=production (kills the dev-mode raw-config load against the stripped image) - Remote-source services no longer allocate ports; stale claims are pruned via recorded process generation; pinned-port conflicts verify a real listener; InfraError/DevStepError report instead of escaping - bos.config.json carries the deterministic post-publish bundle URLs - Dockerfile healthcheck start-periods cover cold-boot duration Verified: runtime image boots healthy with citynode.app blackholed and recovers across docker restart; typecheck 9/9, lint clean, 631 framework tests green. * docs(adr): amend 0011 (outbound local-first) and 0012 (claim identity, listener authority) * refactor(everything-dev): api-contract fetch/checksum path as an Effect service Lifts the manifest/contract-type/auth-export fetch path into ApiContractResolver (Context.Service + Layer, Effect.fn, Schema.TaggedError: ApiManifestFetchError, ApiManifestFormatError, MissingContractTypesError, ContractTypesFetchError, ContractTypesChecksumError, AuthExportFetchError). Behavior-preserving: fetchApiPluginManifest / remoteContractSource / fetchAuthExportTypes keep their signatures and messages (bridges flatten tagged errors to Error with identical strings); transport stays on the http-client promise bridges so the [http] diagnostics and 30s GET cache are unchanged. syncApiContractBridge untouched. * ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4) runtime-remote.test.ts is a live-network smoke: it reads the branch's raw bos.config.json and proxies real traffic (/skill.md, /llms.txt) to config.app.ui.production. Once #228 commits the deterministic bundle URLs, that origin is the deployment this PR ships — a PR CI suite that depends on production's uptime fails whenever the gateway blips, and before the deploy it validates URLs that cannot serve yet (Host tests red: "expected 502 to be 200", 60s hook timeouts). The split was already prepared but never wired: vitest.remote.config.ts owns exactly this file and host has a test:integration:remote script with no consumer, while the default vitest.config.ts include swept it into PR CI. - Default host suite excludes tests/integration/runtime-remote.test.ts (configDefaults preserved) — PR CI no longer touches live origins - deploy.yml runs the smoke after mf check: boots the real host program against the live production URLs — SSR compose from the deployed ui, doc-asset proxy, api ping, root document contract — the functional gate beyond mf check's identity-only check No helper changes, deliberately: in the Deploy context, non-2xx origins make the smoke fail loudly — correct for a post-deploy gate; a down site is already caught by mf check's retry loop. * fix(host-tests): pin the runtime-config fixture to development env loadTestRuntimeConfig called loadResolvedConfig() with no env — with the NODE_ENV-honoring default (hermetic boot), the host test script's NODE_ENV=production resolved production env in the fixture: api source remote → manifest discovery fetched the deployed origin (down, 10s×3 retries) → every beforeAll hook blew the 10s timeout → 6 suites failed at collection (csp-compose, csp-nonce, security, seo, ssr-metadata, ssr). Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the host test script exists for the host's production code paths, not for config resolution. Pin env: "development" — live-origin coverage belongs to the Deploy workflow's smoke (relocated earlier in this PR).
… it from the scaffold (#225) * feat(everything-dev,ui): tenant draft/url helpers as everything-dev/ui/tenant subpath Closes #184. The tenant origin construction (buildTenantUrl, tenantLabel, isLocalHostname), the node-config draft helpers (schema, diff, bundle entry resolution, sha384 integrity preflight), and the new gatewayForAccount merge into one framework module exposed as everything-dev/ui/tenant. The app-owned ui/src/lib/tenant-url.ts and tenant-config-draft.ts copies are deleted; call sites import from the package, so children stop receiving the copies via bos init and versions flow through the catalog / changeset release. gatewayForAccount derives the gateway for an owner account from the runtime config — the runtime's gateway when the account is on the runtime's network, null otherwise — replacing the hardcoded per-network domain checks the PR 177 review flagged in fresh's spawn flow. * fix(everything-dev): keep the ui dist free of node builtins — external react + dead createRequire strip The everything-dev dist's shared rolldown runtime chunk retained a dead `import { createRequire } from "node:module" ` (injected for CLI-side interop, unused after treeshake) — and the new ui-surface dist entries (hydrate/router/tenant) import that chunk, so client builds of the ui subpaths failed with "node:* is a built-in Node.js module and cannot be imported in client-side code". react/react-dom join neverBundle (they are MF shared singletons; bundling them dragged CJS-interop helpers into the dist), and the build strips the dead import from the shared chunk when it is genuinely unused. * feat(ui): read stake-pool contracts through near-kit view calls Closes #185. The hand-rolled JSON-RPC view-function wrapper (raw fetch against the public RPC endpoints, base64 args, manual Zod byte-array decoding — near-rpc.ts) is deleted. The stake-pool query call sites use near-kit's Near.view per network instead, with failure parity: unsupported networks, timeouts, and malformed results resolve null and fall into the same clean query error state. Test mocks move to the near-kit boundary. * style(ui): biome format stake-pool test mock chain * feat(everything-dev,ui): absorb framework UI files into everything-dev/ui subpaths Closes #186. hydrate (client bootstrap), router-client (client router factory), router-server (SSR router module), entry (web entry runner), and router-error (generic error boundary) move into the framework package as new ui subpaths. Child copies shrink to thin wiring stubs that inject only the app's generated artifacts (routeTree.gen, routeConfig.gen, styles.css) and stay framework-owned for sync. The framework modules import the package's own api/auth/runtime/manifest surfaces; the hydrate suite ports to the package keeping the compose digest-parity fallback coverage. globals.d.ts remains a sync-owned copy — ambient declarations cannot be re-exported through stubs. RouterContextWithApi gains the optional authClient the routers already threaded. * refactor(ui,everything-dev): tenant surface through @/app; createRequire conversions at source Review rework (PR 221): app call sites import the tenant surface from @/app (app.ts re-exports everything-dev/ui/tenant) instead of reaching past the app surface; dao-policy/dao-connect's TenantUiOverride re-export chain is cut. The dist's dead createRequire import is eliminated at source — banner.ts reads package.json via readFileSync(new URL(...)) and init.ts statically imports tar (committed dep) instead of a dynamic require — so the rolldown runtime chunk never receives node:module and the emitted-chunk stripper is deleted. * fix(everything-dev): tar ships an ESM build — named extract import * refactor(ui,better-near-auth): per-network near clients through the auth client Review rework (PR 222): better-near-auth's near atom gains getNearClient(network?) — a network whose connector-bound client isn't initialized gets a wallet-less Near cached on demand (public read-only view calls need no wallet; initClientForNetwork upgrades the slot when that network initializes). The stake-pool factories take the app auth client and read contracts through authClient.near.getNearClient(network); the module-local viewClients Map is deleted; pool networks narrow through toNetwork. Timeouts follow the transport defaults (near-kit view has no AbortSignal option — parity on failure-to-null holds). * style: biome organize imports * refactor(everything-dev): SSR router goes generic over the app route tree — shared router defaults Review rework (PR 224): createServerRouterModule is generic over TRouteTree extends AnyRoute — the app stub passes typeof routeTree, the composed-tree casts narrow through the app's tree type, and the 'as unknown as AnyRouter' boundary cast is gone (a concrete tree satisfies the contract directly). The unused errorComponent override param is dropped (RouterError is the framework-owned boundary). The fallback components and QueryClient defaults dedupe into ui/router-defaults, shared by the client factory, the SSR module, and the hydrator. * feat(everything-dev,ui): synthesize the core ui rsbuild config — drop it from the scaffold Closes #187. Following the every-plugin generated-config model, the ui package's dev/build/preview scripts route through the new bos-ui bin (everything-dev/ui-build), which honors a local rsbuild.config.ts as an override and otherwise generates one from the shared every-plugin/ui/mf-build factory — provider role, CORE_UI_PLUGIN_KEY, the web/node exposes, public copy, and the APP_NAME/APP_ACCOUNT defines derived from the resolved runtime config. The config drops from the init copy surface; sync treats an existing child config as app-owned. Manifest generation no longer gates on the config's existence. ui lib target raised to ES2024 (Promise.withResolvers). * refactor(every-plugin,ui): every-plugin owns the core ui build — bos-ui deleted, mf-build → build/ui Review rework (PR 225). The ui workspace follows the /api pattern exactly: scripts run `every-plugin dev|build|preview` (args pass through), and the every-plugin CLI detects the workspace-form core ui (src/routes + src/entry, not plugin-shaped) and dispatches rsbuild with the synthesized config. The normalized build surface: every-plugin/src/build/ui/{factory,generated-config} — mf-build moves to build/ui (the ecosystem has not shipped any of these surfaces; the old ./ui/mf-build export is deleted, not deprecated), the generated config shrinks to a 4-line async defineConfig importing createCoreUiRsbuildConfig (exported factory, no template-embedded config) and readAuthoredConfigInput (everything-dev/config — the authored-config read that handles bos.app.ts and JSON without network resolution, closing the TS-form crash). everything-dev's ui-build runner, bos-ui bin, and their tests are deleted; synthesis coverage lands in every-plugin's suite. * fix(everything-dev): reapply the createRequire conversions + stripper removal The 186→187 rebase auto-resolve had silently re-introduced the pre-rework banner/init createRequire sources and the emitted-chunk stripper. Reapplied: banner reads package.json via readFileSync(new URL(...)), init statically imports tar, the stripper is deleted — the dist is node:module-free again. * style: biome import-name sort in the ported tenant-url call sites * fix(ui): PoolSummary reads pools through the near-kit view client — toNetwork narrowing + authClient * style: biome format the framework router-error * fix(everything-dev): drop the duplicated tar import in init.ts * fix(every-plugin): preview ran build — the dispatch dropped the command runCliCommand routed preview through runCoreUi(args) with args empty, so runCoreUi's destructuring default kicked in and every-plugin preview ran rsbuild build and exited 0 — the preview server never started. CI regression stacks (BOS_NO_WATCH → dev:built) lost the ui entirely: proxied assets 500 (fetch failed) and SSR streaming crashed on the null remote chunk ('Cannot read properties of null (reading stores)'). * fix(every-plugin): build with extra args ran the wrong rsbuild command runCliCommand consumed the command, then runCoreUi re-derived it from the remainder — so 'every-plugin build --environment web' invoked 'rsbuild --environment --config X web' (CACError: Unused args: web). The image build (container-build.ts → build:client/build:ssr) broke in the start-mode regression jobs. Prepend the command explicitly, matching the dev/preview cases.
…, canonicalization (#226) * feat(everything-dev): bos.app.ts operative — loader, TS-form scaffold, canonicalization Closes #188. The config loader accepts the authored TS descriptor alongside bos.config.json (JSON preferred when both coexist; TS-only children are the new child form). Descriptors materialize into the same BosConfigInput the pipeline consumes — import-extends (inlined parent) resolves through resolveApp; bos:///file extends refs flow into the existing JSON extends chain. publish/sync canonicalize the resolved config to JSON for FastKV with identical resolution (proven: a TS-authored child resolves identically to the equivalent JSON child). bos init scaffolds children with the TS config form by default (configInputToDescriptor + serializeAppDescriptorSource); sync reads TS-form children through the same materialization and never injects the parent's JSON into them. Golden-fixture coverage extended (lossless round-trip + convert + parity). ADR 0005 advances to Accepted (phase 1). * refactor(everything-dev): the descriptor module owns the TS config form — loader hardened Review rework (PR 226): serializeAppDescriptorSource absorbs into descriptor/serialize (the descriptor module owns the whole TS-form surface: toConfigInput, its inverse configInputToDescriptor — driven by the shared ATTACHMENT_FIELDS map in descriptor/resolve — and the source emitter); app-config-form.ts is deleted. loadAppDescriptorConfig hardens: a bos.app.ts must default-export an App descriptor, and registry candidates must parse as descriptors (a named export that isn't an App can no longer enter the registry). * refactor(ui): dao-policy reads TenantUiOverride through @/app * fix(everything-dev): drop the unused serialize import from the loader test
…he (ADR 0011) (#231) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion. * docs: clean up plan archives, wayfinder tickets, and phantom references - delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced) - plans/README.md: drop phantom ui-route-grafting-migration file link (kept as plain text + issue #108), prototype count 3→4, fix 03-plugin-type-deps typo - recreate wayfinder decision tickets 08–12 as decision-record stubs (decisions already recorded in README/map) and refresh ticket counts - ADR 0007: replace dead ui-route-grafting-migration links with inline supersession note (ADR 0008); fix stale plan-035 link in ADR 0007 platform-bundle-storage; point ADR 0006 at the archived plan 022 - advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029 and 028 collisions), add README rows for 044–048 (DONE via PR #136) and 030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to done/ with a slim done/README.md preserving status notes, prune dependency notes that only concern archived plans * docs: fix plans/README.md staging miss from the cleanup commit * fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011) A self-contained runtime image fetched its own plugin manifests through its own public origin at boot; when that origin 502'd (Railway restart loop, gateway hiccup, cold start ordering) nothing could ever come up — the boot depended on the thing it was booting. Registry claims survived container restarts via PID reuse and wedged the pinned host port, making the loop permanent. - BundleResolver service (Effect v4, Context.Service + Layer) resolves own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR; foreign namespaces and unset dirs fall through to the network fetch, so registry-tier children are unchanged - One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot covers every boot-time consumer: config manifest discovery, contract types, orchestrator host loading, MF remoteEntry probes - bos start resolves configs with production env when NODE_ENV=production (kills the dev-mode raw-config load against the stripped image) - Remote-source services no longer allocate ports; stale claims are pruned via recorded process generation; pinned-port conflicts verify a real listener; InfraError/DevStepError report instead of escaping - bos.config.json carries the deterministic post-publish bundle URLs - Dockerfile healthcheck start-periods cover cold-boot duration Verified: runtime image boots healthy with citynode.app blackholed and recovers across docker restart; typecheck 9/9, lint clean, 631 framework tests green. * docs(adr): amend 0011 (outbound local-first) and 0012 (claim identity, listener authority) * refactor(everything-dev): api-contract fetch/checksum path as an Effect service Lifts the manifest/contract-type/auth-export fetch path into ApiContractResolver (Context.Service + Layer, Effect.fn, Schema.TaggedError: ApiManifestFetchError, ApiManifestFormatError, MissingContractTypesError, ContractTypesFetchError, ContractTypesChecksumError, AuthExportFetchError). Behavior-preserving: fetchApiPluginManifest / remoteContractSource / fetchAuthExportTypes keep their signatures and messages (bridges flatten tagged errors to Error with identical strings); transport stays on the http-client promise bridges so the [http] diagnostics and 30s GET cache are unchanged. syncApiContractBridge untouched. * feat(child-tier): foreign-namespace bundle proxy + stale-if-error cache (ADR 0011) The child tier loads base workspaces from foreign origins; an origin outage hard-failed browsers (502) and the child's cold boot. Two entrances share one disk cache (BOS_BUNDLE_CACHE_DIR, default .bos/bundle-cache — deliberately separate from BOS_BUNDLE_DIR: cached bytes are a resilience artifact, never a deployment): - Host: /bundles/* falls through to createBundleProxyCacheHandler after the FS handler — namespace→origin map derived from the runtime config's slot URLs (self-origin namespaces never proxied); success writes through, origin failure serves last-known-good bytes with x-bundle-cache: stale, no cache + failure → 502. GET/HEAD only. - CLI fetch adapter: foreign /bundles/… URLs get the same stale-if-error treatment, so boot-time outbound fetches survive a base-origin outage after the first successful boot. Inert for plain dev sessions (no BOS_BUNDLE_DIR/ BOS_BUNDLE_CACHE_DIR) — default behavior unchanged; stale serves only on origin failure, so normal operation keeps serving fresh. Shared cache primitives live in everything-dev/bundle-cache (subpath export; the host already imports everything-dev/* subpaths). * ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4) runtime-remote.test.ts is a live-network smoke: it reads the branch's raw bos.config.json and proxies real traffic (/skill.md, /llms.txt) to config.app.ui.production. Once #228 commits the deterministic bundle URLs, that origin is the deployment this PR ships — a PR CI suite that depends on production's uptime fails whenever the gateway blips, and before the deploy it validates URLs that cannot serve yet (Host tests red: "expected 502 to be 200", 60s hook timeouts). The split was already prepared but never wired: vitest.remote.config.ts owns exactly this file and host has a test:integration:remote script with no consumer, while the default vitest.config.ts include swept it into PR CI. - Default host suite excludes tests/integration/runtime-remote.test.ts (configDefaults preserved) — PR CI no longer touches live origins - deploy.yml runs the smoke after mf check: boots the real host program against the live production URLs — SSR compose from the deployed ui, doc-asset proxy, api ping, root document contract — the functional gate beyond mf check's identity-only check No helper changes, deliberately: in the Deploy context, non-2xx origins make the smoke fail loudly — correct for a post-deploy gate; a down site is already caught by mf check's retry loop. * fix(host-tests): pin the runtime-config fixture to development env loadTestRuntimeConfig called loadResolvedConfig() with no env — with the NODE_ENV-honoring default (hermetic boot), the host test script's NODE_ENV=production resolved production env in the fixture: api source remote → manifest discovery fetched the deployed origin (down, 10s×3 retries) → every beforeAll hook blew the 10s timeout → 6 suites failed at collection (csp-compose, csp-nonce, security, seo, ssr-metadata, ssr). Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the host test script exists for the host's production code paths, not for config resolution. Pin env: "development" — live-origin coverage belongs to the Deploy workflow's smoke (relocated earlier in this PR).
…232) * feat(everything-dev): typed mount contract for ui plugin grafting Plan 023. The graft protocol's mount vocabulary becomes a compile-time contract instead of a stringly convention: - mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry> so MountId derives from its canonical keys; MOUNTS exports the union value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from types.ts, which re-exports both for surface stability) - define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at construction — every root child declaring a _mount must resolve to a declared canonical mount, else it throws with the child id and a closest-mount hint; associations ride a module-scoped WeakMap - compose.ts: defined modules graft via declared mounts (no re-derivation); raw trees keep the deriveMountId fallback, now the declaredSegment derivation with a MountId return type - digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input semantics changed; all compose digests invalidate) - README row records the agreed deviation (validation covers _-roots only) and the plan-024 note (_template has no ui tree yet) Verification: everything-dev 489 tests green (compose suite 20 incl. 9 new), host 175 green; no new typecheck or lint findings vs baseline. * feat(api,host): platform bundle storage + publish --cdn platform (plan 029) Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare account: bundles upload to the platform storage via the CLI session and are served publicly from /bundles/* with immutable cache headers. - api: bundle_objects table + BundleStorage Effect service (R2 drop-in seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings, path allowlist, traversal guard, server-side SRI) and GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File output) - host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin - cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts -> platform storage), session requirement, bos login refusal without one - docs: ADR 0007 (file transport as oRPC contract concern), plan 033 (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check) left for the operator. * chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up) * refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups) - storage routes follow the template convention: .effect() generators with yield* StorageTag (StorageTag now exposed from initialize's returned layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the every-plugin/errors data shapes - AGENTS.md 'Adding API Endpoints': new-route handler convention enforced (.effect + yield* Tag; Context.get reserved for streaming; shared auth middlewares' .use() typing gap documented) - ADR 0007: handler-convention bullet + recorded seam deviation (platform provider skips workspace scripts.deploy; parent uploads dist/ via applyDeployResults) * chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard) * fix(api): StorageTag Self type parameter (class-self-mismatch rule) * feat(dev): unified log pipeline (normalize, classify, filter, broadcast) Every dev-session process line now flows through a single pure pipeline that normalizes multi-line blocks, classifies level and category, and level-filters before broadcasting to the three sinks (screen tail, log file, l/shutdown export) so the views agree by construction. - normalize: Effect Logger { } objects, stack-trace continuations - classify: WARN [Better Auth] stdout lines are warn; legacy LOG_NOISE_PATTERNS folded in as mf/build info; clean exits (code 0) classify as shutdown info, not [ERR] - filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all); [Database] startup runs collapse to one db ready per plugin - file sink always receives everything; export prints a filtered summary plus a full-logs pointer - one ANSI-strip implementation; ADR 0011 records the decision * feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package bos dev's interactive view is now a pure render function over a single session-state holder (processes, log events). A hand-rolled alt-screen renderer redraws on state change, reads q / l / ctrl+c from raw stdin, and restores the terminal on unmount. The piped/non-TTY fallback reuses the same render helpers and prints incrementally — the near-verbatim duplicated streaming view is deleted. - ink, react (peer), gradient-string and their type packages removed from the CLI package manifest; build externals updated - banner gradient replaced with a small truecolor ramp in the theme module (colors resolve at call time, so chalk level is honored) - renderDevState / renderProcessRow / renderLogLine are unit-tested; renderer handle covered for dedupe, incremental rows, ready-block once, alt-screen enter/exit, and key handling * feat(dev): one row per plugin in the service table plugin-ui:* companion rows merge into their parent as an inline '· ui :<port>' annotation (the auth app slot renders as one PLUGINS row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists only host/api/ui; a merged row counts as ready only when both its api and ui surfaces are ready, and 'All N services running' counts merged rows. Sectioning, display names, colors, and column widths live in the shared render module consumed by both the TTY and non-TTY paths — the plugin: prefix check that orphaned plugin-ui rows into SERVICES is gone. Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry (structural AuthSlotShape signature): the inline copies in the infra planner (allocateServices, buildServiceDescriptors, assigned runtime config), the dependency DAG, and the api contract bridge are deleted. allocateServices now also matches remote-url mirrors, fixing its prior allocate/descriptor disagreement; planner tests pin the semantics. * fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed * docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along) * fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1) * fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042) * fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2) * test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance) * refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e * feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1) * feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2) * refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3) * docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along) * docs(plans): 036 DONE — port leases and scoped stack landed through phase 4 * fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3) * fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038) * fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039) * perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040) * chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041) * docs(plans): 037-041 status rows — all DONE with commit refs * chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks * fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break) * fix(everything-dev): block-allocate the auth mirror's ui port again The atomic port-block allocator (plan 036 phase 2) restructured the plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth port allocation that pre-regression allocateServices granted outside the !mirror check. With no port, the planner's patchedUi left the mirror's ui.url empty and the host advertised the auth remote as a relative /remoteEntry.js — the browser could never load the auth plugin UI, so /login never rendered (dev:ssr + dev:csr regression suites red from 2674acc onward). Restore the pre-regression semantics under the block model: the mirror still gets no plugin:<id> api entry, but a local mirror with a local ui gets its plugin-ui:<id> entry allocated, which patches runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth descriptor's BOS_UI_PORT. Also sanitize the regression stack log filename (regression-dev-ssr.log) — the mode's colon made upload-artifact reject the whole artifacts upload, hiding the very logs needed to debug this. New planInfra regression test pins the mirror ui allocation and the patched ui.url. * fix(everything-dev): keep harness-injected origins in config-path production starts A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL there are stray dev leftovers and stay purged. An explicit --config-path start (regression harness, local production stack) injects those origins as deployment truth; deleting them made the in-process host derive the auth origin as https://<domain>, so better-auth issued Secure cookies no http client could send back (empty sessions, 401s, INVALID_ORIGIN). * fix(everything-dev): purge localhost origins only on registry starts The gate moves from !configPath to a positive isRegistryStart check: bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY) load the local config and are local starts too — their origins are deployment truth, not stray dev leftovers. Adds unit tests for all three config-source outcomes and a changeset. * fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler Bun's TTY stdin never enters flowing mode from a bare data listener attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C generated no SIGINT, and the \x03/q bytes never arrived either — the session was unquittable from its own terminal (bos kill from a second terminal was the only exit). The renderer now resumes stdin after enabling raw mode; unmount pauses it. Quit-path cleanup in the same sweep: - devApp's SIGINT/SIGTERM handler now delegates to the same requestShutdownEscalating the TUI uses (the two bodies had drifted into twins); signal counting lives in one place - emergencyKill reuses reapGroup instead of re-implementing the group-kill fallback - suspendForceExitTimer renamed rearmForceExitTimer — it extends the deadline, it does not suspend it - l (export logs) no longer counts toward the quit escalation, so l-then-q quits gracefully instead of force-exiting past the export - force-exit timer is unref'd on both paths Regression coverage: the framework suite gains a pty harness (creack/pty) and two tests that boot a fully interactive stack, wait for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C byte — asserting the CLI exits 0, every service child and watcher grandchild dies, ports free, and the registry cleans up. Both failed (red) before the resume fix; both pass after. * fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check The rebase merge of main (post-#179) kept BOTH the Record<string, Pick<...>> annotation and the branch's trailing `as const satisfies`. The annotation widens the const back to Record, so under noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access yields Pick | undefined and the descriptor spreads became optional — typecheck failed on the auth descriptor. Keep only the satisfies form: same contract enforcement, literal keys stay required. * feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own package.json) run as a child of the plugin's dev process via BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per- plugin renderer had nothing to merge and the port was invisible unless you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011). ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in both folder-form branches (regular plugin + auth mirror); getProcessStates threads it into the process state; mergePluginUiRows synthesizes the same inline annotation package-form companions get. The auth row now reads: AUTH (local) running :3002 · ui :3011. * fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton The post-sign-in redirect loop was fixed three times (#162, #175, #178) without staying fixed: the invariant (one session queryFn, always disableCookieCache) had to hold identically across independently deployed bundles — the read path was compiled separately into the core ui and every plugin ui, and mixed deploys ran divergent copies whose guard decisions ping-ponged past the router limit. - everything-dev/ui/auth joins the ui share list as a strict singleton (core ui provides, plugin uis consume with import: false — zero bundled fallback): one runtime copy; version mismatch fails loudly instead of loading a second disagreeing copy. Version resolves from the building workspace's installed package, not the catalog: range (plan 010 single-resolver principle) - guards move to the platform package: requireSession/requireAdmin + plugin-path helpers + clearAuthenticatedQueries now live in everything-dev/ui/auth (framework-home convention, amended #89) — the login↔authenticated redirect pair is owned and unit-tested in one place - sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts, and the drifted plugin session-cache.ts copy exit child ownership; the plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed from the ui copy - ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔ query-cache hydration bridge for the sync-owned __root.tsx) ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean, everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin 92, template/apps/proposals/votes green; provider/consumer manifests carry the shared entry with matching requiredVersion. * refactor(build): config factories resolve from src; the train owns dist freshness First-principles exit from the dist-staleness class (two resolution rules, ADR 0013): 1. Bundler-configuration code resolves from source — the mf-build and build/rspack factories run only at build time from the working tree, so resolving them through built dists created an invisible build dependency (proven: a stale every-plugin dist silently dropped the shared everything-dev/ui/auth entry from the ui manifest with a green build). every-plugin ships src in its tarball, so its ./ui/mf-build and ./build/rspack subpaths now default to src in every condition; the everything-dev/ui/mf-build re-export shim is deleted (one consumer — ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly, matching the generated plugin configs). Verified: a ui build succeeds with every-plugin's dist deleted outright, manifest intact. 2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth, db, every-plugin's shared runtime) are dist-resolved, and buildWorkspaceTargets unconditionally staleness-checks the framework prerequisites (every-plugin, everything-dev, better-near-auth) before any target. The train (bun run build / deploy) is the only supported build path; AGENTS.md documents it. Removes the interim manifest-assertion tripwire (proven placebo: the manifest is generated from the config chain, so config-vs-manifest checks agree with stale chains). Plugin unit shape (src -> api/src, plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption ticketed (issues 15-17). * fix(build): every-plugin subpaths resolve src under bun, dist under node The default->src flip broke scaffolded children: node refuses to type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_ STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's integration test died loading the factory from src. The correct discriminator is the runtime, not the NODE_ENV: bun (the workspace runtime, any NODE_ENV) resolves src via its always-on 'bun' condition — config factories can never go stale in-workspace; node consumers (published CLIs in children) resolve the immutable published dist. The 'bun' arm now covers every every-plugin subpath — this also fixes a latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run build died: the bos CLI itself imports every-plugin subpaths that only resolved via dist without the development condition; the train now boots from src and staleness-rebuilds the dist itself — verified). mf-build tests move to every-plugin's suite with a relative src import (resolver-agnostic, always current). ADR 0013 wording updated; init.full verified green under CI=true locally. * refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth Answers the PR review: the SSR↔query-cache hydration bridge is router glue better-auth has no concept of (their docs' SSR pattern is Next-shaped: server resolves, pass via props — for TanStack the adapter goes through Query dehydration because the guards read the query). It exits child ownership like the rest of the session surface: ui/src/lib/session-cache.ts deleted, __root.tsx imports via @/lib/auth, tests moved to the package. Ticket 14 updated: endgame is generated route types from the compose manifests (keeps 'to' — search/preload/active semantics href can't give); interim choice pending discussion. * docs: clean up plan archives, wayfinder tickets, and phantom references - delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced) - plans/README.md: drop phantom ui-route-grafting-migration file link (kept as plain text + issue #108), prototype count 3→4, fix 03-plugin-type-deps typo - recreate wayfinder decision tickets 08–12 as decision-record stubs (decisions already recorded in README/map) and refresh ticket counts - ADR 0007: replace dead ui-route-grafting-migration links with inline supersession note (ADR 0008); fix stale plan-035 link in ADR 0007 platform-bundle-storage; point ADR 0006 at the archived plan 022 - advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029 and 028 collisions), add README rows for 044–048 (DONE via PR #136) and 030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to done/ with a slim done/README.md preserving status notes, prune dependency notes that only concern archived plans * docs: fix plans/README.md staging miss from the cleanup commit * fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011) A self-contained runtime image fetched its own plugin manifests through its own public origin at boot; when that origin 502'd (Railway restart loop, gateway hiccup, cold start ordering) nothing could ever come up — the boot depended on the thing it was booting. Registry claims survived container restarts via PID reuse and wedged the pinned host port, making the loop permanent. - BundleResolver service (Effect v4, Context.Service + Layer) resolves own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR; foreign namespaces and unset dirs fall through to the network fetch, so registry-tier children are unchanged - One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot covers every boot-time consumer: config manifest discovery, contract types, orchestrator host loading, MF remoteEntry probes - bos start resolves configs with production env when NODE_ENV=production (kills the dev-mode raw-config load against the stripped image) - Remote-source services no longer allocate ports; stale claims are pruned via recorded process generation; pinned-port conflicts verify a real listener; InfraError/DevStepError report instead of escaping - bos.config.json carries the deterministic post-publish bundle URLs - Dockerfile healthcheck start-periods cover cold-boot duration Verified: runtime image boots healthy with citynode.app blackholed and recovers across docker restart; typecheck 9/9, lint clean, 631 framework tests green. * docs(adr): amend 0011 (outbound local-first) and 0012 (claim identity, listener authority) * refactor(everything-dev): api-contract fetch/checksum path as an Effect service Lifts the manifest/contract-type/auth-export fetch path into ApiContractResolver (Context.Service + Layer, Effect.fn, Schema.TaggedError: ApiManifestFetchError, ApiManifestFormatError, MissingContractTypesError, ContractTypesFetchError, ContractTypesChecksumError, AuthExportFetchError). Behavior-preserving: fetchApiPluginManifest / remoteContractSource / fetchAuthExportTypes keep their signatures and messages (bridges flatten tagged errors to Error with identical strings); transport stays on the http-client promise bridges so the [http] diagnostics and 30s GET cache are unchanged. syncApiContractBridge untouched. * feat(child-tier): foreign-namespace bundle proxy + stale-if-error cache (ADR 0011) The child tier loads base workspaces from foreign origins; an origin outage hard-failed browsers (502) and the child's cold boot. Two entrances share one disk cache (BOS_BUNDLE_CACHE_DIR, default .bos/bundle-cache — deliberately separate from BOS_BUNDLE_DIR: cached bytes are a resilience artifact, never a deployment): - Host: /bundles/* falls through to createBundleProxyCacheHandler after the FS handler — namespace→origin map derived from the runtime config's slot URLs (self-origin namespaces never proxied); success writes through, origin failure serves last-known-good bytes with x-bundle-cache: stale, no cache + failure → 502. GET/HEAD only. - CLI fetch adapter: foreign /bundles/… URLs get the same stale-if-error treatment, so boot-time outbound fetches survive a base-origin outage after the first successful boot. Inert for plain dev sessions (no BOS_BUNDLE_DIR/ BOS_BUNDLE_CACHE_DIR) — default behavior unchanged; stale serves only on origin failure, so normal operation keeps serving fresh. Shared cache primitives live in everything-dev/bundle-cache (subpath export; the host already imports everything-dev/* subpaths). * refactor(everything-dev): code-artifacts as Effect.fn with tagged ArtifactGenError (C1) generateCodeArtifactsEffect is the Effect-native core (tagged ArtifactGenError per phase: write resolved config, load resolved config, sync api contract bridge, generate ui manifest); the exported generateCodeArtifacts keeps its Promise signature as a bridge — callers (dev-program, publish) unchanged. The Effect export is available for the later dev-program adoption. * refactor(everything-dev): config.ts error taxonomy as Schema.TaggedError (C2) All thrown Errors become tagged errors (ConfigNotLoadedError, ConfigLoadError, CircularExtendsError, ConfigNotfoundError, ConfigExtendsError) — every one an Error subclass with the identical message string, so all plain callers' .message reads and the wrapped-cause behavior are unchanged. This gives the later Effect-native phases a typed error channel instead of string matching. * refactor(everything-dev): config pipeline as Effect.fn cores (C3+C4) loadResolvedConfig, resolveRuntimePlugins, and buildRuntimeConfig become Effect.fn generators with a typed error channel; exported signatures stay Promise-based via bridges, so no caller changes. Error taxonomy (C2) carries the channel: ConfigLoadError wraps at the load boundary with the identical "Failed to load config from …" message; inner failures propagate raw before that wrap, matching today's throw/wrap order exactly. - resolveRuntimePlugins: Promise.all → Effect.forEach (unbounded, order- preserving); per-plugin resolution via tryPromise with ConfigExtendsError - buildRuntimeConfig: sync body stays inline; the two async islands (auth entry, manifest discovery) become tryPromise steps — the catch-all discovery diagnostics stay plain inside the promise (same warn messages, same degrade-to-warning semantics), keeping console out of Effect code - loadResolvedConfig: the pipeline composes the Effect cores; the suppress/drain/resume warning discipline is preserved call-for-call (resume on both paths via the inner catch, drain on success only) Behavior-preserving throughout: 634 framework tests green (config-resolved golden tests pin the outputs), typecheck 9/9, lint 0 errors. * ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4) runtime-remote.test.ts is a live-network smoke: it reads the branch's raw bos.config.json and proxies real traffic (/skill.md, /llms.txt) to config.app.ui.production. Once #228 commits the deterministic bundle URLs, that origin is the deployment this PR ships — a PR CI suite that depends on production's uptime fails whenever the gateway blips, and before the deploy it validates URLs that cannot serve yet (Host tests red: "expected 502 to be 200", 60s hook timeouts). The split was already prepared but never wired: vitest.remote.config.ts owns exactly this file and host has a test:integration:remote script with no consumer, while the default vitest.config.ts include swept it into PR CI. - Default host suite excludes tests/integration/runtime-remote.test.ts (configDefaults preserved) — PR CI no longer touches live origins - deploy.yml runs the smoke after mf check: boots the real host program against the live production URLs — SSR compose from the deployed ui, doc-asset proxy, api ping, root document contract — the functional gate beyond mf check's identity-only check No helper changes, deliberately: in the Deploy context, non-2xx origins make the smoke fail loudly — correct for a post-deploy gate; a down site is already caught by mf check's retry loop. * fix(host-tests): pin the runtime-config fixture to development env loadTestRuntimeConfig called loadResolvedConfig() with no env — with the NODE_ENV-honoring default (hermetic boot), the host test script's NODE_ENV=production resolved production env in the fixture: api source remote → manifest discovery fetched the deployed origin (down, 10s×3 retries) → every beforeAll hook blew the 10s timeout → 6 suites failed at collection (csp-compose, csp-nonce, security, seo, ssr-metadata, ssr). Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the host test script exists for the host's production code paths, not for config resolution. Pin env: "development" — live-origin coverage belongs to the Deploy workflow's smoke (relocated earlier in this PR).
…251) * feat(auth): manage linked NEAR accounts; phone sign-in desktop-only - Settings > Sign-in methods: list all linked NEAR accounts, make primary, unlink with confirmation, link another named account, or create a NEAR account derived from a passkey when none is linked (gated on passkeys + passkey wallet availability). Linking uses the dedicated near.link action instead of a sign-in ceremony. - Login page: hide 'Sign in with your phone' on mobile viewports and adapt the no-passkey hint; add useMediaQuery/useIsDesktop hooks. * refactor(auth): address two-axis review findings - Surface account-load failures (error state + retry) instead of rendering the empty state; passkey eligibility requires a successful passkey list rather than swallowing errors - Hide 'Make primary' on the account that is already primary - Export isDeterministicAccountId from better-near-auth and use it in the UI instead of duplicating the NEP-616 account regex - Add useNetworkId() hook; drop the duplicated networkId casts in login and auth-methods - Move query keys to lib/query-keys; collapse the callback-to-promise wrappers into one toPromise helper - Cache MediaQueryList per query in useMediaQuery - Delete unused plugins/auth/ui/src/lib/use-near-account.ts
…x stale claims (#254) ADR renumbering (resolves the four duplicate numbers): - 0007-platform-bundle-storage → 0015 (superseded by 0011, kept for history) - 0011-unified-log-pipeline → 0016 - 0012-session-gas-keys → 0017 - 0013-session-single-owner → 0018 - all 14 external references updated (AGENTS.md, changesets, advisor-plans, scratch issues); 0019 reserved for advisor-plan 042's spreadability ADR ADR honesty fixes: - 0003 amended: tsconfig.contract.json mechanism replaced by EmitPluginManifest - 0010 §7 annotated: bos routes inspector not yet implemented - 0011 records the landed foreign-namespace proxy + stale-if-error amendment - 0007-runtime/0009 status lines note their amendments - docs/adr/README.md index with number-assignment discipline Plans reorganization: - plans/done/ archive (mirrors advisor-plans/done/): orpc-v2-effect-migration, effect-native-plugins, tenant-feature-completeness, ui-extends-ui-federation, db-auth-absorption prototype - toml-infra-alchemy: status correction (phases 1/3/4 live only on the unmerged upstream/feat/alchemy branch) - every-plugin-db-auth-absorption + advisor-plan 017 row: mostly-done status with the databaseLayer/sync-exit remainder - wayfinder tickets 01/04/08/09/11/12 re-annotated; beta-v2-map status banner - plans/README.md: grafting row marked superseded (ADR 0007/0008), node-map (#233) pointer, offline plans marked unscheduled GitHub tracker: closed #190/#104 (delivered), #111/#118 (superseded, reasons in the close comments); #166's parallel-builds half checked off
…ne (#253) * fix(db,plugins): honor sslmode; structured plugin-boot failure pipeline Root cause of the Sep 25 production outage: PR #163's security batch flipped the shared DB driver's TLS verification to on-by-default for every non-local URL, and managed providers' sslmode=require URLs carry certificates no client CA bundle can verify — the auth plugin's pool (followed by the api's) failed at connect time. The pre-existing boot pipeline then retried silently for 120s per plugin, pushing the server's port-bind past the container entrypoint's 180s health gate -> exit(1) -> Railway restart loop -> 502. - db driver: derive TLS from the URL's sslmode with libpq semantics (require/prefer/allow encrypt WITHOUT verifying; verify-ca/verify-full verify; bare non-local URLs keep the secure default; the env var still overrides) + full ssl-derivation matrix unit tests - every-plugin: one shared Effect-native load policy (loadRemoteWithRetry) — capped exponential backoff under a wall-clock budget, per-attempt failure logs deduped by classification signature, poisoned entry-cache purge between attempts, and fail-fast on permanent failures via classifyPluginFailure().retryable; absorbs withRemoteEntryResilience and the dev-serve duplicate loop; readiness poll gets its own budget - host: plugin bootstrap failures stay structured end-to-end — PluginBootstrapError exposes operation + classification, bootstrap logs read [Plugins][<key>] Failed to load plugin (<operation>) — permanent|retryable, and /health + /api/_health surface a structured failures array instead of joined strings - log formatting: [MF][<pluginId>] prefixes, drop the redundant Effect.annotateLogs({ plugin }) JSON trailer, [Plugins][<key>] short keys in host boot logs Verification: everything-dev 696, every-plugin 109 unit + 31 integration, host 206, ui 75, api 137 all green; bun typecheck and bun lint clean. host test:e2e has 3 pre-existing environmental failures identical on pristine main (needs local docker test DBs). * refactor(every-plugin): shared PluginLoadFailureInfo contract (review) Move the structured failure shape from host into every-plugin, next to classifyPluginFailure/PluginFailureClassification — it is the cross-package failure contract (loader context + classification), with host re-exporting for its health surface. Also fix the retry-test type issues surfaced by the build train (unknown ?? {} narrowing and noUncheckedIndexedAccess on mock calls). * fix(everything-dev): bos build reports unknown targets instead of bare "Unknown error" selectWorkspaceTargets only resolves app/plugin slot keys, so a framework-package name (every-plugin) yielded targets=[] -> a messageless error result; all-skipped selections and a missing bos.config.json had the same shape. BuildResultSchema gains the optional error field the CLI already prints, and all three handler error paths now carry messages: unknown targets list the valid slots (framework packages build via the prerequisite train), nothing-built lists the skipped targets, missing config says so.
* feat(everything-dev): typed mount contract for ui plugin grafting
Plan 023. The graft protocol's mount vocabulary becomes a compile-time
contract instead of a stringly convention:
- mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry>
so MountId derives from its canonical keys; MOUNTS exports the union
value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from
types.ts, which re-exports both for surface stability)
- define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at
construction — every root child declaring a _mount must resolve to a
declared canonical mount, else it throws with the child id and a
closest-mount hint; associations ride a module-scoped WeakMap
- compose.ts: defined modules graft via declared mounts (no
re-derivation); raw trees keep the deriveMountId fallback, now the
declaredSegment derivation with a MountId return type
- digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input
semantics changed; all compose digests invalidate)
- README row records the agreed deviation (validation covers _-roots
only) and the plan-024 note (_template has no ui tree yet)
Verification: everything-dev 489 tests green (compose suite 20 incl. 9
new), host 175 green; no new typecheck or lint findings vs baseline.
* feat(api,host): platform bundle storage + publish --cdn platform (plan 029)
Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare
account: bundles upload to the platform storage via the CLI session and
are served publicly from /bundles/* with immutable cache headers.
- api: bundle_objects table + BundleStorage Effect service (R2 drop-in
seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings,
path allowlist, traversal guard, server-side SRI) and
GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File
output)
- host: route-scoped bodyLimit for uploads, second prefix-symmetric
OpenAPIHandler mount at /bundles/* (+ proxy mode), generic
ResponseHeadersHandlerPlugin
- cli: deploy.cdn config (default zephyr), --cdn flag, platform
uploader (dist/ artifacts -> platform storage), session requirement,
bos login refusal without one
- docs: ADR 0007 (file transport as oRPC contract concern), plan 033
(derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset
Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check)
left for the operator.
* chore(everything-dev): drop dead cdnUploadHandled param (code-review follow-up)
* refactor(api): effect-native storage handlers — .effect() + yield* StorageTag (code-review follow-ups)
- storage routes follow the template convention: .effect() generators with
yield* StorageTag (StorageTag now exposed from initialize's returned
layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with
the every-plugin/errors data shapes
- AGENTS.md 'Adding API Endpoints': new-route handler convention enforced
(.effect + yield* Tag; Context.get reserved for streaming; shared
auth middlewares' .use() typing gap documented)
- ADR 0007: handler-convention bullet + recorded seam deviation
(platform provider skips workspace scripts.deploy; parent uploads
dist/ via applyDeployResults)
* chore: pin zephyr plugins to 1.3.0 (deliberate catalog pin, drift guard)
* fix(api): StorageTag Self type parameter (class-self-mismatch rule)
* feat(dev): unified log pipeline (normalize, classify, filter, broadcast)
Every dev-session process line now flows through a single pure
pipeline that normalizes multi-line blocks, classifies level and
category, and level-filters before broadcasting to the three sinks
(screen tail, log file, l/shutdown export) so the views agree by
construction.
- normalize: Effect Logger { } objects, stack-trace continuations
- classify: WARN [Better Auth] stdout lines are warn; legacy
LOG_NOISE_PATTERNS folded in as mf/build info; clean exits
(code 0) classify as shutdown info, not [ERR]
- filter: --log-level / BOS_LOG_LEVEL (default warn; DEBUG = all);
[Database] startup runs collapse to one db ready per plugin
- file sink always receives everything; export prints a filtered
summary plus a full-logs pointer
- one ANSI-strip implementation; ADR 0011 records the decision
* feat(dev): stream-driven ANSI TUI — drop ink/react from the CLI package
bos dev's interactive view is now a pure render function over a single
session-state holder (processes, log events). A hand-rolled alt-screen
renderer redraws on state change, reads q / l / ctrl+c from raw stdin,
and restores the terminal on unmount. The piped/non-TTY fallback reuses
the same render helpers and prints incrementally — the near-verbatim
duplicated streaming view is deleted.
- ink, react (peer), gradient-string and their type packages removed
from the CLI package manifest; build externals updated
- banner gradient replaced with a small truecolor ramp in the theme
module (colors resolve at call time, so chalk level is honored)
- renderDevState / renderProcessRow / renderLogLine are unit-tested;
renderer handle covered for dedupe, incremental rows, ready-block
once, alt-screen enter/exit, and key handling
* feat(dev): one row per plugin in the service table
plugin-ui:* companion rows merge into their parent as an inline
'· ui :<port>' annotation (the auth app slot renders as one PLUGINS
row, e.g. 'AUTH (local) running :3002 · ui :3011'); SERVICES lists
only host/api/ui; a merged row counts as ready only when both its api
and ui surfaces are ready, and 'All N services running' counts merged
rows. Sectioning, display names, colors, and column widths live in the
shared render module consumed by both the TTY and non-TTY paths — the
plugin: prefix check that orphaned plugin-ui rows into SERVICES is
gone.
Auth-mirror detection is single-sourced in isAuthMirrorPluginEntry
(structural AuthSlotShape signature): the inline copies in the infra
planner (allocateServices, buildServiceDescriptors, assigned runtime
config), the dependency DAG, and the api contract bridge are deleted.
allocateServices now also matches remote-url mirrors, fixing its prior
allocate/descriptor disagreement; planner tests pin the semantics.
* fix(dev): TUI quit escalation + viewport-bounded repaint — quittable sessions, no scrollback bleed
* docs(plans): CLI audit plans 037-041 + ADR 0012 + plan 036 amendments (env/port truth, registry lock, docs ride-along)
* fix(dev): repaint erase-to-EOL, renderer dead after unmount, shutdown messages visible (plan 037 phase 1)
* fix(every-plugin): watcher kill escalation + parent-death supervision — rspack/rsbuild watchers can never orphan (plan 042)
* fix(dev): quittable at every lifecycle phase — startup quit kills spawned children, no self-SIGKILL, honest exit codes (plan 037 phase 2)
* test(regression): Go teardown suite — run a real dev stack, kill it, prove everything is dead (036/037/042 acceptance)
* refactor(regression): rename teardown suite to framework — home for everything-dev-level regression/e2e
* feat(everything-dev): bos kill escalation + orphan adoption + verified port release (plan 036 phase 1)
* feat(everything-dev): atomic port-block allocation — announced drift, persist-only-explicit, lease-shaped registry (plan 036 phase 2)
* refactor(everything-dev): scope-owned child handles — acquireRelease teardown by construction (plan 036 phase 3)
* docs(agents): port-allocation model matches ADR 0012 — block layout, persist-only-explicit, escalating kill (plan 036 phase 4 docs ride-along)
* docs(plans): 036 DONE — port leases and scoped stack landed through phase 4
* fix(dev): display/lifecycle defect batch — non-TTY log freeze, --interactive non-TTY fallback, post-ready death status, detectStatus false positives, spawn-error surfacing, per-session log files (plan 037 phase 3)
* fix(everything-dev): env & port truth — preflight probes credentialed DB URLs, origin keys generated-owned at every tier, DB ports honor persisted state (plan 038)
* fix(everything-dev): security hardening — validated plugin keys, no shell spawn, TLS default, fail-closed SRI, key file perms, masked drift logs (plan 039)
* perf(verification): root chain runs framework suites; dev spawn unblocked from network checks; config tests stub the network (plan 040)
* chore(everything-dev): CLI hygiene batch — dead deps removed, zod as dependency, catalog pins, no private-API casts, doc truth, dedupes (plan 041)
* docs(plans): 037-041 status rows — all DONE with commit refs
* chore(everything-dev): vitest pool tuning measured and rejected — singleFork doubles wall time and leaks module state; keep isolated parallel forks
* fix(everything-dev): resolve dev-latest by session start time, not mtime (CI-stable tie-break)
* fix(everything-dev): block-allocate the auth mirror's ui port again
The atomic port-block allocator (plan 036 phase 2) restructured the
plugin loop into a whole-entry mirror skip, dropping the plugin-ui:auth
port allocation that pre-regression allocateServices granted outside the
!mirror check. With no port, the planner's patchedUi left the mirror's
ui.url empty and the host advertised the auth remote as a relative
/remoteEntry.js — the browser could never load the auth plugin UI, so
/login never rendered (dev:ssr + dev:csr regression suites red from
2674acc onward).
Restore the pre-regression semantics under the block model: the mirror
still gets no plugin:<id> api entry, but a local mirror with a local ui
gets its plugin-ui:<id> entry allocated, which patches
runtimeConfig.plugins.auth.ui.url and feeds the folder-form auth
descriptor's BOS_UI_PORT.
Also sanitize the regression stack log filename (regression-dev-ssr.log)
— the mode's colon made upload-artifact reject the whole artifacts
upload, hiding the very logs needed to debug this.
New planInfra regression test pins the mirror ui allocation and the
patched ui.url.
* fix(everything-dev): keep harness-injected origins in config-path production starts
A registry start is a real deployment — localhost CORS_ORIGIN/BASE_URL
there are stray dev leftovers and stay purged. An explicit --config-path
start (regression harness, local production stack) injects those origins
as deployment truth; deleting them made the in-process host derive the
auth origin as https://<domain>, so better-auth issued Secure cookies no
http client could send back (empty sessions, 401s, INVALID_ORIGIN).
* fix(everything-dev): purge localhost origins only on registry starts
The gate moves from !configPath to a positive isRegistryStart check:
bare local bos start runs (no --config-path, no BOS_ACCOUNT/BOS_GATEWAY)
load the local config and are local starts too — their origins are
deployment truth, not stray dev leftovers. Adds unit tests for all
three config-source outcomes and a changeset.
* fix(dev): quittable TUI — resume raw stdin so q/Ctrl-C reach the key handler
Bun's TTY stdin never enters flowing mode from a bare data listener
attach. With the dev TUI's raw mode on, ISIG is disabled, so Ctrl-C
generated no SIGINT, and the \x03/q bytes never arrived either — the
session was unquittable from its own terminal (bos kill from a second
terminal was the only exit). The renderer now resumes stdin after
enabling raw mode; unmount pauses it.
Quit-path cleanup in the same sweep:
- devApp's SIGINT/SIGTERM handler now delegates to the same
requestShutdownEscalating the TUI uses (the two bodies had drifted
into twins); signal counting lives in one place
- emergencyKill reuses reapGroup instead of re-implementing the
group-kill fallback
- suspendForceExitTimer renamed rearmForceExitTimer — it extends the
deadline, it does not suspend it
- l (export logs) no longer counts toward the quit escalation, so
l-then-q quits gracefully instead of force-exiting past the export
- force-exit timer is unref'd on both paths
Regression coverage: the framework suite gains a pty harness
(creack/pty) and two tests that boot a fully interactive stack, wait
for the TUI's alt-screen mount, then quit via q and via the raw Ctrl-C
byte — asserting the CLI exits 0, every service child and watcher
grandchild dies, ports free, and the registry cleans up. Both failed
(red) before the resume fix; both pass after.
* fix(everything-dev): drop the SERVICE_CONFIGS annotation duplicated by the satisfies check
The rebase merge of main (post-#179) kept BOTH the Record<string,
Pick<...>> annotation and the branch's trailing `as const satisfies`.
The annotation widens the const back to Record, so under
noUncheckedIndexedAccess (plugins/apps tsconfig) every dot access
yields Pick | undefined and the descriptor spreads became optional —
typecheck failed on the auth descriptor. Keep only the satisfies form:
same contract enforcement, literal keys stay required.
* feat(dev): surface folder-form plugin UIs in the TUI — auth row shows its ui port
Folder-form plugin UIs (ui/ dir inside the plugin workspace, no own
package.json) run as a child of the plugin's dev process via
BOS_UI_PORT — no plugin-ui:<id> service is spawned, so the one-row-per-
plugin renderer had nothing to merge and the port was invisible unless
you tailed the log stream ([auth] ├─ 🎨 UI: http://localhost:3011).
ServiceDescriptor now carries uiPort next to the BOS_UI_PORT env in
both folder-form branches (regular plugin + auth mirror);
getProcessStates threads it into the process state; mergePluginUiRows
synthesizes the same inline annotation package-form companions get.
The auth row now reads: AUTH (local) running :3002 · ui :3011.
* fix(auth): single-owner session read path — everything-dev/ui/auth as a strict MF singleton
The post-sign-in redirect loop was fixed three times (#162, #175, #178)
without staying fixed: the invariant (one session queryFn, always
disableCookieCache) had to hold identically across independently deployed
bundles — the read path was compiled separately into the core ui and every
plugin ui, and mixed deploys ran divergent copies whose guard decisions
ping-ponged past the router limit.
- everything-dev/ui/auth joins the ui share list as a strict singleton
(core ui provides, plugin uis consume with import: false — zero bundled
fallback): one runtime copy; version mismatch fails loudly instead of
loading a second disagreeing copy. Version resolves from the building
workspace's installed package, not the catalog: range (plan 010
single-resolver principle)
- guards move to the platform package: requireSession/requireAdmin +
plugin-path helpers + clearAuthenticatedQueries now live in
everything-dev/ui/auth (framework-home convention, amended #89) — the
login↔authenticated redirect pair is owned and unit-tested in one place
- sync surface shrinks: ui/src/lib/auth-guards.ts, ui/src/lib/plugin-path.ts,
and the drifted plugin session-cache.ts copy exit child ownership; the
plugin copy still carried the WeakSet bootstrap bookkeeping #178 removed
from the ui copy
- ui/src/lib/session-cache.ts keeps only resolveSessionFromCache (SSR↔
query-cache hydration bridge for the sync-owned __root.tsx)
ADR 0013; follow-up tickets 12-14 (api-auth framework export, route-config
ssr flag drop, retire plugin-path ceremony). Typecheck 9/9, lint clean,
everything-dev 615, ui 384, auth-plugin 244, host 207, api 131, every-plugin
92, template/apps/proposals/votes green; provider/consumer manifests carry
the shared entry with matching requiredVersion.
* refactor(build): config factories resolve from src; the train owns dist freshness
First-principles exit from the dist-staleness class (two resolution rules,
ADR 0013):
1. Bundler-configuration code resolves from source — the mf-build and
build/rspack factories run only at build time from the working tree, so
resolving them through built dists created an invisible build dependency
(proven: a stale every-plugin dist silently dropped the shared
everything-dev/ui/auth entry from the ui manifest with a green build).
every-plugin ships src in its tarball, so its ./ui/mf-build and
./build/rspack subpaths now default to src in every condition; the
everything-dev/ui/mf-build re-export shim is deleted (one consumer —
ui/rsbuild.config.ts now imports every-plugin/ui/mf-build directly,
matching the generated plugin configs). Verified: a ui build succeeds
with every-plugin's dist deleted outright, manifest intact.
2. Shipped code resolves dist — runtime subpaths (everything-dev/ui/auth,
db, every-plugin's shared runtime) are dist-resolved, and
buildWorkspaceTargets unconditionally staleness-checks the framework
prerequisites (every-plugin, everything-dev, better-near-auth) before
any target. The train (bun run build / deploy) is the only supported
build path; AGENTS.md documents it.
Removes the interim manifest-assertion tripwire (proven placebo: the
manifest is generated from the config chain, so config-vs-manifest checks
agree with stale chains). Plugin unit shape (src -> api/src,
plugin.dev.ts -> bos.dev.ts, per-unit bos.app.ts) and CI train consumption
ticketed (issues 15-17).
* fix(build): every-plugin subpaths resolve src under bun, dist under node
The default->src flip broke scaffolded children: node refuses to
type-strip .ts under node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_
STRIPPING) — 'node node_modules/.bin/bos types gen' in bos init's
integration test died loading the factory from src. The correct
discriminator is the runtime, not the NODE_ENV: bun (the workspace
runtime, any NODE_ENV) resolves src via its always-on 'bun' condition —
config factories can never go stale in-workspace; node consumers
(published CLIs in children) resolve the immutable published dist.
The 'bun' arm now covers every every-plugin subpath — this also fixes a
latent bootstrap chicken-and-egg (rm -rf every-plugin/dist && bun run
build died: the bos CLI itself imports every-plugin subpaths that only
resolved via dist without the development condition; the train now boots
from src and staleness-rebuilds the dist itself — verified).
mf-build tests move to every-plugin's suite with a relative src import
(resolver-agnostic, always current). ADR 0013 wording updated;
init.full verified green under CI=true locally.
* refactor(auth): session-cache ownership completes — resolveSessionFromCache joins everything-dev/ui/auth
Answers the PR review: the SSR↔query-cache hydration bridge is router glue
better-auth has no concept of (their docs' SSR pattern is Next-shaped:
server resolves, pass via props — for TanStack the adapter goes through
Query dehydration because the guards read the query). It exits child
ownership like the rest of the session surface: ui/src/lib/session-cache.ts
deleted, __root.tsx imports via @/lib/auth, tests moved to the package.
Ticket 14 updated: endgame is generated route types from the compose
manifests (keeps 'to' — search/preload/active semantics href can't give);
interim choice pending discussion.
* docs: clean up plan archives, wayfinder tickets, and phantom references
- delete .scratch/orpc-v2-migration (numbers.env issue map, unreferenced)
- plans/README.md: drop phantom ui-route-grafting-migration file link
(kept as plain text + issue #108), prototype count 3→4, fix
03-plugin-type-deps typo
- recreate wayfinder decision tickets 08–12 as decision-record stubs
(decisions already recorded in README/map) and refresh ticket counts
- ADR 0007: replace dead ui-route-grafting-migration links with inline
supersession note (ADR 0008); fix stale plan-035 link in ADR 0007
platform-bundle-storage; point ADR 0006 at the archived plan 022
- advisor-plans: renumber wallet/teams batch to 044–048 (resolves 025–029
and 028 collisions), add README rows for 044–048 (DONE via PR #136) and
030–032 (TODO, unexecuted), archive DONE/superseded/absorbed plans to
done/ with a slim done/README.md preserving status notes, prune
dependency notes that only concern archived plans
* docs: fix plans/README.md staging miss from the cleanup commit
* fix(deploy): hermetic boot — the image consumes what it stages (ADR 0011)
A self-contained runtime image fetched its own plugin manifests through
its own public origin at boot; when that origin 502'd (Railway restart
loop, gateway hiccup, cold start ordering) nothing could ever come up —
the boot depended on the thing it was booting. Registry claims survived
container restarts via PID reuse and wedged the pinned host port, making
the loop permanent.
- BundleResolver service (Effect v4, Context.Service + Layer) resolves
own-namespace /bundles/<account>/<gateway>/ URLs from BOS_BUNDLE_DIR;
foreign namespaces and unset dirs fall through to the network fetch,
so registry-tier children are unchanged
- One global-fetch adapter (ManagedRuntime bridge) installed at CLI boot
covers every boot-time consumer: config manifest discovery, contract
types, orchestrator host loading, MF remoteEntry probes
- bos start resolves configs with production env when NODE_ENV=production
(kills the dev-mode raw-config load against the stripped image)
- Remote-source services no longer allocate ports; stale claims are
pruned via recorded process generation; pinned-port conflicts verify a
real listener; InfraError/DevStepError report instead of escaping
- bos.config.json carries the deterministic post-publish bundle URLs
- Dockerfile healthcheck start-periods cover cold-boot duration
Verified: runtime image boots healthy with citynode.app blackholed and
recovers across docker restart; typecheck 9/9, lint clean, 631 framework
tests green.
* docs(adr): amend 0011 (outbound local-first) and 0012 (claim identity, listener authority)
* refactor(everything-dev): api-contract fetch/checksum path as an Effect service
Lifts the manifest/contract-type/auth-export fetch path into
ApiContractResolver (Context.Service + Layer, Effect.fn, Schema.TaggedError:
ApiManifestFetchError, ApiManifestFormatError, MissingContractTypesError,
ContractTypesFetchError, ContractTypesChecksumError, AuthExportFetchError).
Behavior-preserving: fetchApiPluginManifest / remoteContractSource /
fetchAuthExportTypes keep their signatures and messages (bridges flatten
tagged errors to Error with identical strings); transport stays on the
http-client promise bridges so the [http] diagnostics and 30s GET cache are
unchanged. syncApiContractBridge untouched.
* feat(child-tier): foreign-namespace bundle proxy + stale-if-error cache (ADR 0011)
The child tier loads base workspaces from foreign origins; an origin outage
hard-failed browsers (502) and the child's cold boot. Two entrances share one
disk cache (BOS_BUNDLE_CACHE_DIR, default .bos/bundle-cache — deliberately
separate from BOS_BUNDLE_DIR: cached bytes are a resilience artifact, never a
deployment):
- Host: /bundles/* falls through to createBundleProxyCacheHandler after the
FS handler — namespace→origin map derived from the runtime config's slot
URLs (self-origin namespaces never proxied); success writes through, origin
failure serves last-known-good bytes with x-bundle-cache: stale, no cache +
failure → 502. GET/HEAD only.
- CLI fetch adapter: foreign /bundles/… URLs get the same stale-if-error
treatment, so boot-time outbound fetches survive a base-origin outage after
the first successful boot. Inert for plain dev sessions (no BOS_BUNDLE_DIR/
BOS_BUNDLE_CACHE_DIR) — default behavior unchanged; stale serves only on
origin failure, so normal operation keeps serving fresh.
Shared cache primitives live in everything-dev/bundle-cache (subpath export;
the host already imports everything-dev/* subpaths).
* refactor(everything-dev): code-artifacts as Effect.fn with tagged ArtifactGenError (C1)
generateCodeArtifactsEffect is the Effect-native core (tagged
ArtifactGenError per phase: write resolved config, load resolved config,
sync api contract bridge, generate ui manifest); the exported
generateCodeArtifacts keeps its Promise signature as a bridge — callers
(dev-program, publish) unchanged. The Effect export is available for the
later dev-program adoption.
* refactor(everything-dev): config.ts error taxonomy as Schema.TaggedError (C2)
All thrown Errors become tagged errors (ConfigNotLoadedError, ConfigLoadError,
CircularExtendsError, ConfigNotfoundError, ConfigExtendsError) — every one an
Error subclass with the identical message string, so all plain callers'
.message reads and the wrapped-cause behavior are unchanged. This gives the
later Effect-native phases a typed error channel instead of string matching.
* refactor(everything-dev): config pipeline as Effect.fn cores (C3+C4)
loadResolvedConfig, resolveRuntimePlugins, and buildRuntimeConfig become
Effect.fn generators with a typed error channel; exported signatures stay
Promise-based via bridges, so no caller changes. Error taxonomy (C2) carries
the channel: ConfigLoadError wraps at the load boundary with the identical
"Failed to load config from …" message; inner failures propagate raw before
that wrap, matching today's throw/wrap order exactly.
- resolveRuntimePlugins: Promise.all → Effect.forEach (unbounded, order-
preserving); per-plugin resolution via tryPromise with ConfigExtendsError
- buildRuntimeConfig: sync body stays inline; the two async islands (auth
entry, manifest discovery) become tryPromise steps — the catch-all
discovery diagnostics stay plain inside the promise (same warn messages,
same degrade-to-warning semantics), keeping console out of Effect code
- loadResolvedConfig: the pipeline composes the Effect cores; the
suppress/drain/resume warning discipline is preserved call-for-call
(resume on both paths via the inner catch, drain on success only)
Behavior-preserving throughout: 634 framework tests green (config-resolved
golden tests pin the outputs), typecheck 9/9, lint 0 errors.
* ci: relocate the remote-runtime smoke to the Deploy workflow (ADR 0009 §4)
runtime-remote.test.ts is a live-network smoke: it reads the branch's raw
bos.config.json and proxies real traffic (/skill.md, /llms.txt) to
config.app.ui.production. Once #228 commits the deterministic bundle URLs,
that origin is the deployment this PR ships — a PR CI suite that depends on
production's uptime fails whenever the gateway blips, and before the deploy
it validates URLs that cannot serve yet (Host tests red: "expected 502 to be
200", 60s hook timeouts).
The split was already prepared but never wired: vitest.remote.config.ts owns
exactly this file and host has a test:integration:remote script with no
consumer, while the default vitest.config.ts include swept it into PR CI.
- Default host suite excludes tests/integration/runtime-remote.test.ts
(configDefaults preserved) — PR CI no longer touches live origins
- deploy.yml runs the smoke after mf check: boots the real host program
against the live production URLs — SSR compose from the deployed ui,
doc-asset proxy, api ping, root document contract — the functional gate
beyond mf check's identity-only check
No helper changes, deliberately: in the Deploy context, non-2xx origins
make the smoke fail loudly — correct for a post-deploy gate; a down site is
already caught by mf check's retry loop.
* fix(host-tests): pin the runtime-config fixture to development env
loadTestRuntimeConfig called loadResolvedConfig() with no env — with the
NODE_ENV-honoring default (hermetic boot), the host test script's
NODE_ENV=production resolved production env in the fixture: api source
remote → manifest discovery fetched the deployed origin (down, 10s×3 retries)
→ every beforeAll hook blew the 10s timeout → 6 suites failed at collection
(csp-compose, csp-nonce, security, seo, ssr-metadata, ssr).
Fixtures resolve the repo's own local workspaces; NODE_ENV=production in the
host test script exists for the host's production code paths, not for config
resolution. Pin env: "development" — live-origin coverage belongs to the
Deploy workflow's smoke (relocated earlier in this PR).
* feat(api,ui): generalize the node model beyond geography
Drop the node_kind enum and the nodes.kind column — the kind label moves
into nodes.metadata.kind — and make nodes.tenant_id nullable so
standalone org/user/zone-root nodes can exist without a tenant.
parentId is now the only hierarchy axis.
- New org-scoped spawnNode surface (POST /nodes/spawn): any kind, any
parent, no kind-validated parentage or depth limit; kind and geo
details are metadata.
- applyNodeProposal keeps the strict country→state→city ladder as the
DAO geo-provisioning path (tenant + validator + binding stay atomic).
- Validator staking walk and listTenantApps are unchanged — already
parent_id-driven — with kind labels read from metadata.
- Standalone nodes (null tenant) can only be mutated by platform admins.
- Migration 0004 backfills metadata.kind and is replay-idempotent
(journal-wipe safe; no procedural SQL — the virtual migrations loader
splits statements on semicolons).
- UI kind displays tolerate arbitrary labels; NodeSchema.kind and
NodeSchema.tenantId are now nullable.
* refactor(api,ui): Effect-native node surface + review fixes
Code-review follow-ups on the node-model generalization, both axes:
Standards:
- NodesService is now Effect-native (proposals-plugin precedent): every
method returns Effect<T, ORPCError<string, unknown>>, implemented with
Effect.gen; drizzle queries adapt at the promise boundary via
Effect.tryPromise + toOrpcError, business failures via Effect.fail.
- All node oRPC routes are .effect(function* ...) handlers with
yield* ApiServices — no plain Context.get on the node surface;
createNode/updateNode/deleteNode/listNodes/getNode/getSubtree/
getNodeSummary/resolveNodeBySlug/spawnNode included.
- resolveNodeForAccess/authorizeNodeAccess are shared Effect helpers;
the standalone-admin gate and tenant-ownership check live in one
place instead of three copies per handler.
- nodeKindOf (api) and nodeKindLabel (ui) extract the repeated
kind-label/metadata-extraction shapes; kind inputs require min(1).
Spec:
- spawnNode now requires platform admin for standalone (null-tenant)
spawns — closing the mutate-without-spawn asymmetry — and validates
parentId ownership (org members can only graft under their org's
tenants; admins bypass as with reparenting).
- mergeKindMetadata: the explicit kind argument now wins over
metadata.kind; update still relabels via metadata.kind alone.
- createNode keeps its required kind input (unrequested relaxation
reverted to an open string, not the geo enum).
- CONTEXT.md node definition generalized.
Tests: harnesses compose Effects (Effect.suspend + Effect.isEffect);
three multi-step node test bodies flattened to single-call runService
chains; new tests for kind precedence, standalone-spawn admin rule,
and cross-org graft rejection. 152 api + 431 ui tests green.
* refactor(api,ui): tier-1 cleanup — shared tenant guard, test harness, kind labels
- requireTenantOwnedByOrg: one Effect guard for the duplicated
createNode/spawnNode tenant-resolve + org-ownership prologue
- createServiceHarness (api/tests/unit/test-harness.ts): the
runService/squashServiceError pair consolidated from three copies
(nodes/validators/tenants unit tests) into one Promise|Effect
-composing harness with a single TestRunError unwrap
- admin nodes kind-filter labels derive from nodeKindLabel +
geoNodeKinds instead of a second hand-written label map
Tier 2 (Effect-native Tenants/Validators/Discovery conversion) filed
as #252.
152 api + 431 ui tests green; typecheck + lint clean.
…effect barrels) (#260) * chore(framework): remove dead every-plugin facade remnants (zod/orpc/effect barrels) The facade barrels (every-plugin/zod, every-plugin/orpc, every-plugin/effect) were deleted by the effect-native-plugins changeset, but dead remnants taught the deleted convention and pointed at files that no longer exist: - packages/everything-dev tsconfig: drop the every-plugin/effect, /orpc, /orpc/openapi, /zod, and /zod/v4/core path aliases (targets deleted) - packages/every-plugin tsconfig: replace the every-plugin/* wildcard with the one subpath it actually carried (build/ui) - skills + _template/LLM.txt: teach direct imports (zod, @orpc/*, effect) instead of the deleted barrels Why this matters: the deployed auth plugin's embedded types/contract.d.ts was emitted before the barrel deletion and still imports z from every-plugin/zod. Child projects scaffolding via bos init cannot resolve that subpath (no exports entry, never had one), and skipLibCheck silently degrades z to any — which collapses AuthContext to any, erases the WithEffectContext intersection in the oRPC builder context, and makes @orpc/experimental-effect type .effect() handlers' yield channel as Effect<any, any, never>. Effect-native handlers that yield services (#250's yield* ApiServices pattern) then fail TS2345 in the scaffolded child's typecheck while the parent repo passes (it resolves its locally emitted declarations). init.full.test.ts is the canary for this — it keeps the real fetch path and stays red until the merge-triggered deploy refreshes the deployed manifest. * ci: run Framework tests when api/ or plugins/ change #250 generalized the node model with Effect-native handlers in api/src/index.ts and merged with Framework tests skipped — the path filter only triggered them on packages/everything-dev changes. init.full.test.ts scaffolds a child from api/ and plugins/_template, so changes to those paths must trigger it too. * test(everything-dev): skip the scaffolded-api typecheck while the deployed auth artifact is stale The scaffolded api typechecks against the auth plugin's deployed contract declarations fetched through the extends chain. The deployed artifact still imports z from "every-plugin/zod" (unresolvable since the facade-barrel deletion), which collapses AuthContext to any and fails the Effect-service handlers with TS2345. Detect the broken import in the fetched declarations and skip the api assertion with a loud warning until the auth plugin is redeployed — everything else (scaffold, install, types:gen, plugin typecheck) stays enforced.
…255) * refactor(api): Effect-native Tenants, Validators, Discovery services Convert the three remaining promise-based services to the NodesService Effect-native shape (methods return Effect<T, ORPCError<string, unknown>>, Effect.gen bodies with drizzle adapted at the promise boundary): - TenantsService, ValidatorsService: flat Effect.gen with a shared query helper; unique-violation sites map isUniqueViolation to CONFLICT before toOrpcError; transactions keep db.transaction promise callbacks - DiscoveryService: fully converted internals — authorize/eligible/ publicActivity/list/syncLuma/syncDueLuma all Effect-returning; background Luma sync uses Effect.forkDetach with a synchronous single-flight flag; per-connection failures still record to discoveryLumaConnections.error - api/src/index.ts: all tenants/validators/discovery routes are now .effect(function* ...) handlers with yield* ApiServices; authorizedTenant and authorizedNodeForValidators are Effect guards; validateAccountId/ validateHostname keep BAD_REQUEST in the error channel; createEventOn- boardingCode loads the event through the direct error pathway (no more message-munging catch); asOrpcEffect deleted No contract changes — routes, inputs, outputs, and error codes identical. Closes #252 * refactor(api): Effect-native verifyDaoMembership — direct yield, no toOrpcError in routes - verifyDaoMembership returns Effect<VerifyDaoMembershipResult, ORPCError>; the retry loop keeps its exact semantics (3 attempts, last error surfaced in the BAD_REQUEST cause) via Effect.result + Result.isSuccess - createTenant and applyNodeProposal routes yield it directly — the last two toOrpcError adapter sites in api/src/index.ts are gone; toOrpcError now lives only inside the services' query funnels - integration test mocks return Effect.succeed (node-proposals mockResolvedValueOnce → mockReturnValueOnce)
…ion stacks (#258) * fix(db): tolerate concurrent-migration DDL race; fail loud on regression stacks Root cause of the flaky dev:ssr/dev:csr auth-redirect failures: dev stacks boot each local plugin twice concurrently (its own dev server process and the host's in-process MF load), and both auto-apply migrations against the fresh database. Concurrent CREATE TABLE collides in pg_catalog — the loser gets SQLSTATE 23505 on pg_type_typname_nsp_index, which the SAVEPOINT duplicate-DDL tolerance (42710/42701/42P07 only) did not recognize. The escaping DatabaseError classified as unknown -> permanent, so the #253 fail-fast load policy killed the auth plugin where the old 120s silent retry used to self-heal. The host then served without /api/auth/*, and the only two browser specs that need an authenticated session timed out. - db/core.ts: isConcurrentDdlUniqueViolation — 23505 whose constraint/ message matches the pg_catalog allowlist (pg_type_typname_nsp_index, pg_namespace_nspname_index, pg_class_relname_nsp_index). Data-level unique violations stay fatal. - db/run-migrations.ts: SAVEPOINT catch tolerates the catalog collision; each migration transaction retries on deadlock/serialization/lock/ connection states (isRetryableMigrationExecutionError — deliberately narrower than the journal-init set so duplicate/unique classes are either tolerance or fatal, never retry). - browser global-setup: abort the suite immediately when /health reports structured plugin-load failures instead of failing specs with downstream timeouts. everything-dev: 705 tests green (11 in db-run-migrations); bun typecheck and bun lint clean. * refactor(db): dedupe cause-chain walking; import PluginLoadFailureInfo Two-axis review follow-ups on the tolerance commit: - core.ts: one visitCauses walker now backs isRetryableMigrationError, isRetryableMigrationExecutionError, isConcurrentDdlUniqueViolation, and run-migrations.ts isDuplicateObjectError (was the same bounded loop four times, at two depths); CONCURRENT_DDL_CONSTRAINTS is a ReadonlySet to match neighboring sets; 55P03 joins RETRYABLE_SQLSTATES for parity with the statement-level set. - run-migrations.ts: isTolerableDuplicateDdl drops its never-exercised default parameter — the sole call site always passes duplicateSqlStates. - regression global-setup: consume every-plugin's PluginLoadFailureInfo instead of re-declaring a drifting subset as HealthFailure. * feat(db): serialize migrations on a journal-scoped advisory transaction lock The structural fix (ADR 0019) for the concurrent-migration race class: runMigrations now executes the entire run — advisory lock, journal schema/table creation, data-schema creation, journal read, per-migration DDL, and journal inserts — inside ONE db.transaction whose first statement is pg_advisory_xact_lock(hashtextextended('<journal>.<table>', 0)). Drizzle's transaction pins one pooled client for the callback, so lock, journal read, and writes share one session; the xact-scoped lock releases at COMMIT/ROLLBACK or connection death — a crashed migrator can never orphan it, and no acquire/release pairing exists to leak. A concurrent migrator (dev double-boot: plugin dev-server + the host's in-process MF load; parallel vitest files; overlapping prod replicas) blocks bounded by the driver's lock_timeout, then re-reads the winner's committed journal rows and applies nothing. - run-migrations.ts: runLockedTransaction replaces the per-stage flow; journal-init retry is absorbed (its race cannot happen under the lock); the preflight 'record as applied' write moves inside the locked transaction; SAVEPOINT tolerance stays as defense-in-depth for non-participants (drizzle-kit). Whole-tx Effect.retry on 55P03 / deadlock / serialization / connection states. - driver.ts: ensureNamespaceExists takes the same lock before CREATE SCHEMA so it cannot race a migration run. - migrate-test-db.mjs: the regression pre-migration mirror takes the same lock in one explicit transaction. - tests: 55P03 lock-timeout retry characterization; two-concurrent- runners race test gated on TEST_DATABASE=postgres against the committed test DBs (verified 5/5 locally on real postgres: loser applies 0, one journal row per hash). - ADR 0019 records the decision, residuals, and the future deploy-time (alchemy) tier; changeset bumped to minor.
…yer gating (#259) * test: remove low-signal unit tests, add E2E coverage for 404 and relayer gating Delete 26 low-signal test files (mock-echo/tautology assertions subsumed by browser E2E), trim tautological describes from 6 more, and replace the two real coverage gaps with regression browser specs: - not-found.spec.ts: 404 fallback renders with a working back-home link - admin.spec.ts: relayer connect-before-fund gating via injected admin cookies; wallet-gated invalid-amount state represented as test.skip - playwright config: retain traces on failure Add testing-rules subsection to AGENTS.md banning tautology tests and reserving unit tests for what E2E cannot reach. * test(ui): fix thing-details flake under loaded runners The vote button renders only after a four-round sequential query chain (proposal -> thing -> count + userVote -> view). Under a cold, loaded vitest worker the testing-library default 1000ms timeout can expire before the chain settles. Give the async assertions in this file an explicit 5000ms timeout.
…d train (#256) * fix(ui): kill the ?? "citynode.app" gateway default — honest missing-config states (#203) All seven sites derive the gateway from the runtime config via the new getGatewayId() accessor (null when missing/mis-shapen, never a default): render explicit error states, disable dependent queries, fail mutations with a clear message. Node directories fall back to a node's own hostname; the tenant wizard's Extends row shows the configured gateway. Also fixes two pre-existing noUnusedFunctionParameters lint errors in api/src/index.ts and normalizes a stale bos-ui bin entry in bun.lock. Closes #203 * fix(host): fail-loud sweep — dropped errors, honest timeouts, probe in health (#204) - attestation catch logs the error cause instead of a causeless warning - the auth 504 path logs the underlying Better-Auth failure it used to swallow - AUTH_TIMEOUT_MS parses defensively: garbage and non-positive values fail boot loudly; unset keeps the 30s default - both federation noop catches log at debug with the cause (logger gains debug) - the post-listen self-probe surfaces its outcome in /health (ssr.selfProbe); a failed probe degrades the overall status; the unreachable "composing" acceptance in the health check is removed - an empty auth origin in production fails boot loudly — the development localhost:3000 fallback stays (deliberate, parseTrustedOrigins-owned) Closes #204 * fix(everything-dev): fail-loud sweep — env precedence, loadEnv, warning finalizers, log follow (#206) - descriptor env folds into the generated tier before composeSpawnEnv — shell-exported values keep outranking it (documented three-tier precedence, now test-pinned) instead of silently outranking everything - a failed .env load propagates into the database-binding error - bos logs --follow refuses a missing/unknown log file; the readFile rejection is handled and the watcher closes on rotation - suppressWarnings around runtime-config builds is failure-safe (acquireUseRelease) at both the development and start sites - DB_*_TIMEOUT_MS parse defensively: explicit 0 disables, garbage fails boot - the start path passes the generated env tier through (DevSessionData requires it; runApp takes its tiers without silently-empty defaults) Closes #206 * ci(build): consume the build train — dedup the sprinkled prerequisite builds (#209) SSR-mode decision (measured): every-plugin's ui build already carries the explicit escape hatch — includeNodeEnv honors BOS_SSR=1 / DEPLOY=true (packages/every-plugin/src/build/ui/rsbuild-config.ts:158) — so jobs that need SSR dists set BOS_SSR=1 and the train keeps its development-mode NODE_ENV forcing untouched. No ambient-NODE_ENV games. - the 12 hand-maintained prerequisite-build steps across 7 jobs collapse into one train call per job (bun run build template — quiet, staleness-checked every-plugin/everything-dev/better-near-auth + target); the lint job's early builds are deleted outright: audit/lint/typecheck are src-level via the development export condition (ADR 0018) - host/tests/global-setup.ts ensureUiBuild routes through the train (bun run build ui, BOS_SSR=1) with a missing-or-stale dist check — the silently-stale path is gone Closes #209 * fix(host): build the test ui dist directly — the train bakes the dev assetPrefix into SSR Two host-test failures on this branch: 1. buildAuthBaseVariables threw 'Invalid URL' on an empty dev host url — originSource = '' is not nullish, so the ?? fallback never applied. Use a truthiness guard; the empty dev case stays a deliberate pass-through (parseTrustedOrigins owns the localhost fallback) and the production fail-loud throw is unchanged. 2. The SSR integration suites (csp-nonce, seo, ssr-metadata, ssr) failed in beforeAll with ECONNREFUSED localhost:3003 — 'cleanup is not a function' was just the cascade (beforeAll threw before assigning cleanup). global-setup built the ui through the build train, but bos build forces NODE_ENV=development (only --deploy flips it), so rsbuild baked the dev assetPrefix (http://localhost:3003/) into the SSR container and the tests fetched shared deps from a dev port nothing listens on. Build the ui directly with the inherited NODE_ENV=production instead; framework sources resolve from src in tests (vite-tsconfig-paths), so no train prerequisites are needed. The staleness check stays.
…me (#207) (#257) * feat(everything-dev): api subpath — createAuthMiddleware framework-home, collapse the five copies (#207) - new everything-dev/api subpath (src in dev, dist in prod — the ui/auth pattern) exporting createAuthMiddleware, generic over the workspace's auth context: createAuthMiddleware<AuthContext>(builder) preserves the exact narrowing the concrete copies provided (AuthContextShape is the structural minimum the guards read) - api + _template/apps/proposals/votes import from the subpath; the five near-identical sync-owned copies are deleted - bos sync drops lib/auth.ts ownership (api file entry + the plugins lib/auth regex — context.ts stays owned); AuthContext/ AuthOrganizationContext now source from the generated auth-types.gen - changeset: breaking sync-surface change for children Out of scope (unchanged): the DecoratedMiddleware/.use() typing limitation advisor-plan 007 called out. Closes #207 * test(everything-dev): sync structure — auth.ts is no longer framework-owned #207 moved createAuthMiddleware into everything-dev/api (framework home) and sync.ts stopped owning api/src/lib/auth.ts and plugins/*/src/lib/auth.ts — the test still asserted the old contract. Auth files are app-owned now; context.ts stays framework-owned.
…ng-config card (#263) * feat(everything-dev): scaffold agent workflow skills into child repos (bos init) bos init now copies .agents/skills/ (verbatim mattpocock workflow skills + repo-authored everything-dev-app orientation glue), docs/agents/ conventions, and skills-lock.json into child repos; bos sync owns all of it. Child AGENTS.md/skill.md/llms.txt surface the ordered development flow. Also fixes bos init crashing on TS-form (bos.app.ts) children: shared-deps sync receives the converted config explicitly and config resolution tolerates running before the first bun install. * feat(ui): node-config bundle auto-fill from a deployed app + My Node pending-config card Tickets 05 and 06 of the deploy-ux set (.scratch/deploy-ux/issues/): - Org node-config Custom UI bundle section gains "Fill from a deployed app": enter the NEAR account that ran bos publish --deploy, fetch its published config via the apps plugin, and fill the bundle URL + integrity (SSR too when allowed) instead of pasting and hashing manually. - My Node dashboard shows an "Awaiting votes" card for owners/admins when a DAO config-write proposal is pending, deep-linking into the org node-config tab. - CONFIG_WRITE_PLAN moved to the shared sputnik-proposals lib so both surfaces match the same plan.
…LS verification) (#264) Railway private-network Postgres presents a self-signed certificate chain no client CA bundle can verify, so bare non-local URLs failed plugin and auth boots with 'self signed certificate in certificate chain'. Those hosts are VPC-scoped private traffic — resolvePoolSsl now returns ssl: false for them, same as localhost / host.docker.internal.
…262) * docs(adr): universal runtime image + R2-backed child bundle storage (ADR 0020, 0021) ADR 0020: all bundle distribution moves to an R2 bucket behind cdn.everything.dev — root's own URLs included (dual home: image for boot, R2 for distribution), children never ship images. Amends ADR 0011 (image-native keeps the boot role only), reinstates the child half of ADR 0015 (oRPC storage route design, object-store backend). ADR 0021: one universal runtime image published to GHCR; tier is selected at boot by identity x staged bytes (tier auto-detection), not by config. Renames the deployable Dockerfile stage to 'runtime' (last, default target) and the regression fixture to 'regression'. * feat(api,everything-dev,host): universal image tiers + R2-backed bundle storage Phase 1 (ADR 0021): Dockerfile deployable stage named `runtime` (last, default target), regression fixture renamed `regression`; tier auto-detection (unstaged identity → registry tier + notice); inbound namespace guard on the host FS bundle route; BOS_BUNDLE_CACHE_DIR in the image; GHCR push by digest + pull-only Railway deploy. Phase 2 code (ADR 0020): BundleStorage Effect service (S3-compatible via aws4fetch — content-type + cache-control as first-class headers, per the cloudflare-cdn ticket-02 lesson; memory fallback); the POST /api/storage/bundles route (auth, account pinning, path allowlist, traversal rejection, size ceiling, server-side SRI) with a route-scoped host bodyLimit; bos publish CDN upload path — batched dist uploads, URLs at the CDN origin, integrity fields (ssr entrypoint hash separate from the web entry). Code-review fixes: sessions without a linked NEAR principal are refused (unpinnable), validateUploadSize used by the handler, SSR integrity no longer falls back to the web entry's hash, changeset split into single-frontmatter files, deploy pins the pushed digest. * feat!: CDN flip — all bundle URLs on cdn.everything.dev; host /bundles routes deleted Effect rc.112 → rc.117 (user-approved RC upgrade; fixes the latent effect/ByteSize tree skew via tsgo's hoisted platform deps); alchemy collapses into a root devDependency. infra/alchemy.run.ts: R2 bucket (retain, cdn.everything.dev domain) + bucket-scoped S3 credentials via AccountApiToken — the token's permission groups are Workers R2 Storage Bucket Item Read/Write over com.cloudflare.edge.r2.bucket.<accountId>/<bucketId>. Publish: the credential chain (BOS_STORAGE_API_KEY env → bos login session; storage origin env → session siteUrl → gateway; CDN origin env → the resolved config's inherited host slot) in cdn-deploy.ts; bos.config.json bundle URLs flipped to cdn.everything.dev; deploy.yml publishes with the CDN origin + BOS_STORAGE_API_KEY secret. Host: /bundles/* serving deleted (FS handler, proxy+cache, proxy-mode passthrough, oRPC mount) — the CDN owns distribution; the static-asset proxy base+path join no longer produces double slashes. Harness static servers mount the /bundles/<account>/<gateway>/<slot>/ base paths themselves. * chore: gitignore infra/.alchemy state * chore(infra,ci): parametrize the CDN domain — drop the citynode-specific storage origin deploy.yml drops BOS_STORAGE_ORIGIN: the chain derives it from the runtime's own gateway (citynode.app now, everything.dev post-merge). infra/alchemy.run.ts env-drives BOS_BUNDLE_CDN_DOMAIN (default cdn.everything.dev) — sovereign bases override without editing the stack. BOS_BUNDLE_CDN_ORIGIN stays explicit in CI as the determinism rail. * fix(every-plugin): dedupe the retry-logger test — bound failures instead of racing the retry budget The dedup test relied on the 1100ms retry budget expiring before the assertion; on CI the first-attempt timing could land such that the deduped log never fired (expected +0 to be 1). Two bounded identical failures then a success — same assertion, zero timing sensitivity. Also formats the BUNDLE_CDN_DOMAIN const (biome). * fix(ci): re-sandbox alchemy — the root devDep bloated every image build past the harness readiness budget The infra sandbox collapse put alchemy's peer tree (AWS SDK, sharp natives, vite, drizzle-kit rc) into the root lockfile; the Docker builder's frozen install downloads it on every image build and blew the start: regression readiness deadline (build >2m, main was 1m25s). alchemy returns to infra/ (own lockfile, committed for reproducible provisioning; node_modules gitignored); the root lockfile slims back toward main. .dockerignore excludes infra — provisioning tooling belongs in no image. The effect rc.117 bump stays. * fix(regression): the auth slot's URL carries its server's base path The auth static server mounts /bundles/<account>/<gateway>/auth but app.auth.production still pointed at the bare root — the orchestrator's auth remote load 404'd against the basePath guard, the plugin retried forever, and the start: stack never became ready.
…rashed the zone lookup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.