Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/bos-typecheck-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"everything-dev": minor
---

Add a `bos typecheck` command that runs TypeScript type checking across all local workspaces (host, ui, api, auth, and every plugin with a `tsconfig.json`), streaming errors inline and failing with a non-zero exit if any workspace fails. Root `bun run typecheck` now delegates to it.

- New `bos typecheck [packages]` aggregates pass/fail across all configured local workspaces instead of stopping at the first error.
- Framework source hardened for strict consumer configs (`noUncheckedIndexedAccess`): safe non-null assertions in `contract.ts`, `fastkv.ts`, and `api-contract.ts`.
- `plugins/apps` tsconfig aligned with the plugin template (`types: ["node"]`, DOM lib) so its typecheck passes.
5 changes: 5 additions & 0 deletions .changeset/curly-ghosts-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"everything-dev": patch
---

Fix `bos sync` leaking parent-only root `package.json` scripts into child projects. When syncing the root package, scripts are now filtered to the child-appropriate set generated by `buildChildRootScripts`, so parent-specific commands (regression tests, etc.) no longer appear in child projects and child script values (e.g. `typecheck`) stay correct.
4 changes: 4 additions & 0 deletions .changeset/four-bags-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add a `backcompat` regression test mode (`bun run test:regression:backcompat`) that boots a local host while loading the last published UI/API/plugin bundles via Module Federation, verifying the new host works against existing published bundles. Regression commands (`dev`, `prod`, `backcompat`) now run both HTTP and browser suites each time instead of stopping when the HTTP suite fails.
9 changes: 9 additions & 0 deletions .changeset/ui-anon-orgs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"ui": minor
---

Add a dedicated anonymous mount and reorganize organization routes.

- Add a `/_layout/_anon` pathless layout for pre-auth pages. Move login from the public layout into it; the layout redirects authenticated users to `/dashboard` and provides the theme toggle header.
- Rename the organization route group from `/organizations` to `/orgs` (`/orgs`, `/orgs/new`, `/orgs/$slug`) and move invitation acceptance to `/orgs/invites/$id`.
- Remove the stale nostr entry from the authenticated sidebar.
9 changes: 9 additions & 0 deletions .changeset/ui-layout-mounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"ui": minor
---

Reorganize UI routes into mount-point layouts and rename the authenticated workspace.

- Move admin routes under a new `/_layout/_admin` pathless layout that gates on the admin role and redirects non-admins to `/dashboard`. The tenant admin dashboard (`admin/admin/index.tsx`) and system page (`admin/admin/system.tsx`) now render as children of the admin layout through an `Outlet`.
- Rename the authenticated `/home` route to `/dashboard`, updating the sidebar, mobile tab bar, user nav, and login redirect fallbacks.
- Move the apps and things routes under the public layout (`_layout/_public/apps`, `_layout/_public/things`) so they render inside the shared public shell instead of the top-level layout.
7 changes: 2 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@

# app.host
CORS_ORIGIN=http://localhost:4100
TENANT_WHITELIST=
ALLOW_OVERRIDE=
ALLOW_UNTRUSTED_SSR=
CSP_STRICT=

# app.api
API_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5432/api_db

# app.auth
NEAR_SUB_ACCOUNT_PARENT_KEY_MAINNET=
NEAR_SUB_ACCOUNT_PARENT_KEY_TESTNET=
AUTH_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5433/auth_db
BETTER_AUTH_SECRET=
GITHUB_CLIENT_SECRET=
Expand All @@ -22,8 +21,6 @@ TWILIO_AUTH_TOKEN=
TWILIO_PHONE_NUMBER=
RESEND_API_KEY=
NEAR_RELAYER_PRIVATE_KEY=
NEAR_SUB_ACCOUNT_PARENT_KEY_MAINNET=
NEAR_SUB_ACCOUNT_PARENT_KEY_TESTNET=

# plugins.template
TEMPLATE_DATABASE_URL=postgres://everythingdev:everythingdev@localhost:5434/template_db
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ Current fixed-core host rules:
- the shared host still boots once from one base runtime snapshot
- child runtime config must extend the active BOS runtime
- supported request-scoped overrides are `ui` and existing `plugins.<id>.ui`
- tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR`
- tenant SSR is gated per-tenant by the `allowSsr` column on the tenant record; the host's BindingResolver reads permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s)
- nested label routing and account-relative tenant derivation are the intended architecture direction, but not the complete resolver behavior today

For full per-request host/plugin/auth/api swapping, start from `plans/runtime-config-hot-swap.md`.
Expand Down
2 changes: 1 addition & 1 deletion LLM.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Practical consequence:
- Path-based runtime metadata works today.
- Shared-host tenant UI mode now works today: the host can resolve a tenant config per request, enforce that it extends the base BOS runtime, and apply request-scoped UI overrides while keeping auth/API/plugin routers fixed.
- Supported tenant overrides today are `app.ui`, existing `plugins.<id>.ui`, and existing `plugins.<id>.sidebar`.
- Tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR`.
- Tenant SSR is gated per-tenant by the `allow_ssr` column on the tenant record; the host's BindingResolver reads these permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s).
- True wildcard-domain full runtime swapping for host/plugin/auth/api still needs dynamic scoped app rebuilding in the host.
- The active design doc for that work is `plans/runtime-config-hot-swap.md`.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ What works today:
- `app.ui`
- existing `plugins.<pluginId>.ui`
- existing `plugins.<pluginId>.sidebar`
- Tenant SSR is gated by `TENANT_WHITELIST` and `ALLOW_UNTRUSTED_SSR`.
- Tenant SSR is gated per-tenant by the `allow_ssr` column on the tenant record; the host's BindingResolver reads these permissions from the API's `GET /tenants/bindings` endpoint (cached for 30s).

