Schema sources
Understand who owns each object on a workspace, what a sync may change, and how a source lives and dies over the API.
A schema source is the slice of a workspace's schema that one codebase owns. You declare objects and views with the builders, give the set an id, and push it with schema.sync. The workspace applies it as system definitions. Users keep working on top: their attributes and views survive every sync.
import { createStandards, defineSchemaSource, object, text } from "@stndrds/client";
const service = object({ name: "services", label: "Service" })
.labelExpression("{{ name }}")
.attribute(text({ name: "name", label: "Name" }).required());
export const statusSource = defineSchemaSource("status-page", {
label: "Status page",
objects: [service],
});
const standards = createStandards({
baseUrl: "https://api.standards.new/v1",
apiKey: process.env.STANDARDS_API_KEY!,
});
const result = await standards.schema.sync(statusSource);
// { applied: true, hash, objects: { created, updated, deleted }, views: { created, updated, adopted, released } }Three tiers
Every object on a workspace belongs to one of three tiers: native, system or runtime.
- Native objects ship with the platform:
skill,memory,drives,artifact,meeting. Their names are reserved. Declaring one in code fails at build time withReservedObjectNameError, and the API refuses them too. - System objects and attributes are declared in code and pushed by
schema.sync. They carrysystem: trueand are immutable in the app. - Runtime objects and attributes are created by users in the app, or over
POST /schema/objectsand its attribute routes underarchitect:create. They carrysystem: false.
| System | Runtime | |
|---|---|---|
| Who creates | Your code, through schema.sync. | Users in the app, or an API call under architect:create. |
| Who changes | Your code, on the next sync. Users cannot edit, retype or delete it. | Users in the app. |
| Who deletes | Your code. An attribute removed from the source is dropped with its values; an object removed from the source is demoted, not deleted. | Users in the app. |
| What sync does | Owns the definition and rewrites it to match the code. | Keeps it. Promotes it to system when the code declares the same name; refuses with a 400 when the types clash. |
A runtime attribute added on a system object is merged back attribute by attribute: it stays where the user put it, and it becomes a system attribute only if your code later declares a compatible attribute with that name.
Retypes need a migration
Changing an attribute's type in code is ambiguous: is it a retype or a drop plus a create? The sync refuses to guess and answers 400 with the object and the migration to declare. Declare it on the builder, .migration(2, (m) => m.changeType(...)) or .renameAttribute(...), and the next sync replays it once. See SDK / Objects.
Sealed objects
An object marked .sealed() turns every undeclared runtime attribute into a standards diff failure unless .tolerate() names it; neither flag deletes user data. See SDK / Objects.
Demotion keeps records
Remove an object from the source, or remove the whole source, and the object is demoted to runtime: system: false, records untouched, attributes untouched. Users can then edit or delete it in the app. A sync demotes an object; it never deletes one.
Views are adopted and released
A view declared by a source is owned by it. When the source declares a view whose object and name already exist as a runtime view, the row is adopted: it becomes source-owned and workspace overlays on it are preserved. When a view leaves the source, the row is released back to runtime with its overlays. The sync never overwrites a user's view with a copy of its own.
Lifecycle over the API
schema.sync is a thin client over four routes.
| Route | Permission | Answer |
|---|---|---|
GET /schema/sources | workspace:read | { sources: [{ sourceId, hash, appliedAt, objectNames, label, icon }] } |
GET /schema/sources/:id | workspace:read | One summary. 404 with code SCHEMA_SOURCE_NOT_FOUND when the source was never pushed. |
PUT /schema/sources/:id | architect:update | { applied, hash, objects?, views? } |
DELETE /schema/sources/:id | architect:delete | 204. Owned objects are demoted, owned views released. |
The push works like this:
- The client hashes
{ objects, views }(SHA-256 over canonical JSON) and readsGET /schema/sources/:id. - Same hash: nothing is applied and the client answers
{ applied: false }. A changedlabeloriconis still stored; they are not part of the hash. - Different hash or never pushed:
PUT /schema/sources/:idwith{ hash, objects, views, label?, icon? }. The server recomputes the hash and answers400when it differs. It then checks ownership, validates every object and view with the builders' own rules, and applies the source under a per-workspace lock before persisting it.
The routes are exempt from rate limiting: a CI push carries the whole schema in one call.
Conflicts. An object or view already owned by the core, a bundle or another source is a 409 (DuplicateError), with a message like Object "services" is owned by source:legacy-site. The owner is always the other party; the client throws SchemaSourceConflictError with objectName and owner.
Limits. A source declares at most 50 objects and 200 views, and only views for its own objects. A source id is a lowercase slug (^[a-z0-9][a-z0-9-]{0,62}$).
From a terminal
standards sources list # id, hash, last apply, object names
standards sources remove status-page --yes # demote its objects, keep the records
standards pull # read the deployed source against the shared runtime
standards diff # exit 0 when equal, 1 on differences, 2 on errorstandards pull and standards diff compare what is deployed with what users changed at runtime. They never modify files or the workspace. See CLI / Schema and sources.
Next steps
- SDK / Sync — the result shape, the errors and how to run it in CI.
- SDK / Objects — builders,
.sealed(),.tolerate()and migrations. - Concepts / Workspaces and API keys — which key may push a source.
- CLI / Schema and sources —
sources,pullanddiffin detail.