ObjectOS
資源

Changelog & Versioning

How ObjectOS is versioned, what changes between releases, and what's supported.

Versioning policy

ObjectOS follows Semantic Versioning: MAJOR.MINOR.PATCH.

Version bumpWhat it meansWhat to do
Patch (14.7.0 → 14.7.1)Bug fixes, no behavior changeRoll forward, no app changes needed
Minor (14.6 → 14.7)New features, backward-compatibleRoll forward, optionally adopt new features
Major (13 → 14)Breaking changes documented in release notesRead the migration guide before upgrading

All @objectstack/* packages release together with a synchronized version number — they're tested as a matrix, not independently.

Compatibility matrix

ComponentCompatibility rule
ObjectOS image ↔ compiled artifactSame minor version. A 14.7.x image runs a 14.7.x artifact; a 14.7 artifact may use features unavailable in a 14.0 image. The protocol handshake (PROTOCOL_VERSION, 12.0+) rejects incompatible packages at install time.
ObjectOS ↔ CLISame minor version recommended. The CLI in npm i -g writes scaffolds pinned to its own version.
ObjectOS ↔ database driverDriver pinned by image build; verify Postgres ≥ 13 / MongoDB ≥ 5 / Turso (any current).
Node.js22 or newerengines.node is >=22.0.0 across every published @objectstack/* package since 17.0. Node 20 reached end-of-life on 2026-04-30.

Support windows

Support is defined relative to the current major, not against a fixed calendar:

BranchStatus
Current major (17.x today)Active development; new features and fixes
Previous major (16.x today)Security fixes only
Older majorsUnsupported

Critical security fixes are backported to the current and previous major. Everything else lands on main. Dates attached to a specific major are published with that major's release notes.

Release notes

Released ObjectOS versions and their CHANGELOG entries are published at:

Subscribe to releases on GitHub to get notified.

Recent highlights

17.1

@objectstack 17.1.0 (released 2026-08-20) is the current release of the v17 line. Where 17.0 made the authorable surface loud, 17.1 makes the runtime's answers loud: a flag that promised to revoke access now revokes it, a read that failed stops being served as an empty one, a flow that never dispatched stops being reported as a run that failed, and a credential that was never meant to be readable stops being served. Full notes: docs.objectstack.ai/docs/releases/v17.

Warning: 17.1.0 is a minor by version number, not by blast radius. Several of its security corrections change who can read or write on an existing deployment, with no migration step to notice. Work through the 17.1.0 upgrade checklist in the full notes before rolling forward.

What moved:

  • Deactivating a permission set or a position now actually stops granting access (security) — both objects ship a Deactivate action whose dialog promises that access stops, and nothing read the column: a position seeded active: false still granted its permission sets, and a permission set seeded active: false still returned a platform-admin posture. A sharing rule reached users by a second road that never passed that seam, so a rule sharing records with a deactivated position kept sharing them — those shares are revoked on the next reconcile. A row whose active column is absent or NULL is unaffected. On a deployment that used the switch believing it was inert this is a real revocation on live data, so audit your deactivated rows first. See Permission Sets.
  • The shipped admin sets no longer grant export on the * wildcard (breaking) — admin_full_access, organization_admin and the derived organization_admin_no_bypass carried objects['*'].allowExport: true, which made the 17.0 export axis undeniable: an application could declare an object exportable by nobody and the platform exported it anyway. Export is now granted per object, in an app's own permission set; there is deliberately no automatic replacement. Read is untouched — this narrows bulk egress only.
  • Partial field maskingFieldSchema declares maskingRule: the closed preset enum phone / id_card / bank_account / email / name, plus a { keepHead, keepTail } escape hatch. A field declaring a rule is served masked-but-recognizable (138****5678) to every non-system caller, with the field's requiredPermissions as the unmask gate. Masking rides the single runtime channel, so API callers, browser users and the CSV/XLSX export route all see the same value; a masked caller cannot filter, sort, group or aggregate on the field. See Field-Level Security.
  • sys_audit_log can answer "who viewed this record" — the ledger covered writes only; it gains a read action, its writer, and a record_views list view. Record-detail reads only, per-object opt-in with no global switch, batched off the request path, and never any field values — read auditing runs ahead of field masking, so copying values in would mint a plaintext copy of exactly what field-level security withholds. See Audit Logs.
  • Read-only approval visibility, per object, default off — an object can be named so that a user who can read a business record may also see that record's approval requests and full action history. Omitted or empty leaves visibility exactly as it is; on an object you do name it is not a no-op — a supervisor who holds full read but never appears in the approval used to get an empty list, and now sees the request row, every actor and decision, the action's comment text (意见正文), and decision attachments. The tier is read-only and introduces no new permission concept: the service reads the record as the caller, so ordinary CRUD and record access decide. See Approvals.
  • All three flow doors answer one honest status table — a refused dispatch stops being reported as a failed run: 404 not found, 409 FLOW_DISABLED, 422 FLOW_NO_START_NODE, and 400 FLOW_FAILED for a run that actually ran and was rejected. The automation trigger routes, the actions door, and declared type: 'flow' endpoints now read one shared definition instead of three private copies, and an outputMapping is no longer applied to a failure. Callers should branch on the HTTP status, not on an inner success flag. See Flows.
  • error.code is a closed vocabulary at every door — a thrown code outside the platform's standard set no longer reaches error.code; it rides the new optional declaredCode instead, so an application's own spellings survive without widening the platform vocabulary.
  • A failed read stops reading as an empty one — a recurring class closed across the metadata protocol, the roll-up summary index, and the cascade-delete and registry probes: a read that failed used to be indistinguishable from one that legitimately found nothing, so an upsert pre-load turned every update into an insert and "what would break if I delete this" answered "nothing". No catch is removed — each is discriminated by error type, so an unprovisioned table stays benign and everything else surfaces.
  • Author-time gates reach the runtime publish door — rules that only os build / os validate ran now also judge a runtime write, so Studio and the metadata API cannot land what the CLI refuses. Re-run os build / os validate after upgrading: several new refusals can fail a stack that built clean on 17.0, including an unknown top-level stack key, a dashboard header modal action whose target is not a declared page, and a list-view sort naming a formula field or no field at all.
  • Console (Studio) — two objectui pin moves bring dashboard component re-keying, the retirement of the structured confirm object on actions, I18nLabel on metric widgets, and host-performed submitBehavior.url redirects for consoles mounted at a sub-path.

17.0

@objectstack 17.0.0 (released 2026-08-14) is a truth-telling release: where 16.0 made declared metadata honest, 17.0 does the same for the surfaces around it. Full notes: docs.objectstack.ai/docs/releases/v17. What moved:

  • Node.js 22 is the floor (breaking) — engines.node moves from >=18.0.0 to >=22.0.0 across all 50 published manifests. CI, the release pipeline, and every shipped Docker image already ran 22; the promise now matches the evidence. On Node 22+ nothing changes. If your CI pins Node, pin it to 22.
  • Bulk export is its own privilege (breaking) — allowExport unset used to mean "inherit read"; it now means denied, and viewAllRecords / modifyAllRecords no longer confer it either. Package-shipped sets are re-seeded on upgrade, so admin_full_access and organization_admin keep exporting — environment-authored sets are not. Add allowExport: true to any custom set whose users export. See Permission Sets.
  • A file is a record, not a blob in a column — media fields (file/image/avatar/video/audio) store an opaque sys_file id; the { url, name, size, … } object becomes the read (expanded) form. Because the platform owns the bytes, accept / maxSize are declarable and server-enforced rather than browser-side hints. os migrate files-to-references performs the conversion. See Field Types.
  • The GraphQL surface is removed (breaking) — it was schema-only from day one: the handler answered 501 unconditionally because the service was never assigned. /graphql now returns 404. Unaffected: graphql as a protocol for an external datasource, which is someone else's API rather than one ObjectOS serves.
  • A sharing rule with no criteria shares nothing, and a disabled RLS policy is disabled (breaking, security) — a rule stored without criteria used to evaluate as every record of the object, and an RLS policy switched off with enabled: false kept contributing its OR-branch grant. Both now fail closed. See Record Access.
  • An approval request is visible to its participants (security) — getRequest / listRequests / countRequests applied only the tenant half of the visibility rule, so any authenticated user could read any request in their tenant: payload snapshot, decision history, and attachments.
  • Approvers can be routed dynamically — approval nodes gain expression approvers (CEL over current.* / trigger.* / vars.*), a node-level onEmptyApprovers policy, and author-declared decision outputs that resume the run as <nodeId>.<key> flow variables. See Approvals.
  • A broken datasource is loud at boot — a datasource that objects bind to must connect or the boot fails, objectql.init() refuses to start on a dead data driver, and /api/v1/ready answers 503 when one stops answering. Previously such a boot started clean, exited zero, and then failed every read and write of the bound objects.
  • enable.apiMethods shrinks to six primitives (breaking) — the authorable enum is exactly get, list, create, update, delete, bulk. The eight legacy values are derived effective operations; a stored one is stripped at parse with a warning naming its replacement.
  • The dead-metadata sweep continues (breaking) — object.enable.trash and enable.mru, tool.requiresConfirmation, agent.tools[], agent.knowledge, SkillSchema.permissions, report aria / performance, flow.active, and a long tail of parsed-but-ignored keys are removed. Each rejects at parse time with its upgrade prescription rather than being silently stripped. Run os migrate meta --from 16 to rewrite your sources.
  • The authorable surface is closed — every authorable metadata type now rejects unknown keys with a named prescription, on the parse path and not only in create(). A typo is a located error instead of a setting that quietly never took effect.

16.0

@objectstack 16.0.0 converges the developer surface and makes declared metadata honest: one blessed name for the caller's org, real multi-approver governance, time-relative automations that actually fire, and a platform-wide sweep that turns silently-ignored metadata into loud errors at authoring time. Full notes: docs.objectstack.ai/docs/releases. What moved:

  • tenantId alias removed from hook/action ctx (16.0, breaking) — read the caller's org as ctx.user.organizationId / ctx.session.organizationId in every *.hook.ts / *.action.ts body (the value is unchanged; ctx.user is undefined for system writes). The driver-layer tenancy axis (ExecutionContext.tenantId) is a different concept and is untouched.
  • Approvals: quorum, per-group sign-off (会签), and a metadata-driven inbox — approval steps support M-of-N quorum (minApprovals) and one-approver-from-each-group steps, tallied against an open-time snapshot with OOO substitution; a single rejection still vetoes, and thresholds clamp so misconfiguration can never deadlock. Decisions carry file attachments, progress ("2 of 3 · finance pending") is computed server-side, and approve / reject / reassign / send-back / request-info / remind / recall / resubmit are declared type:'api' actions on sys_approval_request — the approvals inbox renders them through its generic action runtime instead of hand-written buttons.
  • Time-relative automations — a flow start node can declare timeRelative (offsetDays: [60, 30, 7] or withinDays); a daily sweep launches the flow once per matching record, so "remind me 60 days before the contract ends" finally just works — no more date-equality conditions that only fire on a lucky edit.
  • Filtered roll-upssummaryOperations.filter lets a parent total aggregate only the matching child rows, and re-aggregates when a child moves in or out of the predicate.
  • Strict dashboard widgets (16.0, breaking) — DashboardWidgetSchema is .strict(): an undeclared key (typo or removed inline-analytics key) is a parse error naming the key and pointing at the dataset shape (dataset + dimensions + values) instead of silently rendering nothing.
  • MCP: stdio gets an identity, agents get a linter — stdio auto-start moves to its own OS_MCP_STDIO_ENABLED switch (default off) and requires an OS_MCP_STDIO_API_KEY=osk_... principal, fail-closed, resolved through the same authorization chain as HTTP (RLS/FLS/tenant apply; no system bypass). A new validate_expression tool lets agents lint a formula against the real object schema before saving it.
  • Enforce-or-remove sweep (16.0, breaking) — declared-but-unenforced metadata is now loud: dead field/object/agent props are removed or tombstoned with guidance, hook events collapse 18 → 8, validation rules lose the never-evaluated 'delete' event, webhook undelete / api triggers are removed, and unknown requires capability tokens are rejected at authoring (the aiStudio / aiSeat aliases are gone — use kebab-case ai-studio / ai-seat).
  • Engine-owned system rows are read-only through the generic data API (ADR-0103) — jobs, notifications, approval-runtime rows, sharing rows, audit trails, secrets, and the like are locked to get/list, with a fail-closed guard rejecting user-context writes through /data. Third-party system objects that legitimately take user writes must declare userActions.
  • Date logic stops lyingrecord.due_date == today() now matches (temporal equality comparisons are rewritten to coerce the field operand), while date arithmetic in formulas (end - start + 1, today() + 30) becomes a build-time error pointing at daysBetween / daysFromNow / addDays / addMonths — those expressions always faulted to null at runtime anyway.
  • Bulk user import defaults to passwordPolicy: 'auto' — rows with a deliverable channel (real email + wired email service, or phone + SMS invite path) are invited; only unreachable rows get a one-time temporary password (must_change_password). Pass passwordPolicy: 'none' explicitly for the old identity-only behavior; per-row outcomes are on rows[].delivery.

14.x

ObjectOS One and the bundled server shipped on @objectstack 14.7.0. The runtime boot contract is unchanged — createStandaloneStack takes the same artifact, environment, and database settings — but 13.0 and 14.0 both land breaking changes in the authorization vocabulary, so review your permission metadata before rolling forward from ≤ 12.x. Full notes: docs.objectstack.ai/docs/releases. What moved in 14.x:

  • ADR-0090 vocabulary convergence completed (14.0, breaking) — book.audience gates on { permissionSet } (was { profile }), PortalSchema.profilespositions, RLSUserContextSchema.rolepositions (string array), sys_record_share.recipient_type: 'role''position'.
  • Object capability flags are enforced (14.0, breaking-ish) — the enable.* flags went from parsed-but-unused to real gates. activities and feeds are opt-out (default true; feeds: false rejects comments with 403 FEEDS_DISABLED), trackHistory drives the History tab, and files is opt-in (403 FILES_DISABLED until enable: { files: true }).
  • Member baseline lost delete (14.2, breaking) — member_default no longer grants record deletion; re-grant per object through position-distributed permission sets.
  • Admin user management & phone auth (14.3) — direct user creation (POST /api/v1/auth/admin/create-user, one-time passwords with forced rotation), bulk import (rows/CSV/XLSX, dry-run, upsert), opt-in phone sign-in with SMS OTP via @objectstack/plugin-sms (Aliyun / Twilio), and an sms notification channel.
  • Data-lifecycle contract (14.4, ADR-0057) — objects declare lifecycle (class, retention, rotation, archive); the default-on LifecycleService reaps, rotates, and archives platform data (sys_activity 14 days, sys_audit_log 90 days hot). Disable with OS_LIFECYCLE_DISABLED=1; tune via lifecycle.retention_overrides. 14.5 removed the per-plugin retentionDays / retentionSweepMs options (JobRunRetention, NotificationRetention) in favor of lifecycle declarations, and split lifecycle-classed system data into a dedicated telemetry datasource (OS_TELEMETRY_DB); os db clean reclaims SQLite space.
  • Effective-dated grants & delegation (14.4, ADR-0091) — position and permission-set assignments support valid_from / valid_until windows (fail-closed, no background job), and delegatable positions let holders self-delegate for a bounded window (≤ 30 days, reason required, never admin scopes).
  • MCP scope ceiling (14.5, ADR-0090 D10) — OAuth-connected agents run under effective_permission = scope_ceiling ∩ user_grants (data:read / data:write / actions:execute), failing closed.
  • Security fixes (14.4–14.5) — FLS keys must be object-qualified (bare keys silently matched nothing; now lint-rejected with autofix); settings and share-link routes no longer trust spoofable x-user-id-style headers; analytics queries are scoped to the caller's read filter.

13.x — Permission Model v2

@objectstack 13.0 restructured authorization (ADR-0090). Breaking:

  • Roles and Profiles converge on Positionssys_role* tables, RoleSchema / defineRole, metadata kinds role / profile, and ExecutionContext.roles[] are gone; positions are flat and hierarchy moved to the business-unit tree. Sharing recipients renamed (roleposition, role_and_subordinatesunit_and_subordinates).
  • Custom objects default to private — an owner-carrying object with no explicit sharingModel is now private; the read / read_write / full OWD aliases were removed. Declare sharingModel: 'public_read_write' to restore prior behavior.
  • RBAC link-table writes gated — writes to sys_user_position, sys_position_permission_set, sys_user_permission_set, and sys_permission_set require tenant admin or a delegated-admin scope.

And new: everyone / guest audience anchors, delegated administration (PermissionSet.adminScope), the explain() engine with per-layer attribution, the access-matrix snapshot gate for os compile, self-serve OAuth 2.1 for MCP clients, author-time security lints (validateSecurityPosture), per-operation Object.requiredPermissions maps, and package namespace-prefix enforcement. Never-enforced schemas (compliance / masking / global-RLS configs) were removed outright.

12.x

@objectstack 12.0 hardened the API default posture:

  • Anonymous data access denied by default (breaking) — api.requireAuth now defaults to true; anonymous /data/* requests get 401. Public-data deployments must opt out explicitly with api: { requireAuth: false } (boot warning). Share links, public forms, /auth, and /health are unaffected.
  • Enforced protocol handshakePROTOCOL_VERSION + checkProtocolCompat() reject incompatible packages at install time.
  • Adaptive record surfaces — records derive page vs. modal/drawer display from field complexity; FormField.span goes responsive ('auto' / 'full'); related lists gain 'primary' tab promotion.
  • Package-shipped permissions — packages declare default permission sets that materialize at boot with provenance tracking.
  • Build-time lintslint-view-refs, validateListViewMode, validateFormLayout, and destructive-operation RBAC mapping gate compilation.

10.x

The 10.x train shipped on @objectstack 10.0 – 10.2. Every breaking change landed in the multi-org tenancy layer, so a single-tenant ObjectOS deployment rolled forward from 9.x with no config changes. What moved:

  • Row-level org scoping split into its own plugin (10.0, breaking) — the organization_id auto-stamp, per-org seed replay, and default-org bootstrap moved out of @objectstack/plugin-security into the opt-in @objectstack/plugin-org-scoping. Single-org deployments are leaner (no wildcard RLS to strip on every query), and OS_MULTI_TENANT=true still auto-registers the plugin ahead of plugin-security, so projects driven by the CLI need no code changes. Per ADR-0002, a tenant is physical isolation (one Environment = one database); organization_id is logical scoping inside one DB, so it earns its own plugin.
  • No more automatic personal workspaces (10.0, breaking) — plugin-security no longer creates a "<User>'s Workspace" org for every new user. Users now accept an invitation or explicitly create their first organization (the Slack / Linear / GitHub-Orgs model). The first registered user — auto-promoted to platform admin — still gets a single Default Organization in multi-tenant mode so their session resolves RLS; single-tenant deployments create no orgs.
  • record form field type (ADR-0014) — Record<string, X> properties (such as an object's fields map) are now editable as first-class form fields in Studio instead of leaking through as raw JSON, and the object preview pane mounts the real ObjectGrid renderer — what you preview is what ships.
  • Pluggable embeddings via IEmbedder — a new embedder protocol plus @objectstack/embedder-openai; the knowledge / RAG adapters now consume IEmbedder and were renamed to drop the plugin- prefix.
  • Public Forms — anonymous Web-to-Lead / Web-to-Case forms served at /f/:slug, a unified FormPage (public /f/:slug + internal /forms/:name), a type: 'form' action variant, and defaultDetailForm on ObjectSchema.
  • Cloud identity splitos cloud login is now separate from os login, the cloud control plane moved to a private repo (a leaner OSS runtime), and objectstack init again scaffolds a project that builds and boots.

9.x

The 9.x train ran on @objectstack 9.0 – 9.11 before the 10.0 cutover. The runtime boot contract is unchanged from 8.0 — createStandaloneStack still takes the same artifact, environment, and database settings — so an 8.0 deployment rolls forward without config changes. The author-facing surface is what moved:

  • Analytics datasets are the single author surface (9.0, breaking) — dashboard widgets, reports, and list-charts now bind a semantic dataset (defineDataset(...)) and select dimensions/measures by name. The old inline query fields (object/valueField/aggregate on widgets, objectName/columns/groupingsDown on reports, xAxisField/yAxisFields on list-charts) were removed. Migration: move the inline query into a defineDataset and reference it by name. ChartTypeSchema also dropped 8 variant types that only rendered as their base (stacked-barbar, splineline, bubblescatter, …).
  • Stricter build-time validation (9.6–9.7) — os compile now fails on bare field references (amount instead of record.amount), unknown CEL functions, and wrong flow-value interpolation syntax, each with a did-you-mean hint. A stack that previously "built but was silently wrong" now fails loudly — re-run os compile after upgrading and fix what it flags.
  • Number-field formulas compute mixed arithmetic (9.7) — record.amount / 100 and record.price * 2 now evaluate instead of silently yielding null; the / 100.0 float-literal workaround is no longer needed.
  • Object-level REST gating, now enforced (ADR-0049) — an object's apiEnabled: false removes it from the REST surface, and an apiMethods whitelist restricts which operations are reachable. Previously parsed but not enforced.
  • Package documentation as metadata + book navigation (9.3–9.6) — src/docs/*.md register as doc metadata; the book element (ADR-0046) declares a derived-membership navigation spine, served at GET /api/v1/meta/book/:name/tree with audience gating.
  • os package install (9.3) — install a package into a running runtime from a catalog id or an inline air-gapped artifact, authenticating with --email / --password.
  • Approvals (9.3) — send-back-for-revision (maxRevisions, default 3), jobs-backed SLA auto-escalation, list search/pagination, and session-less bilingual approve/reject confirm links.
  • Inbound webhook flow triggers (9.3) — a type: 'api' flow mounts an HMAC-verified POST /api/v1/automation/hooks/:flowName/:hookId endpoint with idempotent, queue-backed ingestion.
  • Notification retention defaults on (9.5) — notification history auto-prunes at 90 days; set the messaging retentionDays: 0 to keep history forever.
  • CLI bundles AI provider SDKs (9.0) — OpenAI-compatible providers (DeepSeek, DashScope, SiliconFlow, OpenRouter, Cloudflare) work out of the box on a globally-installed CLI.

One flow-authoring behavior change to note: a create_record node's outputVariable now holds the created record object (was the bare id), so update {var} references that expected an id to {var.id}.

8.0.x

ObjectOS One and the bundled server shipped on @objectstack 8.0.1.

  • MCP over Streamable HTTP — every deployment can act as a network-reachable Model Context Protocol server. Opt in with OS_MCP_SERVER_ENABLED=true; the endpoint is served at /api/v1/mcp with fail-closed auth (anonymous requests are rejected). The plugin was renamed @objectstack/plugin-mcp-server@objectstack/mcp.
  • Self-serve API keysPOST /api/v1/keys mints a show-once sys_api_key. The REST data and metadata APIs (/api/v1/data, /api/v1/meta) now authenticate API keys through the same verifier as MCP, running under the key owner's permissions and record-level security.
  • Field-level conditional rulesvisibleWhen, readonlyWhen, and requiredWhen are enforced server-side by ObjectQL, not just in the form UI.
  • Reusable RLS read filtersecurity.getReadFilter(object, context) exposes the record-access read scope; analytics datasets, dashboards, and reports bridge to it and fail closed when the scope cannot be applied.
  • Standalone host stack — the runtime ships a single-tenant createStandaloneStack host; the 7.x cloud-connected, hostname-routed createObjectOSStack wrapper was removed. A deployment that runs a separately published app names that artifact by URL from then on. For the variable that does this today, and the retired spellings that do not, see Environment Variables.

5.0 — projectenvironment rename (shipped)

The runtime concept formerly called Project was renamed to Environment throughout. Affected:

  • CLI flags: --environment / -e
  • HTTP paths: /api/v1/environments/:environmentId/...
  • Headers: X-Environment-Id
  • Env vars: OS_ENVIRONMENT_ID (OS_PROJECT_ID was kept as a deprecated alias at the time; it is no longer read)
  • DB columns: environment_id
  • JSON schemas: EnvironmentArtifact

Upgrading

See Upgrade and Rollback for the mechanical steps. Pre-flight:

  1. Read the CHANGELOG entry for every minor between your current and target version.
  2. Run os diff <old-artifact> <new-artifact> to surface breaking schema changes.
  3. Run os doctor against the target version.
  4. Bring up one canary instance before rolling the fleet.
  5. Have a rollback plan for both the image tag and the artifact version (they roll independently).

Reporting regressions

If a patch or minor breaks something previously working, file a bug at github.com/objectstack-ai/objectos/issues with the version you upgraded from / to. We treat regressions as the highest-priority class of bug.

On this page