Design direction:
- nested labels compose onto the active runtime account, such as `chicago.pizza.com -> bos://chicago.pizza.pingpayio.near/pizza.com`
Expand Down
32 changes: 31 additions & 1 deletion api/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,30 @@ export const TenantSchema = z.object({
id: z.string(),
subdomain: z.string(),
accountId: z.string(),
orgId: z.string(),
orgId: z.string().nullable(),
name: z.string(),
status: TenantStatusSchema,
allowUiOverrides: z.boolean(),
allowBackendOverrides: z.boolean(),
allowSsr: z.boolean(),
createdAt: z.string(),
updatedAt: z.string(),
deletedAt: z.string().nullable(),
});

export type Tenant = z.infer<typeof TenantSchema>;

export const TenantBindingSchema = z.object({
hostname: z
.string()
.describe("Subdomain hostname that routes to this tenant on the parent domain"),
accountId: z.string(),
allowUiOverrides: z.boolean(),
allowBackendOverrides: z.boolean(),
allowSsr: z.boolean(),
status: TenantStatusSchema,
});

const ThingSchema = z.object({
thingId: z.string().describe("Unique identifier for the thing"),
type: z.string().describe("Plugin-derived thing type"),
Expand Down Expand Up @@ -80,6 +94,9 @@ export const contract = oc.router({
name: z.string(),
accountId: z.string(),
status: z.enum(["active", "pending"]).optional(),
allowUiOverrides: z.boolean().default(true),
allowBackendOverrides: z.boolean().default(false),
allowSsr: z.boolean().default(false),
}),
)
.output(TenantSchema)
Expand All @@ -94,6 +111,9 @@ export const contract = oc.router({
subdomain: z.string().optional(),
accountId: z.string().optional(),
status: TenantStatusSchema.optional(),
allowUiOverrides: z.boolean().optional(),
allowBackendOverrides: z.boolean().optional(),
allowSsr: z.boolean().optional(),
}),
)
.output(TenantSchema)
Expand Down Expand Up @@ -128,6 +148,16 @@ export const contract = oc.router({
.output(TenantSchema)
.errors({ NOT_FOUND }),

listTenantBindings: oc
.route({
method: "GET",
path: "/tenants/bindings",
summary: "List all active tenant domain bindings",
description:
"Public — returns the subdomain-to-config mapping used by the host's BindingResolver.",
})
.output(z.array(TenantBindingSchema)),

tenantPreflight: oc
.route({ method: "POST", path: "/tenants/preflight" })
.input(
Expand Down
3 changes: 3 additions & 0 deletions api/src/db/migrations/0002_awesome_madame_web.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE "tenants" ADD COLUMN "allow_ui_overrides" boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TABLE "tenants" ADD COLUMN "allow_backend_overrides" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "tenants" ADD COLUMN "allow_ssr" boolean DEFAULT false NOT NULL;
1 change: 1 addition & 0 deletions api/src/db/migrations/0003_brief_freak.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "tenants" ALTER COLUMN "org_id" DROP NOT NULL;
165 changes: 165 additions & 0 deletions api/src/db/migrations/meta/0002_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{
"id": "7b8a421a-d0c7-414a-96e8-0c259935178b",
"prevId": "3e88c17e-53d5-4809-b872-33d2f87a8a90",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.tenants": {
"name": "tenants",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"subdomain": {
"name": "subdomain",
"type": "text",
"primaryKey": false,
"notNull": true
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"org_id": {
"name": "org_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"status": {
"name": "status",
"type": "tenant_status",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'active'"
},
"allow_ui_overrides": {
"name": "allow_ui_overrides",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"allow_backend_overrides": {
"name": "allow_backend_overrides",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"allow_ssr": {
"name": "allow_ssr",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"tenants_subdomain_idx": {
"name": "tenants_subdomain_idx",
"columns": [
{
"expression": "subdomain",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"tenants_account_id_idx": {
"name": "tenants_account_id_idx",
"columns": [
{
"expression": "account_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"tenants_subdomain_unique": {
"name": "tenants_subdomain_unique",
"nullsNotDistinct": false,
"columns": ["subdomain"]
},
"tenants_account_id_unique": {
"name": "tenants_account_id_unique",
"nullsNotDistinct": false,
"columns": ["account_id"]
},
"tenants_org_id_unique": {
"name": "tenants_org_id_unique",
"nullsNotDistinct": false,
"columns": ["org_id"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.tenant_status": {
"name": "tenant_status",
"schema": "public",
"values": ["active", "pending", "suspended", "pending_deletion"]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
Loading
Loading