Skip to content
Merged
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
72 changes: 72 additions & 0 deletions .changeset/published-readme-member-existence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
---
"@objectstack/plugin-security": patch
"@objectstack/service-package": patch
"@objectstack/trigger-schedule": patch
"@objectstack/trigger-record-change": patch
"@objectstack/embedder-openai": patch
"@objectstack/driver-sqlite-wasm": patch
"@objectstack/spec": patch
---

docs: name packages that exist in seven published documents, and gate the class (#10893)

A published README ships inside the npm tarball, so an install instruction in one
reaches every reader of the package. Nine `@objectstack/` names across seven
published documents named a package that is in **no directory of this repo**, and
five of those sat on `import` lines inside runnable fences.

`check:published-readme-exports` could not see any of it, by construction. It
resolves a documented import against the package's built type surface through the
workspace member map, so a specifier that is not a member has no type entry to
compare against and the gate reads no further — strict about a member that exists,
silent about one that does not. The gate now makes the member-existence claim
first: an `@objectstack/`-scoped specifier that names no workspace member is a
finding, and the run header prints the scoped population as `N/N` so a recogniser
that stops matching shows up as a denominator that fell.

What each dead claim now says, and why:

- **`@objectstack/trigger-schedule`** and **`@objectstack/trigger-record-change`**
each misnamed **themselves**. Both READMEs — including their `# ` titles and
every fenced import — said `@objectstack/plugin-trigger-…`, a name that has
never been published. The exported class names (`ScheduleTriggerPlugin`,
`TimeRelativeTriggerPlugin`, `RecordChangeTriggerPlugin`) were correct all
along; only the package name was wrong, so this is a rename pinned by each
package's own `name` field.
- **`@objectstack/plugin-security`** told readers to `install
@objectstack/plugin-org-scoping` and register an `OrgScopingPlugin` from it. No
such package exists. The organization wall ships as the enterprise
`@objectstack/organizations` runtime, whose `OrganizationsPlugin` registers the
`org-scoping` service this plugin probes — the name `objectstack serve` and
`objectstack doctor` both print. Asking for the wall without it is a refusal to
boot (ADR-0093 D5), not a silent downgrade, and the page now says so. The
tenant-isolation bullet pointed at `@objectstack/service-tenant`, which is the
cloud control-plane runtime from the separate `cloud` repository and not where
the wall comes from either.
- **`@objectstack/service-package`** described packages being "delivered to
runtime kernels that load them through `@objectstack/service-marketplace`". That
package was never built: ADR-0003, ADR-0016 and ADR-0025 all name it as future
work. The loading half that does exist here is
`@objectstack/cloud-connection`'s `MarketplaceInstallLocalPlugin`.
- **`@objectstack/embedder-openai`** had a fenced example importing
`KnowledgeTursoPlugin` from `@objectstack/knowledge-turso` — the worst shape,
because a reader pastes it. No knowledge adapter in this repository consumes an
`IEmbedder` at all: `knowledge-memory` and `knowledge-ragflow` take no embedder
option, and the adapters the contract is written for are not here. The example
is now the `embed()` surface that does exist, with the gap stated rather than
papered over with a substitute package name.
- **`@objectstack/driver-sqlite-wasm`**'s "When to use" table compared it against
`@objectstack/driver-sqlite` and `@objectstack/driver-postgres`. Neither has
ever existed; `@objectstack/driver-sql` covers PostgreSQL, MySQL and SQLite
through Knex, choosing the client from its optional peers.
- **`@objectstack/spec`**'s published `prompts/architecture.md` instructed code
generators to write `import { User } from '@objectstack/protocol'`. The package
is `@objectstack/spec`, which the same sentence names as the path being
replaced.

Four `@objectstack/` names that are **not** in this repo are deliberately left as
they are, because prose may name a package this repo does not build and a runnable
import may not: `@objectstack/security-enterprise` (the enterprise edition, whose
install hint the CLI prints and a CLI test pins), `@objectstack/service-tenant`
(the cloud runtime), `@objectstack/framework` (the umbrella install name), and the
two names `service-datasource`'s README recalls as its own past.
12 changes: 8 additions & 4 deletions packages/drivers/driver-sqlite-wasm/README.md
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
# @objectstack/driver-sqlite-wasm

SQLite-on-WASM driver for ObjectStack. Runs the same `SqlDriver` codepath as
`@objectstack/driver-sqlite` but swaps the native `better-sqlite3` N-API binding
`@objectstack/driver-sql` but swaps the native `better-sqlite3` N-API binding
for [`sql.js`](https://sql.js.org) (SQLite compiled to WebAssembly), so it works
in environments where native modules are unavailable — most notably
**StackBlitz WebContainer** (Node-in-browser).
Expand All@@ -14,14 +14,18 @@ in environments where native modules are unavailable — most notably

| Driver | Backend | Runs in WebContainer | Native binary |
|---|---|---|---|
| `@objectstack/driver-sqlite` | `better-sqlite3` | ❌ | yes |
| `@objectstack/driver-sql` (SQLite) | `better-sqlite3`, an optional dependency | ❌ | yes |
| **`@objectstack/driver-sqlite-wasm`** | `sql.js` (WASM) | ✅ | no |
| `@objectstack/driver-postgres` | `pg` | ✅ (with TCP) | no |
| `@objectstack/driver-sql` (Postgres) | `pg`, an optional peer dependency | ✅ (with TCP) | no |

Two rows name the same package on purpose: `@objectstack/driver-sql` covers
PostgreSQL, MySQL and SQLite through Knex, selecting the client from its optional
peers. There is no separate `driver-sqlite` or `driver-postgres` package.

Pick the WASM driver when you need a zero-binary SQLite that boots in the
browser sandbox, in serverless edge runtimes that expose Node `fs`, or in
CI environments where building `better-sqlite3` against the host Node is
painful. For production servers, prefer the native driver.
painful. For production servers, prefer `@objectstack/driver-sql` with a native client.

## Install

Expand Down
23 changes: 15 additions & 8 deletions packages/plugins/embedder-openai/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,7 +14,7 @@ OpenAI-compatible embedder for ObjectStack. Works against any endpoint that spea
| Ollama (local) | `http://localhost:11434/v1` | `bge-m3`, `nomic-embed-text` |
| LiteLLM / vLLM / 自建网关 | (your endpoint) | (your model) |

Implements `IEmbedder` from `@objectstack/spec/contracts` — drop directly into any knowledge adapter (e.g. `@objectstack/knowledge-turso`).
Implements `IEmbedder` from `@objectstack/spec/contracts`, so it drops into any knowledge adapter that computes vectors. ⚠️ No adapter in this repository does yet — see [Using the embedder](#using-the-embedder).

## Install

Expand DownExpand Up@@ -77,21 +77,28 @@ const embedder = createOpenAIEmbedder({
});
```

## Plug into a knowledge adapter
## Using the embedder

`OpenAIEmbedder` is a plain `IEmbedder`: construct it and hand it to whatever needs
vectors.

```ts
import { OpenAIEmbedder } from '@objectstack/embedder-openai';
import { KnowledgeTursoPlugin } from '@objectstack/knowledge-turso';

const embedder = new OpenAIEmbedder({ apiKey: process.env.OPENAI_API_KEY! });

kernel.use(new KnowledgeTursoPlugin({
url: 'libsql://your-tenant.turso.io',
authToken: env.TURSO_TOKEN,
embedding: embedder,
}));
const [vector] = await embedder.embed(['ObjectStack turns an app into typed metadata']);
console.log(embedder.id, embedder.dimensions, vector.length);
```

⚠️ **No knowledge adapter in this repository consumes an `IEmbedder` yet.** The two this
repo builds — `@objectstack/knowledge-memory` and `@objectstack/knowledge-ragflow` — take
no embedder option. The vector-computing adapters the contract is written for
(`@objectstack/knowledge-turso`, `@objectstack/knowledge-sqlite-vec`, named in
[`embedder.ts`](../../spec/src/contracts/embedder.ts)) are in no directory of this repo,
so an earlier revision of this page showed a `new KnowledgeTursoPlugin({ embedding })`
example that no reader could install or run.

## Options

| Option | Default | Description |
Expand Down
30 changes: 19 additions & 11 deletions packages/plugins/plugin-security/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,23 +37,31 @@ await kernel.bootstrap();

`SecurityPlugin` is single-tenant by default. It enforces RBAC, owner-based RLS, and Field-Level Security regardless of mode.

For **multi-tenant** (logical row-level Organization scoping) install `@objectstack/plugin-org-scoping` *before* SecurityPlugin:

```typescript
import { OrgScopingPlugin } from '@objectstack/plugin-org-scoping';

await kernel.use(new OrgScopingPlugin()); // MUST be BEFORE SecurityPlugin
await kernel.use(new SecurityPlugin());
```
For **multi-tenant** (logical row-level Organization scoping) the organization wall itself
is **not in this package and not in this repository**. It ships as the enterprise
`@objectstack/organizations` runtime, whose `OrganizationsPlugin` registers the
`org-scoping` service; a host app declares and installs it in its own `package.json`, and
`objectstack serve` resolves it from the app rather than from the framework. It must be
registered **before** `SecurityPlugin`, so the posture probe below finds it.

Asking for the wall without the package is not a silent downgrade: `objectstack serve`
prints `FATAL: tenancy posture '<posture>' was requested but @objectstack/organizations
could not be loaded` and **refuses to boot** (ADR-0093 D5), unless the operator explicitly
sets `OS_ALLOW_DEGRADED_TENANCY=1`. `objectstack doctor` reports the same missing runtime.

> ⚠️ Earlier revisions of this page told readers to install `@objectstack/plugin-org-scoping`
> and register an `OrgScopingPlugin` from it. No such package exists — not on npm, and in no
> directory of this repo. The open edition ships no organization wall; there is nothing to
> install *here* to get one.

SecurityPlugin resolves the tenancy **posture** (`single` | `group` | `isolated`) once at start time — preferring the `tenancy` service, and falling back to probing `getService('org-scoping')` (present ⇒ the historical `isolated` posture). Two consequences:

- **Tenant isolation is not an RLS policy.** Since ADR-0095 D1 the organization wall is **Layer 0** (`tenant-layer.ts`): an independent filter AND-composed ahead of business RLS, so a business-RLS change can never weaken it (W1) and the `viewAllRecords` / `modifyAllRecords` superuser bypass can never cross it (W2 — crossing takes a true `PLATFORM_ADMIN`). Under the `single` posture Layer 0 is inert. Accordingly the default `member_default` / `viewer_readonly` sets ship **no** wildcard `tenant_isolation` policy: `member_default` carries the owner-scoped `owner_only_writes` / `owner_only_deletes` plus per-object `_self` carve-outs on the better-auth identity tables, and `viewer_readonly` carries the `_self` carve-outs only.
- **The platform's own tenant-scoped RLS policies are still stripped when no wall is enforced** (`single`), so single-tenant deployments aren't filtered to zero rows and don't pay the field-existence safety net on every find — e.g. `organization_admin`'s `sys_member_org` / `sys_invitation_org` / `sys_team_org`, and the `sys_organization_self` carve-out. The strip is by **provenance**, not by pattern-matching the predicate: an app-authored tenant policy is never stripped — it reaches the compiler and fails closed there, with a one-time operator warning (ADR-0105 D3).

`organization_id` auto-injection on insert is provided by OrgScopingPlugin; `owner_id` auto-injection always runs in SecurityPlugin regardless.
`organization_id` auto-injection on insert is provided by that organizations runtime; `owner_id` auto-injection always runs in SecurityPlugin regardless.

In CLI / dev-server mode the `OS_MULTI_ORG_ENABLED` environment variable (default `false`) toggles whether the runtime registers `OrgScopingPlugin` alongside `SecurityPlugin`. Set `OS_MULTI_ORG_ENABLED=true` before `objectstack serve` / `pnpm dev` to enable.
In CLI / dev-server mode the `OS_MULTI_ORG_ENABLED` environment variable (default `false`) toggles whether the runtime registers `OrganizationsPlugin` alongside `SecurityPlugin`. Set `OS_MULTI_ORG_ENABLED=true` before `objectstack serve` / `pnpm dev` to enable.

## Key Exports

Expand DownExpand Up@@ -92,7 +100,7 @@ Compilation output is a filter AST merged into every query's `where` clause, so
## When to use

- ✅ Any multi-user deployment.
- ✅ Enforcing tenant isolation (combine with `@objectstack/service-tenant`).
- ✅ Enforcing tenant isolation — the wall itself comes from the enterprise organizations runtime described above, not from this package.

## When not to use

Expand Down
5 changes: 3 additions & 2 deletions packages/services/service-package/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@

## Overview

`service-package` persists ObjectStack packages — the unit of metadata distribution consisting of a `manifest` plus its `objects`, `views`, `apps`, `flows`, `agents`, `tools`, and `translations` — into the `sys_packages` system table so they can be published, listed, and delivered to runtime kernels that load them through `@objectstack/service-marketplace`.
`service-package` persists ObjectStack packages — the unit of metadata distribution consisting of a `manifest` plus its `objects`, `views`, `apps`, `flows`, `agents`, `tools`, and `translations` — into the `sys_packages` system table so they can be published, listed, and delivered to runtime kernels that load them. In this repo the loading half is `@objectstack/cloud-connection`, whose `MarketplaceInstallLocalPlugin` installs a marketplace package into the local kernel through the `manifest` service.

Typical consumers:

Expand DownExpand Up@@ -105,7 +105,8 @@ CREATE INDEX IF NOT EXISTS idx_packages_latest
## When not to use

- ❌ Not a package manager for npm/TypeScript source packages — use npm.
- ❌ Not a runtime plugin loader — pair with `@objectstack/service-marketplace` or a custom loader for that.
- ❌ Not a runtime plugin loader — pair with `@objectstack/cloud-connection`'s
`MarketplaceInstallLocalPlugin`, or a custom loader, for that.

## Related Packages

Expand Down
2 changes: 1 addition & 1 deletion packages/spec/prompts/architecture.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ B. Terminology
* ALWAYS emphasize "Polyglot Data". We are not just a SQL wrapper; we handle Redis and Excel native files.
* Studio vs. Cloud: Studio is for "Local Data & Development". Cloud is for "Deployment & Collaboration".
C. Coding Style
* Monorepo: Use generic imports (e.g., import { User } from '@objectstack/protocol') instead of relative paths like ../../packages/spec.
* Monorepo: Use generic imports (e.g., import { User } from '@objectstack/spec') instead of relative paths like ../../packages/spec.
* UI: Use Shadcn UI + Tailwind CSS. Dark mode default for developer tools (Studio/Console).
* Data Fetching: All UI components must be Server-Driven or strongly typed against the Schema.
5. Execution Context
Expand Down
4 changes: 2 additions & 2 deletions packages/triggers/trigger-record-change/README.md
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
# @objectstack/plugin-trigger-record-change
# @objectstack/trigger-record-change

Auto-launch ObjectStack flows on record changes.

Expand DownExpand Up@@ -74,7 +74,7 @@ update path.
```ts
import { AutomationServicePlugin } from '@objectstack/service-automation';
import { MessagingServicePlugin } from '@objectstack/service-messaging';
import { RecordChangeTriggerPlugin } from '@objectstack/plugin-trigger-record-change';
import { RecordChangeTriggerPlugin } from '@objectstack/trigger-record-change';

kernel
.use(new AutomationServicePlugin()) // engine + flows
Expand Down
8 changes: 4 additions & 4 deletions packages/triggers/trigger-schedule/README.md
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
# @objectstack/plugin-trigger-schedule
# @objectstack/trigger-schedule

Auto-launch ObjectStack flows on a schedule (cron / interval / once).

Expand All@@ -9,7 +9,7 @@ platform `IJobService` (the `'job'` service), so it stays adapter-agnostic: the
job service selects a cron-capable adapter (e.g. the durable `DbJobAdapter` or
`CronJobAdapter`) for cron schedules and the interval adapter for the rest.

This is the sibling of `@objectstack/plugin-trigger-record-change` — same
This is the sibling of `@objectstack/trigger-record-change` — same
engine baseline, a different event source.

## What it does
Expand DownExpand Up@@ -48,7 +48,7 @@ in its context.
```ts
import { AutomationServicePlugin } from '@objectstack/service-automation';
import { JobServicePlugin } from '@objectstack/service-job';
import { ScheduleTriggerPlugin } from '@objectstack/plugin-trigger-schedule';
import { ScheduleTriggerPlugin } from '@objectstack/trigger-schedule';

kernel
.use(new AutomationServicePlugin()) // engine + flows
Expand DownExpand Up@@ -110,7 +110,7 @@ It needs both the job service (sweep cadence) **and** the ObjectQL engine (the
date-window query); register it alongside the schedule trigger:

```ts
import { ScheduleTriggerPlugin, TimeRelativeTriggerPlugin } from '@objectstack/plugin-trigger-schedule';
import { ScheduleTriggerPlugin, TimeRelativeTriggerPlugin } from '@objectstack/trigger-schedule';

kernel
.use(new ScheduleTriggerPlugin()) // plain schedule flows
Expand Down
Loading
Loading