diff --git a/.changeset/v17-docs-sweep-run-4.md b/.changeset/v17-docs-sweep-run-4.md new file mode 100644 index 0000000000..d2ae98d79b --- /dev/null +++ b/.changeset/v17-docs-sweep-run-4.md @@ -0,0 +1,8 @@ +--- +--- + +Docs-only: v17 docs sweep run 4 (rc.1 catch-up; closes the missing-run-2 +coverage gap over `a641d10..0f9faa2`). Fixes the retired `api.requireAuth` +opt-out still being taught in permissions/forms docs (#3963) and the +half-updated kernel services checklist (16 → 15 services after the `workflow` +slot retirement, #4451). Releases nothing. diff --git a/content/docs/kernel/services-checklist.mdx b/content/docs/kernel/services-checklist.mdx index 763f88e664..9a8e906815 100644 --- a/content/docs/kernel/services-checklist.mdx +++ b/content/docs/kernel/services-checklist.mdx @@ -14,7 +14,7 @@ the code wins. See [Plugins & Packages](/docs/plugins/packages) for the full package catalog. -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. +The ObjectStack protocol defines **15 kernel services** registered via the `CoreServiceName` enum (v17 removed the never-implemented `graphql` entry and retired the never-filled `workflow` slot, #4451). 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 guarantees only **data** and **metadata**, and even those are filled by packages (`@objectstack/objectql`, `@objectstack/metadata`) rather than baked in — the kernel's own contribution is an in-memory fallback for the `core` slots that have one (`metadata`, `cache`, `queue`, `job`, `i18n` — **not** `auth`). Everything else — including **auth** and **automation** — is delivered by plugins. `@objectstack/objectql` is an example kernel implementation to get the basic API running; production kernels will be rebuilt as separate plugins. @@ -50,7 +50,7 @@ The ObjectStack protocol defines **16 kernel services** registered via the `Core ├─────────────────────────────────────────────────────────┤ │ Plugin Layer │ │ All other services: analytics, auth, automation, │ -│ workflow, ui, realtime, notification, ai, i18n, │ +│ ui, realtime, notification, ai, i18n, │ │ search, file-storage, cache, queue, job │ │ │ │ Discovery API reports availability per service │ @@ -70,17 +70,16 @@ The ObjectStack protocol defines **16 kernel services** registered via the `Core | 3 | **analytics** | `optional` | 2 | ❌ Plugin Required | `@objectstack/service-analytics` | | 4 | **auth** | `core` | — | ✅ Implemented | `@objectstack/plugin-auth` | | 5 | **ui** | `optional` | 5 | ❌ Nothing fills this slot | `@objectstack/metadata-protocol` — `/ui/view` is served by its `protocol` service, not by a `ui` service | -| 6 | **workflow** | `optional` | 3 | ❌ Nothing ships | — | -| 7 | **automation** | `optional` | 1 | ❌ Plugin Required | `@objectstack/service-automation` | -| 8 | **realtime** | `optional` | 6 | ❌ Plugin Required (in-process only — no HTTP/WS route is mounted) | `@objectstack/service-realtime` | -| 9 | **notification** | `optional` | 7 | ❌ Plugin Required | `@objectstack/service-messaging` | -| 10 | **ai** | `optional` | — | ❌ Nothing ships in this repo | `@objectstack/service-ai` (Cloud/EE — not installable, so the table entry is `null`) | -| 11 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` | -| 12 | **file-storage** | `optional` | — | ❌ Plugin Required | `@objectstack/service-storage` | -| 13 | **search** | `optional` | — | ❌ Nothing ships | — | -| 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` | +| 6 | **automation** | `optional` | 1 | ❌ Plugin Required | `@objectstack/service-automation` | +| 7 | **realtime** | `optional` | 6 | ❌ Plugin Required (in-process only — no HTTP/WS route is mounted) | `@objectstack/service-realtime` | +| 8 | **notification** | `optional` | 7 | ❌ Plugin Required | `@objectstack/service-messaging` | +| 9 | **ai** | `optional` | — | ❌ Nothing ships in this repo | `@objectstack/service-ai` (Cloud/EE — not installable, so the table entry is `null`) | +| 10 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` | +| 11 | **file-storage** | `optional` | — | ❌ Plugin Required | `@objectstack/service-storage` | +| 12 | **search** | `optional` | — | ❌ Nothing ships | — | +| 13 | **cache** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` | +| 14 | **queue** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` | +| 15 | **job** | `core` | — | ✅ Built-in (in-memory fallback) | `@objectstack/service-job` | The Provider column mirrors `CORE_SERVICE_PROVIDER` in @@ -319,7 +318,7 @@ registered under their own names (`security.permissions`, `security.rls`, --- -## 5–7. Business Services +## 5–6. Business Services ### 5. ui Service — 5 declared methods, none routed ❌ `listViews`, `getView`, `createView`, `updateView`, `deleteView` @@ -337,7 +336,7 @@ the domain answers **501** with that remedy spelled out, not a generic "install plugin". -### 6. workflow Service — retired in v17 +### Retired in v17: the `workflow` slot The slot, its `IWorkflowService` contract and the three `WorkflowProtocol` methods (`getWorkflowConfig`, `getWorkflowState`, `workflowTransition`) were removed in [#4451](https://github.com/objectstack-ai/objectstack/issues/4451): @@ -350,7 +349,7 @@ flow nodes on the approvals runtime (ADR-0019 — decisions via `@objectstack/plugin-approvals`), and record-triggered automation is lifecycle hooks + `record_change` flows. -### 7. automation Service — 1 method ✅ `@objectstack/service-automation` +### 6. automation Service — 1 method ✅ `@objectstack/service-automation` `triggerAutomation` Trigger engine, event triggers from ObjectQL hooks, flow executor, scheduled triggers. The `/automation` dispatcher domain gates on `isServiceServeable`, so a slot filled by @@ -358,9 +357,9 @@ a self-declared stub answers as an empty one. --- -## 8–11. Communication Services +## 7–10. Communication Services -### 8. realtime — 6 methods · `@objectstack/service-realtime` +### 7. realtime — 6 methods · `@objectstack/service-realtime` `realtimeConnect`, `realtimeDisconnect`, `realtimeSubscribe`, `realtimeUnsubscribe`, `setPresence`, `getPresence` @@ -374,7 +373,7 @@ in-process only and no HTTP/WS surface is mounted. Re-advertising waits on a rea transport. -### 9. notification — 7 methods · `@objectstack/service-messaging` +### 8. notification — 7 methods · `@objectstack/service-messaging` `registerDevice`, `unregisterDevice`, `getNotificationPreferences`, `updateNotificationPreferences`, `listNotifications`, `markNotificationsRead`, `markAllNotificationsRead` The slot name is `notification` (singular) and the package that fills it shares no word @@ -386,7 +385,7 @@ on `INotificationService` — a send-only provider (SMTP, Twilio, a webhook) fil slot legitimately without an inbox, and each route probes its own method and answers 501 when absent. -### 10. ai — contract removed ❌ +### 9. ai — contract removed ❌ ~~`aiNlq`, `aiSuggest`, `aiInsights`~~ @@ -402,7 +401,7 @@ The `ai` slot still exists in `CoreServiceName`, but nothing in this repo fills it (`CORE_SERVICE_PROVIDER.ai` is `null`). -### 11. i18n — 3 methods +### 10. i18n — 3 methods `getLocales`, `getTranslations`, `getFieldLabels` **Service Name**: `i18n` · **Criticality**: `core` @@ -467,7 +466,7 @@ AppPlugin will: --- -## 12–16. Infrastructure Services +## 11–15. 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`) stay disabled until a plugin provides them. diff --git a/content/docs/permissions/authorization.mdx b/content/docs/permissions/authorization.mdx index 917b2ee9e0..abb452377f 100644 --- a/content/docs/permissions/authorization.mdx +++ b/content/docs/permissions/authorization.mdx @@ -48,7 +48,7 @@ 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` and the metadata endpoints (`/meta`) — the raw-hono standard `/data` routes left the matrix when that duplicate surface was deleted in v17 (#4073), and the dispatcher GraphQL endpoint left when the GraphQL surface was removed (`/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): serving the whole data plane publicly requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Narrower public surfaces do **not** need it — each derives its own authorization from a declaration rather than from the deployment posture: control plane (`/auth`, `/health`, `/discovery`) is allow-listed; public form submission carries a `publicFormGrant` (ADR-0056 Option A); share-links validate their token then read as SYSTEM; and an anonymous **GET** of the book/doc read surface is admitted so `book.audience: 'public'` works under the secure default, with the ADR-0046 §6.7 audience gate — `'public'` only, fail-closed — doing the authorizing (#3963). | `packages/core/src/security/anonymous-deny.ts` `shouldDenyAnonymous` — called by `rest-server.ts` `enforceAuth` and the dispatcher `handleMetadata`/`handleAI` (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` and the metadata endpoints (`/meta`) — the raw-hono standard `/data` routes left the matrix when that duplicate surface was deleted in v17 (#4073), and the dispatcher GraphQL endpoint left when the GraphQL surface was removed (`/graphql` now 404s) — one shared decision, so a caller denied on `/data` can't read the same rows through a sibling door. **Unconditional** (#3963, closing out ADR-0056 D2): the `api.requireAuth: false` opt-out was retired in v17 — the key is tombstoned, so authoring it is a parse error, and anonymous callers are denied on every data surface with no deployment-level escape hatch. Narrower public surfaces do **not** need it — each derives its own authorization from a declaration rather than from the deployment posture: control plane (`/auth`, `/health`, `/discovery`) is allow-listed; public form submission carries a `publicFormGrant` (ADR-0056 Option A); share-links validate their token then read as SYSTEM; and an anonymous **GET** of the book/doc read surface is admitted so `book.audience: 'public'` works under the secure default, with the ADR-0046 §6.7 audience gate — `'public'` only, fail-closed — doing the authorizing (#3963). | `packages/core/src/security/anonymous-deny.ts` `shouldDenyAnonymous` — called by `rest-server.ts` `enforceAuth` and the dispatcher `handleMetadata`/`handleAI` (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 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 | diff --git a/content/docs/permissions/explain.mdx b/content/docs/permissions/explain.mdx index b240fcdd7b..22ce906a27 100644 --- a/content/docs/permissions/explain.mdx +++ b/content/docs/permissions/explain.mdx @@ -86,8 +86,9 @@ through. ## Who may ask -The endpoint is **authenticated-only** — even on `requireAuth: false` -deployments (an access report is sensitive even about oneself). Beyond that, +The endpoint is **authenticated-only** (an access report is sensitive even +about oneself; since v17 anonymous access to object data is always denied +anyway — the `requireAuth: false` opt-out was retired, #3963). Beyond that, authorization lives in the *service*, so REST and in-process callers share one rule: diff --git a/content/docs/ui/forms.mdx b/content/docs/ui/forms.mdx index ce26f72ac4..a5da15de35 100644 --- a/content/docs/ui/forms.mdx +++ b/content/docs/ui/forms.mdx @@ -44,7 +44,8 @@ Only the spec'd whitelist of form fields is accepted; everything else (status, o > from the form's own declaration — `{ object: }` — > and the SecurityPlugin authorizes **only** create + the immediate read-back on > exactly that object, never anything else and never the anonymous fall-open. So -> public forms work under secure-by-default (`requireAuth: true`) with **no** +> public forms work under the unconditional anonymous-deny posture (v17 +> retired the `requireAuth: false` opt-out, #3963) with **no** > `guest_portal` permission set. The `guest_portal` permission set + `anonymous` flag > are still attached for **back-compat** (object hooks that detect a guest via a > falsy `ctx.user?.id`), but they are no longer the authorization mechanism. @@ -231,7 +232,7 @@ The companion `GET /api/v1/forms/:slug` route returns `500 FORM_RESOLVE_FAILED` ### Auth model -- Neither route calls `enforceAuth`, so they work even when the project is configured with `requireAuth: true`. +- Neither route calls `enforceAuth`, so they work under the always-on anonymous-deny default (there is no `requireAuth` knob to configure since v17). - The execution context handed to ObjectQL is `{ publicFormGrant: { object }, permissions: ['guest_portal'], anonymous: true }` with no `userId`. The Security plugin honors `publicFormGrant` first — a create + read-back grant scoped to exactly the declared object — so authorization holds even without a `guest_portal` profile. `permissions: ['guest_portal']` is retained for back-compat. - No CSRF or auth header is needed; embed the form on any domain. diff --git a/docs/v17-docs-sweep.md b/docs/v17-docs-sweep.md index af81f6636d..63495816bc 100644 --- a/docs/v17-docs-sweep.md +++ b/docs/v17-docs-sweep.md @@ -132,4 +132,62 @@ merely *references* changed code, use the `docs-accuracy-audit` workflow the found seventeen. Grep the pattern repo-wide (including `skills/`), do not work a fingerprint list file-by-file. +### 2026-08-02 — run 4 (rc.1 catch-up; closes the run-2 gap) + +- **Watermark:** framework `ff17642` (origin/main, post-#4590) · spec + `17.0.0-rc.1` · `PROTOCOL_VERSION = '17.0.0'`. +- **Coverage note:** the log holds run 1 (`a641d10`) and run 3 (version-number + pass only) — **no run 2 exists**, so the 362 changesets added + `a641d10..0f9faa2` had never been swept with removal fingerprints. This run's + delta is therefore `a641d10..HEAD` (435 new changesets, ~48 breaking), not + just the run-3 watermark. +- **Fingerprints added:** `api.requireAuth` (retired outright, #3963 — a doc + presenting the opt-out as available is drift); `workflow` kernel service slot + (#4451); standalone `validation` metadata kind (#4509, ADR-0088); `job` + runtime create/org override (#4509); flow `script` `config.actionType` / + `.template` / `.recipients` / `.variables` / inline `script` (#4343); + `query.cursor` / `query.distinct` / `joins` / `windowFunctions` / + nested-select (#4286, #4196); `EnhancedApiError.fieldErrors` (ADR-0114 D4); + `BatchOptions.validateOnly` (#4052); `registerStandardEndpoints` (#4073); + `readReplicas` (#4481); connector template / trigger-registry Connector + clusters (#4500, #4503); dual-source names (`WebhookConfig` from `./api`, + `CacheStrategyEnum`, `MetadataFormat` `'yml'/'ts'/'js'` aliases, + contracts `ShareRecipientType` → `RecordShareRecipientType`; #4537–#4539, + #4572). +- **Fixed (drift → corrected):** + - `permissions/authorization.mdx` — gate 1 still said serving the data plane + publicly "requires an explicit `api.requireAuth: false` opt-out"; the + opt-out is retired and tombstoned (#3963). + - `permissions/explain.mdx` — "even on `requireAuth: false` deployments", a + posture that no longer exists. + - `ui/forms.mdx` — two mentions of configuring `requireAuth`; rewritten + against the unconditional anonymous-deny posture. + - `kernel/services-checklist.mdx` — partially updated at change time: §6 and + the provider callout documented the `workflow` retirement, but the page + still claimed **16** services, listed `workflow` in the architecture + diagram, and carried its live row in the summary table. Now 15 services, + row removed, tables and section numbering re-aligned (enum verified: + 15 members). +- **Judged, not drift (skip re-checking):** `actionType` in + `deployment/validating-metadata.mdx` / `skills/objectstack-ui` (dashboard + action buttons — same key name, different surface); flows.mdx / automation + skill (document the #4343 retirement); `bulkActionDefs` in ui docs (teach + the new typed shape, #4457); `having` (documents "enforced since #4286"); + `fieldErrors` in api docs (documents the tombstone); `windowFunctions` / + `query.distinct` hits (all "removed in spec 17" notices, run 3 fixed their + version numbers); `maxRetries` in flows.mdx (explicit count — the required + posture, #4247). +- **Zero-hit fingerprints (nothing to fix):** `validateOnly`, + `registerStandardEndpoints`, `readReplicas`, `CacheStrategyEnum`, + `WebhookConfig`, `MetadataFormat` aliases, `ShareRecipientType`, standalone + `.validation.ts`, job `allowRuntimeCreate`, connector template cluster, + `storage.notNull` misclaims, plugin lifecycle-hook family. +- **Method note:** docs-at-change-time discipline improved markedly in this + window — most removals landed with their retirement callouts already written + (flows.mdx #4343, query-syntax #4286, error-catalog ADR-0114). The drift + that survives is the *second-order* kind: a page partially updated (checklist + count vs narrative) or a cross-reference in a page the change didn't touch + (requireAuth in explain/forms). Grep the fingerprint repo-wide even when the + primary page looks done. +