Skip to content

Commit 45a5787

Browse files
os-zhuangclaude
andauthored
docs(sweep): run 4 — rc.1 catch-up, close the run-2 coverage gap (#4600)
* docs(sweep): run 4 — rc.1 catch-up, and close the run-2 coverage gap The sweep log holds run 1 (watermark a641d10) and run 3 (a version-number-only pass) — no run 2 was ever recorded, so the 362 changesets added a641d10..0f9faa2 were never swept with removal fingerprints. This run's delta is a641d10..HEAD (435 new changesets, ~48 breaking), against a tree now at 17.0.0-rc.1. Fixed drift: - permissions/authorization.mdx — gate 1 still taught the api.requireAuth:false opt-out; #3963 retired it outright (tombstoned, always-deny). - permissions/explain.mdx, ui/forms.mdx — three more passages describing the requireAuth knob as configurable. - kernel/services-checklist.mdx — partially updated at change time: the narrative documented the workflow-slot retirement (#4451) while the page still claimed 16 services, listed workflow in the architecture diagram, and carried its live summary-table row. Now 15 (matches the 15-member enum), row dropped, table and section numbering re-aligned. Everything else the new fingerprints matched was already correct at change time — flows.mdx (#4343 script convergence), query-syntax (#4286 removals, having enforcement), error-catalog (ADR-0114 fieldErrors tombstone), bulkActionDefs (#4457) — and fourteen fingerprints hit nothing at all. Run 4 appended to docs/v17-docs-sweep.md with the coverage-gap note and the second-order-drift method lesson. Verified: pnpm docs:build passes; check:release-notes, doc-authoring, role-word, org-identifier, nul-bytes green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXvaYR7TiJBCxYwjn51owH * chore: empty changeset for the docs-only sweep PR The Check Changeset gate counts changesets added by the PR itself; an empty-frontmatter changeset is the sanctioned releases-nothing declaration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXvaYR7TiJBCxYwjn51owH --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9b43ee2 commit 45a5787

6 files changed

Lines changed: 94 additions & 27 deletions

File tree

.changeset/v17-docs-sweep-run-4.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
---
3+
4+
Docs-only: v17 docs sweep run 4 (rc.1 catch-up; closes the missing-run-2
5+
coverage gap over `a641d10..0f9faa2`). Fixes the retired `api.requireAuth`
6+
opt-out still being taught in permissions/forms docs (#3963) and the
7+
half-updated kernel services checklist (16 → 15 services after the `workflow`
8+
slot retirement, #4451). Releases nothing.

content/docs/kernel/services-checklist.mdx

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ the code wins. See [Plugins & Packages](/docs/plugins/packages) for the full
1414
package catalog.
1515
</Callout>
1616

17-
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.
17+
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.
1818

1919
**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.
2020

@@ -50,7 +50,7 @@ The ObjectStack protocol defines **16 kernel services** registered via the `Core
5050
├─────────────────────────────────────────────────────────┤
5151
│ Plugin Layer │
5252
│ All other services: analytics, auth, automation, │
53-
workflow, ui, realtime, notification, ai, i18n, │
53+
│ ui, realtime, notification, ai, i18n,
5454
│ search, file-storage, cache, queue, job │
5555
│ │
5656
│ Discovery API reports availability per service │
@@ -70,17 +70,16 @@ The ObjectStack protocol defines **16 kernel services** registered via the `Core
7070
| 3 | **analytics** | `optional` | 2 | ❌ Plugin Required | `@objectstack/service-analytics` |
7171
| 4 | **auth** | `core` || ✅ Implemented | `@objectstack/plugin-auth` |
7272
| 5 | **ui** | `optional` | 5 | ❌ Nothing fills this slot | `@objectstack/metadata-protocol``/ui/view` is served by its `protocol` service, not by a `ui` service |
73-
| 6 | **workflow** | `optional` | 3 | ❌ Nothing ships ||
74-
| 7 | **automation** | `optional` | 1 | ❌ Plugin Required | `@objectstack/service-automation` |
75-
| 8 | **realtime** | `optional` | 6 | ❌ Plugin Required (in-process only — no HTTP/WS route is mounted) | `@objectstack/service-realtime` |
76-
| 9 | **notification** | `optional` | 7 | ❌ Plugin Required | `@objectstack/service-messaging` |
77-
| 10 | **ai** | `optional` || ❌ Nothing ships in this repo | `@objectstack/service-ai` (Cloud/EE — not installable, so the table entry is `null`) |
78-
| 11 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` |
79-
| 12 | **file-storage** | `optional` || ❌ Plugin Required | `@objectstack/service-storage` |
80-
| 13 | **search** | `optional` || ❌ Nothing ships ||
81-
| 14 | **cache** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` |
82-
| 15 | **queue** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` |
83-
| 16 | **job** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-job` |
73+
| 6 | **automation** | `optional` | 1 | ❌ Plugin Required | `@objectstack/service-automation` |
74+
| 7 | **realtime** | `optional` | 6 | ❌ Plugin Required (in-process only — no HTTP/WS route is mounted) | `@objectstack/service-realtime` |
75+
| 8 | **notification** | `optional` | 7 | ❌ Plugin Required | `@objectstack/service-messaging` |
76+
| 9 | **ai** | `optional` || ❌ Nothing ships in this repo | `@objectstack/service-ai` (Cloud/EE — not installable, so the table entry is `null`) |
77+
| 10 | **i18n** | `core` | 3 | ✅ Built-in (in-memory fallback) | `@objectstack/service-i18n` |
78+
| 11 | **file-storage** | `optional` || ❌ Plugin Required | `@objectstack/service-storage` |
79+
| 12 | **search** | `optional` || ❌ Nothing ships ||
80+
| 13 | **cache** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-cache` |
81+
| 14 | **queue** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-queue` |
82+
| 15 | **job** | `core` || ✅ Built-in (in-memory fallback) | `@objectstack/service-job` |
8483

8584
<Callout type="info">
8685
The Provider column mirrors `CORE_SERVICE_PROVIDER` in
@@ -319,7 +318,7 @@ registered under their own names (`security.permissions`, `security.rls`,
319318

320319
---
321320

322-
## 5–7. Business Services
321+
## 5–6. Business Services
323322

324323
### 5. ui Service — 5 declared methods, none routed ❌
325324
`listViews`, `getView`, `createView`, `updateView`, `deleteView`
@@ -337,7 +336,7 @@ the domain answers **501** with that remedy spelled out, not a generic "install
337336
plugin".
338337
</Callout>
339338

340-
### 6. workflow Service — retired in v17
339+
### Retired in v17: the `workflow` slot
341340
The slot, its `IWorkflowService` contract and the three `WorkflowProtocol`
342341
methods (`getWorkflowConfig`, `getWorkflowState`, `workflowTransition`) were
343342
removed in [#4451](https://github.com/objectstack-ai/objectstack/issues/4451):
@@ -350,17 +349,17 @@ flow nodes on the approvals runtime (ADR-0019 — decisions via
350349
`@objectstack/plugin-approvals`), and record-triggered automation is lifecycle
351350
hooks + `record_change` flows.
352351

353-
### 7. automation Service — 1 method ✅ `@objectstack/service-automation`
352+
### 6. automation Service — 1 method ✅ `@objectstack/service-automation`
354353
`triggerAutomation`
355354
Trigger engine, event triggers from ObjectQL hooks, flow executor, scheduled triggers.
356355
The `/automation` dispatcher domain gates on `isServiceServeable`, so a slot filled by
357356
a self-declared stub answers as an empty one.
358357

359358
---
360359

361-
## 8–11. Communication Services
360+
## 7–10. Communication Services
362361

363-
### 8. realtime — 6 methods · `@objectstack/service-realtime`
362+
### 7. realtime — 6 methods · `@objectstack/service-realtime`
364363
`realtimeConnect`, `realtimeDisconnect`, `realtimeSubscribe`, `realtimeUnsubscribe`, `setPresence`, `getPresence`
365364

366365
<Callout type="warn">
@@ -374,7 +373,7 @@ in-process only and no HTTP/WS surface is mounted. Re-advertising waits on a rea
374373
transport.
375374
</Callout>
376375

377-
### 9. notification — 7 methods · `@objectstack/service-messaging`
376+
### 8. notification — 7 methods · `@objectstack/service-messaging`
378377
`registerDevice`, `unregisterDevice`, `getNotificationPreferences`, `updateNotificationPreferences`, `listNotifications`, `markNotificationsRead`, `markAllNotificationsRead`
379378

380379
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
386385
slot legitimately without an inbox, and each route probes its own method and answers
387386
501 when absent.
388387

389-
### 10. ai — contract removed ❌
388+
### 9. ai — contract removed ❌
390389
~~`aiNlq`, `aiSuggest`, `aiInsights`~~
391390

392391
<Callout type="warn">
@@ -402,7 +401,7 @@ The `ai` slot still exists in `CoreServiceName`, but nothing in this repo fills
402401
it (`CORE_SERVICE_PROVIDER.ai` is `null`).
403402
</Callout>
404403

405-
### 11. i18n — 3 methods
404+
### 10. i18n — 3 methods
406405
`getLocales`, `getTranslations`, `getFieldLabels`
407406

408407
**Service Name**: `i18n` · **Criticality**: `core`
@@ -467,7 +466,7 @@ AppPlugin will:
467466

468467
---
469468

470-
## 12–16. Infrastructure Services
469+
## 11–15. Infrastructure Services
471470

472471
`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.
473472

content/docs/permissions/authorization.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ site — the file you read when behavior surprises you.
4848

4949
| # | Gate | What it decides | Enforcement site | Failure direction |
5050
|---|---|---|---|---|
51-
| 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 |
51+
| 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 |
5252
| 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 |
5353
| 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 |
5454
| 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 |

content/docs/permissions/explain.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ through.
8686

8787
## Who may ask
8888

89-
The endpoint is **authenticated-only** — even on `requireAuth: false`
90-
deployments (an access report is sensitive even about oneself). Beyond that,
89+
The endpoint is **authenticated-only** (an access report is sensitive even
90+
about oneself; since v17 anonymous access to object data is always denied
91+
anyway — the `requireAuth: false` opt-out was retired, #3963). Beyond that,
9192
authorization lives in the *service*, so REST and in-process callers share one
9293
rule:
9394

content/docs/ui/forms.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ Only the spec'd whitelist of form fields is accepted; everything else (status, o
4444
> from the form's own declaration — `{ object: <the form's target object> }`
4545
> and the SecurityPlugin authorizes **only** create + the immediate read-back on
4646
> exactly that object, never anything else and never the anonymous fall-open. So
47-
> public forms work under secure-by-default (`requireAuth: true`) with **no**
47+
> public forms work under the unconditional anonymous-deny posture (v17
48+
> retired the `requireAuth: false` opt-out, #3963) with **no**
4849
> `guest_portal` permission set. The `guest_portal` permission set + `anonymous` flag
4950
> are still attached for **back-compat** (object hooks that detect a guest via a
5051
> 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`
231232

232233
### Auth model
233234

234-
- Neither route calls `enforceAuth`, so they work even when the project is configured with `requireAuth: true`.
235+
- Neither route calls `enforceAuth`, so they work under the always-on anonymous-deny default (there is no `requireAuth` knob to configure since v17).
235236
- 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.
236237
- No CSRF or auth header is needed; embed the form on any domain.
237238

docs/v17-docs-sweep.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,62 @@ merely *references* changed code, use the `docs-accuracy-audit` workflow the
132132
found seventeen. Grep the pattern repo-wide (including `skills/`), do not work a
133133
fingerprint list file-by-file.
134134

135+
### 2026-08-02 — run 4 (rc.1 catch-up; closes the run-2 gap)
136+
137+
- **Watermark:** framework `ff17642` (origin/main, post-#4590) · spec
138+
`17.0.0-rc.1` · `PROTOCOL_VERSION = '17.0.0'`.
139+
- **Coverage note:** the log holds run 1 (`a641d10`) and run 3 (version-number
140+
pass only) — **no run 2 exists**, so the 362 changesets added
141+
`a641d10..0f9faa2` had never been swept with removal fingerprints. This run's
142+
delta is therefore `a641d10..HEAD` (435 new changesets, ~48 breaking), not
143+
just the run-3 watermark.
144+
- **Fingerprints added:** `api.requireAuth` (retired outright, #3963 — a doc
145+
presenting the opt-out as available is drift); `workflow` kernel service slot
146+
(#4451); standalone `validation` metadata kind (#4509, ADR-0088); `job`
147+
runtime create/org override (#4509); flow `script` `config.actionType` /
148+
`.template` / `.recipients` / `.variables` / inline `script` (#4343);
149+
`query.cursor` / `query.distinct` / `joins` / `windowFunctions` /
150+
nested-select (#4286, #4196); `EnhancedApiError.fieldErrors` (ADR-0114 D4);
151+
`BatchOptions.validateOnly` (#4052); `registerStandardEndpoints` (#4073);
152+
`readReplicas` (#4481); connector template / trigger-registry Connector
153+
clusters (#4500, #4503); dual-source names (`WebhookConfig` from `./api`,
154+
`CacheStrategyEnum`, `MetadataFormat` `'yml'/'ts'/'js'` aliases,
155+
contracts `ShareRecipientType``RecordShareRecipientType`; #4537#4539,
156+
#4572).
157+
- **Fixed (drift → corrected):**
158+
- `permissions/authorization.mdx` — gate 1 still said serving the data plane
159+
publicly "requires an explicit `api.requireAuth: false` opt-out"; the
160+
opt-out is retired and tombstoned (#3963).
161+
- `permissions/explain.mdx` — "even on `requireAuth: false` deployments", a
162+
posture that no longer exists.
163+
- `ui/forms.mdx` — two mentions of configuring `requireAuth`; rewritten
164+
against the unconditional anonymous-deny posture.
165+
- `kernel/services-checklist.mdx` — partially updated at change time: §6 and
166+
the provider callout documented the `workflow` retirement, but the page
167+
still claimed **16** services, listed `workflow` in the architecture
168+
diagram, and carried its live row in the summary table. Now 15 services,
169+
row removed, tables and section numbering re-aligned (enum verified:
170+
15 members).
171+
- **Judged, not drift (skip re-checking):** `actionType` in
172+
`deployment/validating-metadata.mdx` / `skills/objectstack-ui` (dashboard
173+
action buttons — same key name, different surface); flows.mdx / automation
174+
skill (document the #4343 retirement); `bulkActionDefs` in ui docs (teach
175+
the new typed shape, #4457); `having` (documents "enforced since #4286");
176+
`fieldErrors` in api docs (documents the tombstone); `windowFunctions` /
177+
`query.distinct` hits (all "removed in spec 17" notices, run 3 fixed their
178+
version numbers); `maxRetries` in flows.mdx (explicit count — the required
179+
posture, #4247).
180+
- **Zero-hit fingerprints (nothing to fix):** `validateOnly`,
181+
`registerStandardEndpoints`, `readReplicas`, `CacheStrategyEnum`,
182+
`WebhookConfig`, `MetadataFormat` aliases, `ShareRecipientType`, standalone
183+
`.validation.ts`, job `allowRuntimeCreate`, connector template cluster,
184+
`storage.notNull` misclaims, plugin lifecycle-hook family.
185+
- **Method note:** docs-at-change-time discipline improved markedly in this
186+
window — most removals landed with their retirement callouts already written
187+
(flows.mdx #4343, query-syntax #4286, error-catalog ADR-0114). The drift
188+
that survives is the *second-order* kind: a page partially updated (checklist
189+
count vs narrative) or a cross-reference in a page the change didn't touch
190+
(requireAuth in explain/forms). Grep the fingerprint repo-wide even when the
191+
primary page looks done.
192+
135193
<!-- Append the next run above this line, newest last. -->

0 commit comments

Comments
 (0)