From 78b78d363e450874db065ed7fcad6a5268f830f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 01:10:17 +0000 Subject: [PATCH 1/3] =?UTF-8?q?docs(releases,nav):=20v17=20follow-up=20?= =?UTF-8?q?=E2=80=94=20Console=20gap=20backfill,=20sidebar=20restore,=20fi?= =?UTF-8?q?rst=20docs=20sweep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three related closures on the just-merged v17 release page (#3906): 1. The Console section was missing 25 objectui commits. The v17 frontend window is cf2d56e32a11 → 4a4829d0ef39 = 128 commits, but the pending console-*.md changesets covered only 103: the range 2cb8d78e24ad...c6cfdf1288b6 landed with no changeset — exactly the "SHA bump leaves no trace" failure scripts/bump-objectui.sh exists to prevent. Two of the missing commits are breaking and each is the frontend half of a backend change the page already documented (useClientNotifications delegates ↔ #3612 SDK removal; @object-ui/types Capabilities re-exports ↔ the retired capabilities cluster). Both now sit beside their backend halves and in the upgrade checklist, with the other eight substantive commits folded into the Console section and .changeset/console-c6cfdf1288b6-backfill.md recording the range itself. 2. "releases" returns to the root sidebar. #3423 unlisted References and Releases together ("lookup content ... the docs home page links both"); that trade holds for References (22 generated pages) but not for Releases in a release window, when the upgrade notes are what people open the docs for. References stays out. check-release-notes now enforces what its success line already claimed: the section must be reachable via the root nav OR a /docs/releases link on the docs home — either satisfies it (preserving #3423's option), losing both fails the build. Negative-tested both directions. 3. First v17 docs sweep (docs/v17-docs-sweep.md — playbook + append-only run log with a watermark, built to re-run while the RC train iterates). Run 1 fixed the drift v17 left in hand-written prose: services-checklist (17→16 services, graphql rows, the dissolved ObjectStackProtocol), the anonymous-deny matrix still listing /graphql + handleGraphQL, sharing docs describing owner/group shapes as "declared but not enforced" (they no longer parse), http-protocol claiming enable.trash exists, REST/GraphQL in objectql diagrams/prose, and the objectstack-api skill description (mirrors regenerated via gen:skill-docs). False positives and out-of-scope findings (spec comment drift, implementation-status.mdx staleness) are logged for the next run. Verified: pnpm docs:build passes; check:release-notes / doc-authoring / role-word / org-identifier / nul-bytes / skill-docs-sync all green; the new guard branch fails when both entry points are removed and passes with either. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LXvaYR7TiJBCxYwjn51owH --- .changeset/console-c6cfdf1288b6-backfill.md | 31 ++++++ .changeset/v17-page-console-gap-and-nav.md | 52 +++++++++ content/docs/ai/skills-reference.mdx | 6 +- content/docs/kernel/services-checklist.mdx | 15 +-- content/docs/meta.json | 3 +- content/docs/permissions/authorization.mdx | 4 +- .../docs/permissions/field-level-security.mdx | 2 +- content/docs/permissions/index.mdx | 2 +- .../docs/permissions/permissions-matrix.mdx | 2 +- .../docs/protocol/kernel/http-protocol.mdx | 9 +- content/docs/protocol/objectql/index.mdx | 6 +- content/docs/protocol/objectql/schema.mdx | 2 +- content/docs/releases/v17.mdx | 41 ++++++- docs/v17-docs-sweep.md | 104 ++++++++++++++++++ scripts/check-release-notes.mjs | 37 +++++++ skills/README.md | 2 +- skills/objectstack-api/SKILL.md | 4 +- 17 files changed, 287 insertions(+), 35 deletions(-) create mode 100644 .changeset/console-c6cfdf1288b6-backfill.md create mode 100644 .changeset/v17-page-console-gap-and-nav.md create mode 100644 docs/v17-docs-sweep.md diff --git a/.changeset/console-c6cfdf1288b6-backfill.md b/.changeset/console-c6cfdf1288b6-backfill.md new file mode 100644 index 0000000000..38578dd73f --- /dev/null +++ b/.changeset/console-c6cfdf1288b6-backfill.md @@ -0,0 +1,31 @@ +--- +"@objectstack/console": minor +--- + +Console (objectui) backfill for `2cb8d78e24ad...c6cfdf1288b6` — the one refresh in +the v17 window that landed with no changeset. + +`scripts/bump-objectui.sh` emits a `@objectstack/console` changeset on every bump +precisely so a SHA move leaves a trace (see `docs/releases-maintenance.md`). One +bump in this window did not, so 25 commits — including two breaking ones — were +absent from the release history and from the curated v17 page. This entry records +them after the fact; it declares no new SHA move (`.objectui-sha` already points +past this range at `4a4829d0ef39`). + +Frontend changes in this range: + +- feat(react)!: trim dead device/preference delegates from useClientNotifications (objectstack#3612 companion) (#2862) +- feat(types)!: drop the ObjectStack/ObjectOS/ObjectQL/ObjectUI Capabilities re-exports (#2860) +- feat: gate detail/form edit & delete on the server's effective operation set (framework#3546) (#2832) +- feat(app-shell): approver values become record lookups (framework#3508) (#2834) +- feat(console): group tenancy posture affordances — org switcher as write context + org attribution (ADR-0105 Phase 1) (#2858) +- feat(console): i18n the system-settings hub (objectui#2851 P2) (#2859) +- fix(dashboard,charts): resolve `{current_user_id}` in widget filters (framework#3574) (#2857) +- fix(grid): validate email format in the import preview (objectstack#3566) (#2840) +- fix(fields): consistent image-field rendering + click-to-zoom (#2836) (#2837) +- fix(app-shell): stop the flow-node repeater from committing during render (#2838) (#2839) + +Plus 15 dependency bumps, three of them major for the Console's own build: +`maplibre-gl` 5→6, `chalk` 5→6, `jsdom` 29→30 (dev). + +objectui range: `2cb8d78e24ad...c6cfdf1288b6` diff --git a/.changeset/v17-page-console-gap-and-nav.md b/.changeset/v17-page-console-gap-and-nav.md new file mode 100644 index 0000000000..75f3c7a0a5 --- /dev/null +++ b/.changeset/v17-page-console-gap-and-nav.md @@ -0,0 +1,52 @@ +--- +--- + +Docs-only: close two gaps in the v17 release page and make the Releases section +reachable from the sidebar again. Releases nothing. + +**The Console section was missing 25 objectui commits.** The v17 frontend window +is `cf2d56e32a11` (what 16.1.0 pinned) → `4a4829d0ef39` = 128 commits, but the +pending `console-*.md` changesets only covered 103 of them: the range +`2cb8d78e24ad...c6cfdf1288b6` landed with no changeset, which is exactly the +"a SHA bump leaves no trace" failure `scripts/bump-objectui.sh` exists to +prevent. Two of the missing commits are breaking, and both are the frontend half +of a backend change the page already documented — so the page described one side +of each and omitted the other: + +- `useClientNotifications` loses its dead device/preference delegates + (objectui#2862), the companion to the SDK removal in #3612 +- `@object-ui/types` drops the Capabilities re-exports (objectui#2860), the + companion to the retired capabilities-descriptor cluster + +Both now appear beside their backend halves and in the upgrade checklist, along +with the other eight substantive commits (effective-operation-set gating on +detail/form, approver record lookups, `{current_user_id}` in widget filters, org +switcher as write context, system-settings i18n, image click-to-zoom, import +email validation, flow-node repeater render fix) and the three major build-dep +bumps. `.changeset/console-c6cfdf1288b6-backfill.md` records the range itself so +the history no longer has a hole. + +**`releases` returns to the root sidebar.** #3423 took References and Releases +out of the tree as a decluttering pass, on the stated basis that "URLs still +resolve and the docs home page links both". That trade holds for References (22 +generated pages) but reads poorly for Releases during a release window, when +finding the upgrade notes is the reason someone opened the docs. References stays +out. + +**First v17 docs sweep** (`docs/v17-docs-sweep.md` — playbook + append-only run +log, designed for re-runs while the RC train keeps accumulating changesets). +Run 1 fixed the drift v17 left in hand-written prose: `services-checklist` +(17→16 services, graphql rows, the dissolved `ObjectStackProtocol`), +authorization/permissions pages still describing `/graphql` in the +anonymous-deny matrix and owner/`group` sharing shapes as "declared but not +enforced" (they no longer parse), `http-protocol` claiming `enable.trash` +exists, "REST/GraphQL" in objectql diagrams/prose, and the `objectstack-api` +skill description (mirrors regenerated via `gen:skill-docs`). + +**`check-release-notes` now checks what it claims.** Its success line said every +release page is "navigable" while it only ever read the section's own +`meta.json` — it could not see that the section had been unlisted from the root +nav, so it stayed green for the entire period the whole section was absent from +the sidebar. It now also requires the section to be reachable via the root nav +**or** a `/docs/releases` link on the docs home page: either satisfies it, so +#3423's trade remains available, but losing both now fails the build. diff --git a/content/docs/ai/skills-reference.mdx b/content/docs/ai/skills-reference.mdx index 449df50bec..14e9cd92ce 100644 --- a/content/docs/ai/skills-reference.mdx +++ b/content/docs/ai/skills-reference.mdx @@ -51,7 +51,7 @@ ObjectStack ships **9 domain-specific skills**. Each is self-contained — an AI | 4 | [UI](#ui) | `ui` | `skills/objectstack-ui/` | Author ObjectStack UI metadata — Views (list/form/kanban/calendar/gantt), Apps (navigation), Pages (structured plus the HTML and React source-authoring tiers, ADR-0080/0081), Dashboards, Reports, Charts, Actions, and package Docs (`src/docs/*.md`). | | 5 | [Automation](#automation) | `automation` | `skills/objectstack-automation/` | Design ObjectStack automation — Flows (visual logic), Triggers, Approvals, state machines, scheduled jobs, and webhooks. | | 6 | [AI](#ai) | `ai` | `skills/objectstack-ai/` | Design ObjectStack AI skills, tools, knowledge sources, conversations, model registry entries, and MCP integrations. | -| 7 | [API](#api) | `api` | `skills/objectstack-api/` | Design the server-side API surface that an ObjectStack runtime exposes — REST/GraphQL endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | +| 7 | [API](#api) | `api` | `skills/objectstack-api/` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | | 8 | [i18n](#i18n) | `i18n` | `skills/objectstack-i18n/` | Author ObjectStack translation bundles — object/field labels, view text, app navigation strings, automation messages — and configure locale fallback, coverage reporting, and the per-locale source layout. | | 9 | [Formula](#formula) | `expression` | `skills/objectstack-formula/` | Author CEL expressions used across ObjectStack — formula fields, field conditional rules (`visibleWhen`, `readonlyWhen`, `requiredWhen`), validation / sharing / visibility predicates, flow conditions, and dynamic seed values. | @@ -145,9 +145,9 @@ Do not use for general LLM prompting questions unrelated to ObjectStack metadata **Domain** `api` · **Path** `skills/objectstack-api/` -Design the server-side API surface that an ObjectStack runtime exposes — REST/GraphQL endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. +Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. -Use when the user is adding `*.endpoint.ts`, configuring auth providers, defining custom routes, or extending the REST/GraphQL generator. +Use when the user is adding `*.endpoint.ts`, configuring auth providers, defining custom routes, or extending the REST generator. Do not use for: consuming an ObjectStack API from a client (that is just standard HTTP — no skill needed); the auto-generated CRUD endpoints (those follow from objectstack-data); request-side query syntax (see objectstack-query). CEL expressions in route guards or auth predicates: load objectstack-formula alongside. diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index 1916f5468b..3bf944daa0 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -13,7 +13,7 @@ for the protocol-method inventory; treat the per-service status columns as historical until this page is regenerated. -The ObjectStack protocol defines **17 kernel services** registered via the `CoreServiceName` enum. Each service maps to a set of protocol methods (57 total), governed by the `ObjectStackProtocol` interface. +The ObjectStack protocol defines **16 kernel services** registered via the `CoreServiceName` enum (the never-implemented `graphql` entry was removed in v17). Each service maps to a set of protocol methods governed by its per-domain contract (`DataProtocol`, `MetadataProtocol`, ...) — the transitional `ObjectStackProtocol` composition alias was dissolved in v17 (ADR-0076 D9); capability availability comes from the runtime discovery `services` registry. **Key architecture principle**: The kernel itself only provides **data** and **metadata** (framework). Everything else — including **auth** and **automation** — is delivered by **plugins**. The current `@objectstack/objectql` package is an example kernel implementation to get the basic API running; production kernels will be rebuilt as new plugins. @@ -42,7 +42,7 @@ The ObjectStack protocol defines **17 kernel services** registered via the `Core ├─────────────────────────────────────────────────────────┤ │ Plugin Layer │ │ All other services: auth, automation, workflow, ui, │ -│ realtime, notification, ai, i18n, graphql, search, │ +│ realtime, notification, ai, i18n, search, │ │ file-storage, cache, queue, job │ │ │ │ Discovery API reports: enabled/unavailable/degraded │ @@ -72,7 +72,6 @@ The ObjectStack protocol defines **17 kernel services** registered via the `Core | 14 | **cache** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` | | 15 | **queue** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` | | 16 | **job** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-job` | -| 17 | **graphql** | `optional` | — | ❌ Plugin Required | TBD plugin | **Criticality Levels** @@ -282,7 +281,7 @@ The i18n service is a **core built-in service** with automatic in-memory fallbac // Production (overrides built-in fallback) kernel.use(new I18nServicePlugin({ defaultLocale: 'en', localesDir: './i18n' })); -// Development (automatic — DevPlugin registers i18n stub for all 17 core services) +// Development (automatic — DevPlugin registers i18n stub for all 16 core services) kernel.use(new DevPlugin()); // No plugin required — kernel auto-injects in-memory fallback if none registered @@ -331,9 +330,9 @@ AppPlugin will: --- -## 12–17. Infrastructure Services +## 12–16. Infrastructure Services -`cache`, `queue`, and `job` are `core` services: like `i18n`, the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). The `optional` services (`file-storage`, `search`, `graphql`) stay disabled until a plugin provides them. +`cache`, `queue`, and `job` are `core` services: like `i18n`, the kernel auto-injects an in-memory fallback when no plugin registers them (see `CORE_FALLBACK_FACTORIES` in `packages/core/src/fallbacks/`). The `optional` services (`file-storage`, `search`) stay disabled until a plugin provides them. | Service | Description | |:--------|:------------| @@ -342,7 +341,6 @@ AppPlugin will: | **cache** | General-purpose cache. In-memory fallback; Redis via `@objectstack/service-cache`. | | **queue** | Message queue. In-memory fallback; BullMQ / Redis via `@objectstack/service-queue`. | | **job** | Scheduled task execution. In-memory fallback; cron-based, concurrency control. | -| **graphql** | Auto-generate GraphQL schema from Object metadata. | --- @@ -373,8 +371,7 @@ AppPlugin will: |:-------|:------------| | **plugin-realtime** | WebSocket / SSE real-time push | | **plugin-notification** | Notification engine | -| **plugin-ai** | NLQ, chat, suggestions, insights | -| **plugin-graphql** | GraphQL API endpoint | +| **plugin-ai** | Chat, completion, models, conversations (`service-ai`, Cloud/EE) | | **plugin-search** | Full-text search | | **plugin-job** | Scheduled tasks | | **plugin-queue** | Message queue | diff --git a/content/docs/meta.json b/content/docs/meta.json index 1855420376..968b3e7a1b 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -17,6 +17,7 @@ "deployment", "plugins", "kernel", - "protocol" + "protocol", + "releases" ] } diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index 8ca869adfa..5f0c89061e 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -48,10 +48,10 @@ site — the file you read when behavior surprises you. | # | Gate | What it decides | Enforcement site | Failure direction | |---|---|---|---|---| -| 1 | **Anonymous deny** | No identity → HTTP 401. **Uniform across every HTTP surface that reaches object data** (#2567): REST `/data`, the metadata endpoints (`/meta`), the dispatcher GraphQL endpoint (`/graphql`), and the raw-hono standard `/data` routes — one shared decision, so a caller denied on `/data` can't read the same rows through a sibling door. **Default-on** (ADR-0056 D2): public serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. | `packages/core/src/security/anonymous-deny.ts` `shouldDenyAnonymous` — called by `rest-server.ts` `enforceAuth`, the dispatcher `handleGraphQL`/`handleMetadata`/`handleAI`, and `plugin-hono-server` `denyAnonymous` (default in `packages/spec/src/api/rest-server.zod.ts`); a source-enumerating ratchet in `authz-conformance.test.ts` fails CI if a new surface ships ungated | fail-closed | +| 1 | **Anonymous deny** | No identity → HTTP 401. **Uniform across every HTTP surface that reaches object data** (#2567): REST `/data`, the metadata endpoints (`/meta`), and the raw-hono standard `/data` routes (the dispatcher GraphQL endpoint left the matrix when the GraphQL surface was removed in v17 — `/graphql` now 404s) — one shared decision, so a caller denied on `/data` can't read the same rows through a sibling door. **Default-on** (ADR-0056 D2): public serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. | `packages/core/src/security/anonymous-deny.ts` `shouldDenyAnonymous` — called by `rest-server.ts` `enforceAuth`, the dispatcher `handleMetadata`/`handleAI`, and `plugin-hono-server` `denyAnonymous` (default in `packages/spec/src/api/rest-server.zod.ts`); a source-enumerating ratchet in `authz-conformance.test.ts` fails CI if a new surface ships ungated | fail-closed | | 2 | **Public-form grant** | An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No guest-portal configuration needed (anonymous principals hold the `guest` position). | `packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow | | 3 | **Object CRUD** | `allowRead/Create/Edit/Delete` (+ the destructive lifecycle class `allowTransfer/Restore/Purge`, gated ahead of the M2 operations — #1883) resolved across the caller's permission sets. | `packages/plugins/plugin-security/src/permission-evaluator.ts` `checkObjectPermission` | fail-closed 403 | -| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`; **unset or unknown ⇒ `private`, fail-closed** — ADR-0090 D1) plus the external dial (`externalSharingModel`, ADR-0090 D11), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not positions). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | fail-closed to owner-only | +| 4 | **OWD / sharing** | Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`; **unset or unknown ⇒ `private`, fail-closed** — ADR-0090 D1) plus the external dial (`externalSharingModel`, ADR-0090 D11), manual record shares, criteria sharing rules (owner-type rules were removed from the authoring surface in v17 rather than left declared-but-skipped — [Sharing Rules](/docs/permissions/sharing-rules#recipient-types)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not positions). | `packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts` | fail-closed to owner-only | | 5 | **Row-level security** | CEL predicates (`using` read filter, `check` write post-image) compiled into the query. If **no** applicable policy compiles, the result is a deny-all sentinel (fail-closed); an uncompilable policy alongside compilable ones is excluded from the OR-union **with a logged warning** — exclusion can only narrow access, never widen it. Tenant isolation is a wildcard RLS rule AND-ed on top. | `packages/plugins/plugin-security/src/rls-compiler.ts` + `security-plugin.ts` | fail-closed | | 6 | **Field-level security** | Read mask (strip non-readable fields) + write deny per `fields` rules. Caller queries that **filter, sort, group, or aggregate by** a non-readable field are rejected outright (HTTP 403, `field_predicate_denied`) — masking only the output would leave row presence as a value oracle. RLS-injected predicates are exempt (they run after the guard and may reference hidden fields like `owner_id`). | `packages/plugins/plugin-security/src/field-masker.ts` + `predicate-guard.ts` | fail-closed on predicates; see posture note below | diff --git a/content/docs/permissions/field-level-security.mdx b/content/docs/permissions/field-level-security.mdx index 229bed1996..bcc741d1d3 100644 --- a/content/docs/permissions/field-level-security.mdx +++ b/content/docs/permissions/field-level-security.mdx @@ -41,7 +41,7 @@ fields: { The client-side ObjectForm / inline grid hides non-editable fields from the UI — but that is a **UX layer only**. The SecurityPlugin middleware enforces field-level write rules on the server, regardless of how the -request arrived (REST, GraphQL, raw ObjectQL call). +request arrived (REST, SDK, raw ObjectQL call). **On read** — `find` / `findOne` results have non-readable fields stripped from every record before the response leaves the engine. diff --git a/content/docs/permissions/index.mdx b/content/docs/permissions/index.mdx index 68e736e5ad..8db4556533 100644 --- a/content/docs/permissions/index.mdx +++ b/content/docs/permissions/index.mdx @@ -44,7 +44,7 @@ export const HighValueOpportunitySharingRule = defineSharingRule({ Because AI agents act through the same permission-aware surface, these rules bound agent access exactly as they bound users ([Actions as Tools](/docs/ai/actions-as-tools)). -> **Implementation status — Permission Model v2 (ADR-0090) is live.** REST → ObjectQL propagates a populated `ExecutionContext` (userId, tenantId, positions, permissions, principalKind) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks fire on every authenticated request; authenticated principals implicitly hold the `everyone` position and anonymous principals hold `guest` (D9). The `member_default` baseline is **additive** (D5 — no fallback cliff) and its owner-write policies are domained to `org_member` holders. The default `member_default` set ships the wildcard tenant-isolation RLS (`organization_id == current_user.organization_id`) with the same canonical-name contract as before; SecurityPlugin is the sole authority for tenant isolation, and analytics auto-bridges to `security.getReadFilter`. **Anonymous traffic is denied by default** (ADR-0056 D2), and public forms self-authorize via a declaration-derived `publicFormGrant`. **An unset OWD fails closed to `private`** (D1) and the D7 publish linter makes it a build error. Criteria sharing rules (with `position` / `unit_and_subordinates` recipients) are live and dogfood-proven; **owner-type rules and `group` recipients are declared but not enforced** — the seeder skips them with a warning ([Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules)). RBAC-table writes are governed by the delegated-admin gate (D12), and the `security` service answers `explain(request)` per evaluation layer (D6). The Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See [Implementation Status](/docs/releases/implementation-status) for the latest matrix. +> **Implementation status — Permission Model v2 (ADR-0090) is live.** REST → ObjectQL propagates a populated `ExecutionContext` (userId, tenantId, positions, permissions, principalKind) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks fire on every authenticated request; authenticated principals implicitly hold the `everyone` position and anonymous principals hold `guest` (D9). The `member_default` baseline is **additive** (D5 — no fallback cliff) and its owner-write policies are domained to `org_member` holders. The default `member_default` set ships the wildcard tenant-isolation RLS (`organization_id == current_user.organization_id`) with the same canonical-name contract as before; SecurityPlugin is the sole authority for tenant isolation, and analytics auto-bridges to `security.getReadFilter`. **Anonymous traffic is denied by default** (ADR-0056 D2), and public forms self-authorize via a declaration-derived `publicFormGrant`. **An unset OWD fails closed to `private`** (D1) and the D7 publish linter makes it a build error. Criteria sharing rules (with `position` / `unit_and_subordinates` recipients) are live and dogfood-proven; v17 reconciled the authoring surface with the enforced runtime (#1878) — `group` was renamed to the enforced `team`, `business_unit` joined the enum, and owner-type rules and `guest` recipients were pruned rather than left declared-but-skipped ([Sharing Rules](/docs/permissions/sharing-rules#recipient-types)). RBAC-table writes are governed by the delegated-admin gate (D12), and the `security` service answers `explain(request)` per evaluation layer (D6). The Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See [Implementation Status](/docs/releases/implementation-status) for the latest matrix. ## What's in this module diff --git a/content/docs/permissions/permissions-matrix.mdx b/content/docs/permissions/permissions-matrix.mdx index e960dd6b16..5b37b96834 100644 --- a/content/docs/permissions/permissions-matrix.mdx +++ b/content/docs/permissions/permissions-matrix.mdx @@ -157,7 +157,7 @@ Sharing rules extend access beyond ownership and the depth axis. The declarative | **Criteria-Based** | `criteria` | Share records matching a CEL predicate over field values | All opportunities where `record.amount > 100000` are shared with "VP Sales" | -**Enforcement status:** only **criteria** rules are enforced today. Declared `owner`-type rules, and rules with `group` / `guest` recipients, are skipped at seed time with a warning (ADR-0049 — never silently advertised as live). See [Sharing Rules](/docs/permissions/sharing-rules#owner-based-sharing-rules). +**Enforcement status:** every authorable rule and recipient type is enforced. v17 reconciled the surface with the runtime (#1878): `owner`-type rules and `group` / `guest` recipients — previously declared but skipped at seed time — no longer parse; `group` became the enforced `team` and `business_unit` joined the enum. See [Sharing Rules](/docs/permissions/sharing-rules#recipient-types). diff --git a/content/docs/protocol/kernel/http-protocol.mdx b/content/docs/protocol/kernel/http-protocol.mdx index 06a6330186..c1d3ac187e 100644 --- a/content/docs/protocol/kernel/http-protocol.mdx +++ b/content/docs/protocol/kernel/http-protocol.mdx @@ -562,10 +562,11 @@ Delete returns a `DeleteDataResponse`: the `object` name, the record `id`, and a ``` -`enable.trash` exists as an object flag, but there is currently **no soft-delete / -recycle-bin runtime** (ADR-0049): `DELETE` performs a hard delete and returns the -`{ object, id, success }` shape above. Records are removed permanently — there are no -`deleted_at` / `deleted_by` fields and no restore semantics. +There is **no soft-delete / recycle-bin runtime**: `DELETE` performs a hard delete and +returns the `{ object, id, success }` shape above. Records are removed permanently — +there are no `deleted_at` / `deleted_by` fields and no restore semantics. The +`enable.trash` flag that once promised this was removed in v17 (#2377, ADR-0049 +enforce-or-remove): authoring it is now a parse error rather than a silent no-op. **Constraint Violations:** diff --git a/content/docs/protocol/objectql/index.mdx b/content/docs/protocol/objectql/index.mdx index b974a2b2f2..ba7947d872 100644 --- a/content/docs/protocol/objectql/index.mdx +++ b/content/docs/protocol/objectql/index.mdx @@ -48,7 +48,7 @@ Traditional software development chains you to specific database technologies: ┌─────────────────────────────────────────────────────────────┐ │ APPLICATION LAYER │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ REST API │ │ GraphQL │ │ UI Forms │ │ +│ │ REST API │ │ Client SDK │ │ UI Forms │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ └─────────┼─────────────────┼─────────────────┼───────────────┘ │ │ │ @@ -193,7 +193,7 @@ validations: **Validation executes:** - Before database write (server-side) - In UI forms (compiled to JavaScript) -- In API requests (REST/GraphQL) +- In API requests (REST) - In bulk imports (CSV, Excel) ## Real-World Use Cases @@ -300,7 +300,7 @@ ObjectQL (Data Layer) │ └→ Row-level security, field-level access │ ├→ API Protocol - │ └→ REST/GraphQL endpoints + │ └→ REST endpoints │ └→ Automation Protocol └→ Triggers, workflows, scheduled jobs diff --git a/content/docs/protocol/objectql/schema.mdx b/content/docs/protocol/objectql/schema.mdx index 47b8a7dea6..d87d1655ec 100644 --- a/content/docs/protocol/objectql/schema.mdx +++ b/content/docs/protocol/objectql/schema.mdx @@ -238,7 +238,7 @@ enable: trackHistory: true # Track field history (who changed what, when) searchable: true # Index for global search # API & Integration - apiEnabled: true # Generate REST/GraphQL endpoints + apiEnabled: true # Generate REST endpoints # Collaboration & Activities files: true # Enable file attachments diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index 7249ff1fbd..87e690d803 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -325,6 +325,11 @@ call was a guaranteed 404. They are removed, along with the four unconsumed flag was accepted, transmitted and dropped with no seeding, no error and no stored trace +**Console consumers:** the companion objectui change trims the matching dead +delegates from `useClientNotifications` (`@object-ui/react`), so a host calling +`registerDevice` / `unregisterDevice` / `getPreferences` / `updatePreferences` +through that hook loses them at the same time (objectui#2862). + Kept: `client.events` (explicitly a local in-memory buffer), the dispatcher-served `notifications.list/markRead/markAllRead`, `approvals.*`, and `meta.getLegalNextStates`. Re-adding any removed surface now requires the server @@ -473,6 +478,11 @@ import or the authored key. | Aspirational config on Theme / Translation / Webhook | still-dead after #3494 | | `ChartInteraction.zoom` / `.clickAction` | never implemented (#3752) | +The Console side follows: `@object-ui/types` drops its +`ObjectStack`/`ObjectOS`/`ObjectQL`/`ObjectUI` Capabilities re-exports, which +pointed at the same retired cluster (objectui#2860). Import what you still need +from `@objectstack/spec` directly. + ### Smaller breaking changes - **MongoDB driver declares itself single-tenant** and refuses to boot in a @@ -695,8 +705,8 @@ most of its rows in silence. ## New in Console (Studio) — bundled objectui 17.0 -The v17 line bundles four Console refreshes, landing on the pin -`4a4829d0ef39` (`.objectui-sha`). +The v17 Console window is `cf2d56e32a11 → 4a4829d0ef39` (`.objectui-sha`) — 128 +objectui commits on top of the pin 16.1.0 shipped. ### Files, actions and forms @@ -707,7 +717,9 @@ The v17 line bundles four Console refreshes, landing on the pin object; the spec's `disabled` predicate is honored on every action-rendering surface; inline `lookup` action params get a real record picker. - Forms consume spec-aligned `FormView` buttons/defaults, and an invalid submit - scrolls to and focuses the first errored field. + scrolls to and focuses the first errored field; the flow-node repeater stops + committing during render. +- Image fields render consistently and support click-to-zoom. ### Approvals @@ -735,7 +747,12 @@ The v17 line bundles four Console refreshes, landing on the pin - A toast fires when a save silently dropped read-only fields, and write warnings stop being lost on the detail page. - Real per-caller **FLS** is wired into import targets and grid columns, and the - Import Wizard gains an **"Import as historical data"** option. + Import Wizard gains an **"Import as historical data"** option; the import + preview validates email format up front. +- **Detail and form edit/delete are gated on the server's effective operation + set** (#3546), the same adjudication the grid rows use — the Console stops + offering a verb the server would refuse. +- Dashboard and chart widget filters resolve `{current_user_id}` (#3574). - The five per-view-type configs and `ListView` read the spec-canonical vocabulary (`filter`, `$notContains`, type/label/maxLength keys), and `secret` stays out of inline edit. @@ -753,7 +770,10 @@ The v17 line bundles four Console refreshes, landing on the pin operator-facing reason; the sharing-rule dialog becomes usable (i18n, a picker that lists people, permission-aware CTAs); `delegated_admin` is reachable and both of its pickers are narrowed; scoped-invitation placement invites straight - into a unit and its positions. + into a unit and its positions; the flow designer reads approver value sources + off the schema, and approver values become **record lookups** (#3508). +- **Group tenancy posture affordances** (ADR-0105 Phase 1): the org switcher + becomes the write context, and records carry org attribution. - The API console lists the whole **AI family** and the routes that exist, and the tool preview stops linking to a 404. @@ -762,9 +782,13 @@ The v17 line bundles four Console refreshes, landing on the pin - **The locale backfill completes: all ten packs reach full key parity.** The four highest-traffic namespaces are translated into the eight trailing locales, hand-rolled zh/en branches and `pick({en,zh})` clones are retired, and `en` - becomes the complete source of truth for grid import and set-password. + becomes the complete source of truth for grid import and set-password. The + system-settings hub is localized. - ESLint runs on PRs across every package, and the last five unchecked packages are type-checked — which surfaced two runtime bugs hiding there. +- Console build dependencies take three major bumps — `maplibre-gl` 5→6 (the + `plugin-map` default import is dropped to match), `chalk` 5→6, and `jsdom` + 29→30 (dev) — relevant if you build the Console from source. ## Upgrade checklist @@ -804,6 +828,11 @@ The v17 line bundles four Console refreshes, landing on the pin device/preference helpers, `client.ai.{nlq,suggest,insights}` and `projects.listTemplates()`; repoint marketplace publish to `POST /api/v1/packages/publish`; drop `os environments create --template`. +- **Console hosts:** the same removals land in objectui — drop the + `useClientNotifications` device/preference delegates (`@object-ui/react`) and + replace the retired `@object-ui/types` Capabilities re-exports with imports + from `@objectstack/spec`. If you build the Console from source, note the + `maplibre-gl` 5→6 / `chalk` 5→6 major bumps. - **Type importers:** replace `ObjectStackProtocol` / `ObjectStackProtocolSchema` with the narrowest per-domain slices; drop GraphQL types and any of the removed dead spec clusters. diff --git a/docs/v17-docs-sweep.md b/docs/v17-docs-sweep.md new file mode 100644 index 0000000000..31b6711e59 --- /dev/null +++ b/docs/v17-docs-sweep.md @@ -0,0 +1,104 @@ +# v17 docs sweep — log and playbook + +The v17 train is still accumulating changesets, so the docs cannot be aligned +once and declared done — they need **re-sweeping until `changeset pre exit`**. +This file is both the playbook for running a sweep and the append-only log of +every run, so the next sweep (human or agent) starts from a watermark instead +of re-auditing everything. + +Scope: hand-written prose — `content/docs/**` (excluding `references/`, which +regenerates from spec, and `releases/`, which *documents* the old shapes on +purpose) and `skills/**`. Note `content/docs/ai/skills-reference.mdx` and +`skills/README.md` are **generated** from `skills/*/SKILL.md` +(`pnpm --filter @objectstack/spec gen:skill-docs`) — fix the SKILL.md, then +regenerate; never edit the mirrors. + +## How to run a sweep + +1. Read the changesets added since the last run's watermark: + `git diff --name-only --diff-filter=A ..HEAD -- '.changeset/*.md'` + Extract every **removed / renamed / re-defaulted** surface (the `!:` entries + first, but minors retire surfaces too under the launch-window convention). +2. Extend the fingerprint table below with any new removals, then grep each + fingerprint over the scope. Judge every hit in context — a hit that + *documents the removal* is correct; a hit that *teaches the removed surface* + is drift. +3. Fix drift at the source (SKILL.md before its mirrors), regenerate, run: + `pnpm check:release-notes check:doc-authoring check:role-word` and + `pnpm docs:build`. +4. Append a run entry at the bottom: watermark, fingerprints added, files + fixed, false positives (so the next run can skip re-judging them), and + out-of-scope findings (file an issue per Prime Directive #10). + +## Fingerprints (v17 removals/renames → what a hit means) + +| Pattern | Drift if the doc… | Source changeset | +|---|---|---| +| `conditionalRequired` | teaches it instead of `requiredWhen` | retire-three-deprecated-aliases | +| `action.execute` (as authored key) | teaches it instead of `target` | retire-three-deprecated-aliases | +| `knowledge.topics` | teaches it instead of `sources` | retire-three-deprecated-aliases | +| `agent.tools` / inline tool arrays on agents | teaches the removed slot instead of `skills` | ai-agent-authoring-and-tools-removal | +| `tool.requiresConfirmation` (on **AIToolSchema**) | teaches the removed flag — the action-level `ai.requiresConfirmation` **survives**, do not "fix" it | tool-requires-confirmation-removed | +| `ai.nlq` / `ai.suggest` / `ai.insights` | presents the phantom SDK surface as callable | remove-dead-sdk-surface | +| `client.permissions/realtime/workflow/views` CRUD, notifications device/preference, `listTemplates`, `--template` | presents removed SDK/CLI surface | remove-dead-client-surfaces, drop-dead-* | +| GraphQL as *our* API surface (`/graphql`, `handleGraphQL`, "REST/GraphQL endpoints", `graphql` service, `plugin-graphql`) | advertises the removed surface — external-datasource `'graphql'` protocol and generic industry mentions are **not** drift | remove-graphql-surface | +| `ObjectStackProtocol` (interface/schema) | presents the dissolved alias as the contract | v17-dissolve-protocol-alias | +| `enable.trash` / `enable.mru` | says the flag *exists* (even as "exists but unenforced") | remove-enable-trash-mru | +| legacy `apiMethods` values (`upsert`, `aggregate`, `search`, `history`, `restore`, `purge`, `import`, `export` **inside an apiMethods list**) | teaches authoring them | apimethod-enum-shrink | +| sharing `accessLevel: 'full'`, recipient `group` / `guest`, owner-type rules | presents them as authorable, or as "declared but not enforced" (they no longer parse) | sharing-rule-recipient-reconcile, fix(sharing) #3865 | +| `allowExport` unset ⇒ export allowed | claims export inherits read | export-axis-opt-in | +| `SkillSchema.permissions` | teaches the removed field | prune-skill-permissions | +| Node `18` as a floor | states an out-of-date prerequisite | engines-node-22 | +| `PortalSchema`, `AuditConfig`, Capabilities-descriptor cluster, `FeatureFlagSchema`, `DEFAULT_*_ROUTES`, report `aria`/`performance`, `ReportColumn/GroupingSchema` | teaches a pruned cluster | prune-* family | +| `GetTranslationsRequest` `namespace`/`keys` filters | teaches the dropped filters | i18n-translations-request-drop-phantom-filters | + +## Run log + +### 2026-07-29 — run 1 (baseline) + +- **Watermark:** framework `a641d10` (origin/main, post-#3906) · 305 changesets + present · objectui pin `4a4829d0ef39`. +- **Fixed (drift → corrected):** + - `kernel/services-checklist.mdx` — claimed 17 kernel services incl. + `graphql` and governance by the `ObjectStackProtocol` interface; now 16 + services, per-domain contracts, all graphql rows/diagram entries removed, + `plugin-ai` row no longer names the phantom NLQ/suggest/insights surface. + - `permissions/authorization.mdx` — anonymous-deny row listed `/graphql` + + `handleGraphQL` (removed from the matrix in v17); gate 4 said owner-type + rules are "declared but seed-skipped" (they no longer parse). + - `permissions/permissions-matrix.mdx` — sharing enforcement status still + described the pre-#1878 declared-but-skipped posture. + - `permissions/index.mdx` — status blurb said `group` recipients are + "declared but not enforced" (renamed to `team`; owner/`guest` pruned). + - `protocol/kernel/http-protocol.mdx` — said `enable.trash` "exists as an + object flag" (removed in v17; authoring it is now a parse error). + - `protocol/objectql/index.mdx`, `protocol/objectql/schema.mdx`, + `permissions/field-level-security.mdx` — "REST/GraphQL" in diagrams, + comments and prose. + - `skills/objectstack-api/SKILL.md` — description advertised "REST/GraphQL + endpoints" and a "REST/GraphQL generator"; mirrors regenerated + (`content/docs/ai/skills-reference.mdx`, `skills/README.md`). +- **Judged, not drift (skip re-checking):** + - `data-modeling/fields.mdx` (documents the `conditionalRequired` removal), + `data-modeling/objects.mdx` `apiMethods` row, `api/plugin-endpoints.mdx` + (documents the nlq/suggest/insights inversion), `api/client-sdk.mdx`, + `permissions/permission-sets.mdx` + `sharing-rules.mdx` (already teach the + v17 posture) — all corrected at change time by their changesets. + - `ai.requiresConfirmation` hits in `ai/connect-mcp.mdx`, + `ai/actions-as-tools.mdx`, `protocol/objectui/actions.mdx` — the surviving + action-level key, not the removed tool-level one. + - `automation/webhooks.mdx` "GraphQL subscriptions" — an out-of-scope + comparison in a "what this is not" list, not our surface. + - `ExecutionContext.tenantId` mentions — the internal field survives; only + the hook/action `ctx.tenantId` alias was removed (v16). +- **Out of scope, logged for follow-up:** + - `packages/spec/src/system/core-services.zod.ts` — the `'ai'` enum member's + comment still reads "AI Engine (NLQ, Chat, Suggest, Insights)"; comment-only + spec fix, belongs in a spec-touching PR. + - `content/docs/releases/implementation-status.mdx` — "Last Updated June + 2026", predates the whole v17 window; needs a regeneration pass of its own + rather than spot fixes. +- **Not yet swept:** `examples/**` inline prose and `docs/**` (internal); + lower-priority — user-facing `content/docs` + `skills` covered first. + + diff --git a/scripts/check-release-notes.mjs b/scripts/check-release-notes.mjs index 4cae09efc6..e64f4f3710 100644 --- a/scripts/check-release-notes.mjs +++ b/scripts/check-release-notes.mjs @@ -9,6 +9,12 @@ // content/docs/releases/v.mdx, and that page must be wired into the // section's meta.json so it is actually navigable. // +// "Navigable" is checked at BOTH levels, because wiring a page into a section +// that is itself unreachable proves nothing: the section must be reachable too, +// via the root sidebar (content/docs/meta.json) or a /docs/releases link on the +// docs home page. Either satisfies it — #3423 deliberately traded the sidebar +// entry for the home-page link — but losing both orphans every release page. +// // Released majors are read from the spec package CHANGELOG (the changesets // source of truth). Curated pages only began at v9, and v10/v11 were never // backfilled — those are the documented, intentional exceptions below. Anything @@ -24,6 +30,8 @@ import { readFileSync, existsSync } from 'node:fs'; const SPEC_CHANGELOG = 'packages/spec/CHANGELOG.md'; const RELEASES_DIR = 'content/docs/releases'; const META_PATH = `${RELEASES_DIR}/meta.json`; +const ROOT_META_PATH = 'content/docs/meta.json'; +const DOCS_HOME = 'content/docs/index.mdx'; // Curated release pages started at v9; v10/v11 were never backfilled (see // content/docs/releases/index.mdx). Everything from v12 on must have a page. @@ -43,6 +51,35 @@ const meta = JSON.parse(readFileSync(META_PATH, 'utf8')); const metaPages = new Set(meta.pages || []); const problems = []; + +// The section itself has to be REACHABLE, or every page inside it is orphaned no +// matter how well the section's own meta.json is wired. #3423 took `releases` +// out of the root sidebar deliberately ("lookup content ... the docs home page +// links both"), which is a legitimate IA choice — the failure mode is losing +// BOTH entry points, which this check catches while allowing either one. +// Until now the success message claimed "navigable" while only ever reading the +// section's own meta.json. +const inRootNav = (() => { + try { + return (JSON.parse(readFileSync(ROOT_META_PATH, 'utf8')).pages || []).includes('releases'); + } catch { + return false; + } +})(); +const onDocsHome = (() => { + try { + return /\]\(\/docs\/releases\b/.test(readFileSync(DOCS_HOME, 'utf8')); + } catch { + return false; + } +})(); +if (!inRootNav && !onDocsHome) { + problems.push( + `the Releases section is unreachable — "releases" is not in ${ROOT_META_PATH} ` + + `"pages" AND ${DOCS_HOME} carries no /docs/releases link. Every release page ` + + `is then an orphan that only a direct URL can reach. Restore one of the two.`, + ); +} for (const major of releasedMajors()) { if (major < FLOOR_MAJOR || KNOWN_MISSING.has(major)) continue; const slug = `v${major}`; diff --git a/skills/README.md b/skills/README.md index 4c910b4abb..ed90a41e5e 100644 --- a/skills/README.md +++ b/skills/README.md @@ -35,7 +35,7 @@ apps too). | [UI](./objectstack-ui/SKILL.md) | `ui` | Author ObjectStack UI metadata — Views (list/form/kanban/calendar/gantt), Apps (navigation), Pages (structured plus the HTML and React source-authoring tiers, ADR-0080/0081), Dashboards, Reports, Charts, Actions, and package Docs (`src/docs/*.md`). | | [Automation](./objectstack-automation/SKILL.md) | `automation` | Design ObjectStack automation — Flows (visual logic), Triggers, Approvals, state machines, scheduled jobs, and webhooks. | | [AI](./objectstack-ai/SKILL.md) | `ai` | Design ObjectStack AI skills, tools, knowledge sources, conversations, model registry entries, and MCP integrations. | -| [API](./objectstack-api/SKILL.md) | `api` | Design the server-side API surface that an ObjectStack runtime exposes — REST/GraphQL endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | +| [API](./objectstack-api/SKILL.md) | `api` | Design the server-side API surface that an ObjectStack runtime exposes — REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. | | [i18n](./objectstack-i18n/SKILL.md) | `i18n` | Author ObjectStack translation bundles — object/field labels, view text, app navigation strings, automation messages — and configure locale fallback, coverage reporting, and the per-locale source layout. | | [Formula](./objectstack-formula/SKILL.md) | `expression` | Author CEL expressions used across ObjectStack — formula fields, field conditional rules (`visibleWhen`, `readonlyWhen`, `requiredWhen`), validation / sharing / visibility predicates, flow conditions, and dynamic seed values. | diff --git a/skills/objectstack-api/SKILL.md b/skills/objectstack-api/SKILL.md index 45ab2312bb..50a01fe23a 100644 --- a/skills/objectstack-api/SKILL.md +++ b/skills/objectstack-api/SKILL.md @@ -2,10 +2,10 @@ name: objectstack-api description: > Design the server-side API surface that an ObjectStack runtime exposes — - REST/GraphQL endpoints, auth providers, realtime channels, error envelopes, + REST endpoints, auth providers, realtime channels, error envelopes, batch/versioning contracts. Use when the user is adding `*.endpoint.ts`, configuring auth providers, defining custom routes, or extending the - REST/GraphQL generator. Do not use for: consuming an ObjectStack API from + REST generator. Do not use for: consuming an ObjectStack API from a client (that is just standard HTTP — no skill needed); the auto-generated CRUD endpoints (those follow from objectstack-data); request-side query syntax (see objectstack-query). CEL expressions in route guards or auth From 5d9ba822a622431c13b6054097a181afe6253e63 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 01:15:43 +0000 Subject: [PATCH 2/3] fix(spec): regenerate the ADR-0087 manifest + upgrade guide the RC bump left stale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v17 RC version bump moved PROTOCOL_VERSION to 17.0.0 without re-running gen:spec-changes / gen:upgrade-guide, so spec-changes.json still declared protocolVersion 16.0.0 with no protocol-17 entries while the protocol-17 conversions were already registered and live. check:spec-changes (Check Generated Artifacts) fails on pristine origin/main — reproduced in a detached worktree — and surfaced here only because this PR is the first since the bump to touch a path the job's filter watches (skills/**). Mechanical regeneration, no hand edits: spec-changes.json gains the five protocol-17 conversions and protocolVersion 17.0.0; the upgrade guide gains its generated Protocol 16 → 17 section. All four job steps now pass locally (check:skill-docs, check:spec-changes, check:upgrade-guide, check:authorable-surface). Ships as a spec patch so npm consumers get the corrected manifest. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LXvaYR7TiJBCxYwjn51owH --- .changeset/spec-changes-manifest-catchup.md | 19 ++++++ docs/protocol-upgrade-guide.md | 20 +++++- packages/spec/spec-changes.json | 73 ++++++++++++++++++++- 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 .changeset/spec-changes-manifest-catchup.md diff --git a/.changeset/spec-changes-manifest-catchup.md b/.changeset/spec-changes-manifest-catchup.md new file mode 100644 index 0000000000..d74ce6ceec --- /dev/null +++ b/.changeset/spec-changes-manifest-catchup.md @@ -0,0 +1,19 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): regenerate the ADR-0087 change manifest the RC version bump left stale + +The v17 RC bump moved `PROTOCOL_VERSION` to `17.0.0` but did not re-run +`gen:spec-changes` / `gen:upgrade-guide`, so the published `spec-changes.json` +still declared `protocolVersion: 16.0.0` with no protocol-17 entries — while the +protocol-17 conversions (`execute`→`target`, `conditionalRequired`→`requiredWhen`, +`knowledge.topics`→`sources`, `agent.tools` removal, sharing `full`→`edit`) were +already registered and applied at load. Anything projecting the manifest (the +generated upgrade guide, the `spec_changes` MCP tool) reported a 16-era chain on +a 17 protocol. + +`check:spec-changes` caught it, but only on the first PR to touch a +generated-artifacts path after the bump — this regenerates both projections and +turns the gate green again. `docs/protocol-upgrade-guide.md` now carries the +Protocol 16 → 17 section. diff --git a/docs/protocol-upgrade-guide.md b/docs/protocol-upgrade-guide.md index eda395db9d..5882299793 100644 --- a/docs/protocol-upgrade-guide.md +++ b/docs/protocol-upgrade-guide.md @@ -3,7 +3,7 @@ # Metadata protocol upgrade guide -Current protocol: **16.0.0** · chain support floor: **protocol 10** · generated from the ADR-0087 registries (`@objectstack/spec` `conversions/` + `migrations/`). +Current protocol: **17.0.0** · chain support floor: **protocol 10** · generated from the ADR-0087 registries (`@objectstack/spec` `conversions/` + `migrations/`). ## How to upgrade — from any past major @@ -114,6 +114,24 @@ Protocol 16 flipped `DashboardWidgetSchema` to `.strict()` (framework#3251, ADR- - Why not automatic: The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key. - Done when: `objectstack validate` passes with no unknown-key parse errors on dashboard widgets. +## Protocol 16 → 17 + +Protocol 17 removes the last three deprecated authorable aliases: action `execute` (use `target`), field `conditionalRequired` (use `requiredWhen`), and agent `knowledge.topics` (use `knowledge.sources`). Each was already lowered into its canonical key at parse time and dropped from the parsed output, so no runtime behaviour changes — only the authorable surface shrinks to one spelling per slot. All three are pure key renames with unchanged values and replay losslessly; the schemas reject the removed spellings with a fix-it error naming the replacement. + +It also removes the sharing-rule access level `full` (#3865): declared as "Full Access (Transfer, Share, Delete)" but never enforced as anything but `edit` — both gates matched `edit`/`full` alike, so Setup promised admins a delete grant it never issued (ADR-0078). Unlike the OWD `sharingModel: 'full'` alias retired at step 13, this one HAS a lossless target precisely because it was inert — old and new shapes are behaviourally identical — so it converts mechanically and leaves no semantic residue. It is the one protocol-17 conversion that keeps a load-path acceptance window: it had no prior deprecation, and a removed enum value cannot carry the fix-it error the three key renames tombstone theirs with. + +Finally it removes agent `tools` (#3894): the legacy inline `{type,name,description}[]` fallback, which the runtime resolved against the FULL tool registry with no surface check — the one seam that broke ADR-0064's "an agent reaches exactly its surface-compatible skills' tools, nothing falls through to the global registry". Unlike the renames above this has NO lossless target: each entry has to become a reference inside a skill, which is a human decision about which skill. The conversion therefore drops the dead key (the runtime stopped reading it in cloud#910, so it already contributes nothing) and emits a notice per agent so the author knows where capability must be re-declared; the schema tombstones the key with a fix-it error naming `skills`. + +### Mechanical (applied for you) + +| Conversion | Surface | Change | Load window | +|---|---|---|---| +| `action-execute-to-target` | `action.execute` | action key 'execute' → 'target' (the deprecated handler alias, #3713) | retired — `migrate meta` only | +| `field-conditionalRequired-to-requiredWhen` | `field.conditionalRequired` | field key 'conditionalRequired' → 'requiredWhen' (the deprecated predicate alias, #3754) | retired — `migrate meta` only | +| `agent-knowledge-topics-to-sources` | `agent.knowledge.topics` | agent knowledge key 'topics' → 'sources' (the deprecated RAG-source alias, #1891) | retired — `migrate meta` only | +| `agent-tools-to-skills` | `agent.tools` | agent key 'tools' removed — declare capability in a skill (ADR-0064, #3894) | retired — `migrate meta` only | +| `sharing-rule-access-level-full-to-edit` | `sharingRule.accessLevel` | sharing-rule accessLevel 'full' → 'edit' (#3865 — `full` never granted more than `edit`) | live — protocol 17 loader accepts the old shape | + --- *Machine-readable equivalents: `spec-changes.json` (shipped in `@objectstack/spec` and attached to each GitHub Release) and the structured output of `objectstack migrate meta --json`.* diff --git a/packages/spec/spec-changes.json b/packages/spec/spec-changes.json index 75838db21a..9b02c11174 100644 --- a/packages/spec/spec-changes.json +++ b/packages/spec/spec-changes.json @@ -1,11 +1,11 @@ { "$comment": "GENERATED (ADR-0087 D4) — do not edit. Regenerate with: pnpm --filter @objectstack/spec gen:spec-changes. A projection of the D2 conversion table + D3 migration chain; the upgrade guide and the MCP spec_changes tool derive from this same data.", - "protocolVersion": "16.0.0", + "protocolVersion": "17.0.0", "supportFloor": 10, "migrateCommand": "objectstack migrate meta --from (N >= 10)", "aggregate": { "from": 10, - "to": 16, + "to": 17, "added": [], "converted": [ { @@ -67,6 +67,36 @@ "to": "page component key 'visibility' → 'visibleWhen' (ADR-0089)", "conversionId": "page-component-visibility-to-visibleWhen", "toMajor": 15 + }, + { + "surface": "action.execute", + "to": "action key 'execute' → 'target' (the deprecated handler alias, #3713)", + "conversionId": "action-execute-to-target", + "toMajor": 17 + }, + { + "surface": "field.conditionalRequired", + "to": "field key 'conditionalRequired' → 'requiredWhen' (the deprecated predicate alias, #3754)", + "conversionId": "field-conditionalRequired-to-requiredWhen", + "toMajor": 17 + }, + { + "surface": "agent.knowledge.topics", + "to": "agent knowledge key 'topics' → 'sources' (the deprecated RAG-source alias, #1891)", + "conversionId": "agent-knowledge-topics-to-sources", + "toMajor": 17 + }, + { + "surface": "agent.tools", + "to": "agent key 'tools' removed — declare capability in a skill (ADR-0064, #3894)", + "conversionId": "agent-tools-to-skills", + "toMajor": 17 + }, + { + "surface": "sharingRule.accessLevel", + "to": "sharing-rule accessLevel 'full' → 'edit' (#3865 — `full` never granted more than `edit`)", + "conversionId": "sharing-rule-access-level-full-to-edit", + "toMajor": 17 } ], "migrated": [ @@ -330,6 +360,45 @@ } ], "removed": [] + }, + { + "from": 16, + "to": 17, + "added": [], + "converted": [ + { + "surface": "action.execute", + "to": "action key 'execute' → 'target' (the deprecated handler alias, #3713)", + "conversionId": "action-execute-to-target", + "toMajor": 17 + }, + { + "surface": "field.conditionalRequired", + "to": "field key 'conditionalRequired' → 'requiredWhen' (the deprecated predicate alias, #3754)", + "conversionId": "field-conditionalRequired-to-requiredWhen", + "toMajor": 17 + }, + { + "surface": "agent.knowledge.topics", + "to": "agent knowledge key 'topics' → 'sources' (the deprecated RAG-source alias, #1891)", + "conversionId": "agent-knowledge-topics-to-sources", + "toMajor": 17 + }, + { + "surface": "agent.tools", + "to": "agent key 'tools' removed — declare capability in a skill (ADR-0064, #3894)", + "conversionId": "agent-tools-to-skills", + "toMajor": 17 + }, + { + "surface": "sharingRule.accessLevel", + "to": "sharing-rule accessLevel 'full' → 'edit' (#3865 — `full` never granted more than `edit`)", + "conversionId": "sharing-rule-access-level-full-to-edit", + "toMajor": 17 + } + ], + "migrated": [], + "removed": [] } ] } From d2425d939ae9858c8650684342a6a0ef12cd5bc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 01:17:12 +0000 Subject: [PATCH 3/3] docs(sweep): point run 2+ at the docs-accuracy-audit deep pass The docs-drift-check bot on #3908 lists 105 spec-referencing docs and the docs-accuracy-audit workflow that re-verifies them. The fingerprint grep in this playbook targets removals; record the audit workflow (scoped via scripts/docs-audit/affected-docs.mjs) as the complementary deep pass to run once before changeset pre exit. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01LXvaYR7TiJBCxYwjn51owH --- docs/v17-docs-sweep.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/v17-docs-sweep.md b/docs/v17-docs-sweep.md index 31b6711e59..fd497df566 100644 --- a/docs/v17-docs-sweep.md +++ b/docs/v17-docs-sweep.md @@ -30,6 +30,13 @@ regenerate; never edit the mirrors. fixed, false positives (so the next run can skip re-judging them), and out-of-scope findings (file an issue per Prime Directive #10). +Complementary deep pass: the fingerprint grep above is cheap and targeted at +*removals*; for a full implementation-accuracy re-verification of prose that +merely *references* changed code, use the `docs-accuracy-audit` workflow the +`docs-drift-check` PR bot advertises — scope it with +`node scripts/docs-audit/affected-docs.mjs ` and pass the list as +`args.docs`. Worth one scoped run before `changeset pre exit`. + ## Fingerprints (v17 removals/renames → what a hit means) | Pattern | Drift if the doc… | Source changeset |