Skip to content

Commit c22e2c3

Browse files
os-zhuangclaude
andauthored
fix(spec): regenerate drifted reference docs and gate them in CI (#3134)
content/docs/references/** is generated from packages/spec and committed, but no CI job ever regenerated and diffed it, so the public reference docs drifted silently while main stayed green. #3076 added RowCrudActionOverride to the spec and the docs never learned the type existed. Regenerate: 7 files, every change traced to a spec change that shipped without re-running the generator — RowCrudActionOverride and ServiceSelfInfo missing outright, dashboard filterBindings/name missing, readonly (#2948/#3003) and allowTransfer (#3004) stale, and connector ADR-0096 → ADR-0097 (both ADRs exist and are distinct, so the published docs were pointing readers at the wrong one). Verified deterministic: two consecutive runs produce byte-identical output. Gate: build-docs.ts --check, following the sibling convention. Every write goes through emit() and every wiped folder through manageDir(), so check and write run identical generation logic and differ only in the final disposition — it cannot pass on output a real run would not produce. Verified output-identical to the previous generator across all 258 files, and proven to fail on stale content, a missing page, a stale leftover page, and a vacuous no-schema run. Not `git diff --exit-code`: that misses untracked files. Placement: lint.yml's "TypeScript Type Check" — no paths filter and a required status check, so the gate can neither go dormant nor be merged past. ci.yml's "Build Docs" is gated on a `docs` filter excluding packages/spec/**, so it skips exactly the spec-only PRs that cause this drift. Also un-dormants the sibling gates: check:spec-changes / check:upgrade-guide read the ADR-0087 registries but ran under a filter listing only skills/**, and that filter watched content/docs/guides/skills.mdx, a path #2584 moved. Job renamed check-skill-docs → check-generated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a702efc commit c22e2c3

11 files changed

Lines changed: 229 additions & 56 deletions

File tree

.github/workflows/ci.yml

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
outputs:
2525
docs: ${{ steps.changes.outputs.docs }}
2626
core: ${{ steps.changes.outputs.core }}
27-
skilldocs: ${{ steps.changes.outputs.skilldocs }}
27+
generated: ${{ steps.changes.outputs.generated }}
2828
steps:
2929
- name: Checkout repository
3030
uses: actions/checkout@v7
@@ -38,10 +38,30 @@ jobs:
3838
- 'content/**'
3939
- 'pnpm-lock.yaml'
4040
- '.github/workflows/ci.yml'
41-
skilldocs:
41+
# Inputs AND outputs of every generated artifact checked by the
42+
# `check-generated` job. A path missing here makes that gate dormant on
43+
# exactly the PRs that can break it, so keep the two in lockstep.
44+
generated:
45+
# check:skill-docs — catalog source is skills/*/SKILL.md; the derived
46+
# listings are skills/README.md + the reference page below. (That page
47+
# was content/docs/guides/skills.mdx until #2584 moved it; this filter
48+
# kept watching the old path, so hand-edits to the generated block went
49+
# unchecked from then until now.)
4250
- 'skills/**'
43-
- 'content/docs/guides/skills.mdx'
51+
- 'content/docs/ai/skills-reference.mdx'
4452
- 'packages/spec/scripts/build-skill-docs.ts'
53+
# check:spec-changes / check:upgrade-guide — derived from the ADR-0087
54+
# registries and the protocol version, none of which the skills paths
55+
# above cover. Both gates were dormant on spec-only PRs: a protocol bump
56+
# or a new migration could not trigger the job that verifies them.
57+
- 'packages/spec/scripts/build-spec-changes.ts'
58+
- 'packages/spec/scripts/build-upgrade-guide.ts'
59+
- 'packages/spec/src/migrations/**'
60+
- 'packages/spec/src/conversions/**'
61+
- 'packages/spec/src/kernel/protocol-version.ts'
62+
- 'packages/spec/api-surface.json'
63+
- 'packages/spec/spec-changes.json'
64+
- 'docs/protocol-upgrade-guide.md'
4565
- '.github/workflows/ci.yml'
4666
core:
4767
- 'packages/**'
@@ -354,10 +374,18 @@ jobs:
354374
- name: Build Docs
355375
run: pnpm --filter @objectstack/docs build
356376

357-
check-skill-docs:
358-
name: Check Skill Docs
377+
# Generated artifacts that are committed to the tree: regenerate, fail on drift.
378+
# Named for the class, not for skill docs alone — it has checked the ADR-0087
379+
# protocol artifacts for a while, and a name that hid them is part of why their
380+
# paths went missing from the filter above.
381+
#
382+
# The generated reference docs (content/docs/references/**) are deliberately NOT
383+
# here: they need to gate spec-only PRs, and this whole job is filter-gated, so
384+
# they live in lint.yml's required, unfiltered "TypeScript Type Check" job.
385+
check-generated:
386+
name: Check Generated Artifacts
359387
needs: filter
360-
if: needs.filter.outputs.skilldocs == 'true'
388+
if: needs.filter.outputs.generated == 'true'
361389
runs-on: ubuntu-latest
362390
permissions:
363391
contents: read

.github/workflows/lint.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,21 @@ jobs:
129129
- name: Type check (@objectstack/spec)
130130
run: pnpm --filter @objectstack/spec exec tsc --noEmit
131131

132+
# Generated-docs gate: content/docs/references/** is generated from the spec
133+
# by `gen:schema && gen:docs` and committed. Nothing regenerated it in CI, so
134+
# it drifted silently — #3076 added RowCrudActionOverride to the spec and the
135+
# public reference docs never learned the type existed. Regenerates and fails
136+
# on any difference.
137+
#
138+
# Deliberately lives in this job, not in ci.yml's "Build Docs": that job is
139+
# gated on a `docs` paths-filter that does not include packages/spec/**, so it
140+
# skips exactly the spec-only PRs that cause this drift (#3076 was one). This
141+
# job has no paths filter and is a required status check, so the gate cannot go
142+
# dormant. It reads src/ + json-schema/ via tsx and needs no build, so it runs
143+
# before the workspace build and fails in ~2s.
144+
- name: Check generated reference docs are in sync with the spec
145+
run: pnpm --filter @objectstack/spec check:docs
146+
132147
# Example apps are AI-authoring reference templates; a red typecheck is a
133148
# bad signal to copy from. tsup transpiles them without a full typecheck,
134149
# so build alone will not catch type drift — typecheck them explicitly.

content/docs/references/api/discovery.mdx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ not been verified (may 501 at runtime).
2828
## TypeScript Usage
2929

3030
```typescript
31-
import { ApiRoutes, Discovery, RouteHealthEntry, RouteHealthReport, ServiceInfo, WellKnownCapabilities } from '@objectstack/spec/api';
32-
import type { ApiRoutes, Discovery, RouteHealthEntry, RouteHealthReport, ServiceInfo, WellKnownCapabilities } from '@objectstack/spec/api';
31+
import { ApiRoutes, Discovery, RouteHealthEntry, RouteHealthReport, ServiceInfo, ServiceSelfInfo, WellKnownCapabilities } from '@objectstack/spec/api';
32+
import type { ApiRoutes, Discovery, RouteHealthEntry, RouteHealthReport, ServiceInfo, ServiceSelfInfo, WellKnownCapabilities } from '@objectstack/spec/api';
3333

3434
// Validate data
3535
const result = ApiRoutes.parse(data);
@@ -132,6 +132,19 @@ const result = ApiRoutes.parse(data);
132132
| **rateLimit** | `Object` | optional | Rate limit and quota info for this service |
133133

134134

135+
---
136+
137+
## ServiceSelfInfo
138+
139+
### Properties
140+
141+
| Property | Type | Required | Description |
142+
| :--- | :--- | :--- | :--- |
143+
| **status** | `Enum<'stub' \| 'degraded'>` || stub = placeholder or dev fake (do not use for real work); degraded = functional fallback with reduced capability |
144+
| **handlerReady** | `boolean` | optional | Whether the HTTP handler genuinely serves requests. Defaults: false for stub, true for degraded. |
145+
| **message** | `string` | optional | Human-readable explanation, e.g. what to install for the full implementation |
146+
147+
135148
---
136149

137150
## WellKnownCapabilities

content/docs/references/data/field.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ const result = Address.parse(data);
150150
| **conditionalRequired** | `string \| Object` | optional | Predicate (CEL) — field is required when TRUE. Alias of `requiredWhen`. |
151151
| **widget** | `string` | optional | Form widget override — names a registered field component (resolved as `field:<widget>`) to render this field instead of the `type` default. Degrades to the `type` renderer when unregistered. e.g. "object-ref", "filter-condition", "recipient-picker". |
152152
| **hidden** | `boolean` | optional | Hidden from default UI |
153-
| **readonly** | `boolean` | optional | Read-only in UI |
153+
| **readonly** | `boolean` | optional | Read-only — never editable in forms, AND server-enforced on UPDATE: a non-system write to this field is silently dropped from the payload (#2948/#3003; symmetric with `readonlyWhen`). INSERT may still seed it (defaultValue, import). |
154154
| **requiredPermissions** | `string[]` | optional | [ADR-0066 D3] Capabilities required to read/edit this field (mask on read, deny on write; AND-gate). |
155155
| **system** | `boolean` | optional | Auto-injected system/audit field (e.g. created_at, updated_by, organization_id). Tools that surface system fields separately from author-declared business fields should branch on this flag. |
156156
| **sortable** | `boolean` | optional | Whether field is sortable in list views |

content/docs/references/data/object.mdx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ API Operations Enum
1414
## TypeScript Usage
1515

1616
```typescript
17-
import { ApiMethod, Index, Lifecycle, LifecycleClass, Object, ObjectAccessConfig, ObjectCapabilities, ObjectExtension, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
18-
import type { ApiMethod, Index, Lifecycle, LifecycleClass, Object, ObjectAccessConfig, ObjectCapabilities, ObjectExtension, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
17+
import { ApiMethod, Index, Lifecycle, LifecycleClass, Object, ObjectAccessConfig, ObjectCapabilities, ObjectExtension, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, RowCrudActionOverride, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
18+
import type { ApiMethod, Index, Lifecycle, LifecycleClass, Object, ObjectAccessConfig, ObjectCapabilities, ObjectExtension, ObjectExternalBinding, ObjectFieldGroup, ObjectOwnershipEnum, ObjectRequiredPermissions, PerOperationRequiredPermissions, RowCrudActionOverride, SoftDeleteConfig, TenancyConfig, VersioningConfig } from '@objectstack/spec/data';
1919

2020
// Validate data
2121
const result = ApiMethod.parse(data);
@@ -282,6 +282,21 @@ Type: `string[]`
282282
| **delete** | `string[]` | optional | Capabilities required to delete (delete/purge). |
283283

284284

285+
---
286+
287+
## RowCrudActionOverride
288+
289+
Boolean-or-predicates override for a built-in row CRUD affordance.
290+
291+
### Properties
292+
293+
| Property | Type | Required | Description |
294+
| :--- | :--- | :--- | :--- |
295+
| **enabled** | `boolean` | optional | Object-level on/off for the generic affordance; same meaning as the bare boolean form. Omitted → managedBy bucket default. |
296+
| **visibleWhen** | `string \| Object` | optional | Per-record CEL predicate; false → hide the row button for that record. Fail-closed. |
297+
| **disabledWhen** | `string \| Object` | optional | Per-record CEL predicate; true → render the row button disabled for that record. Fail-soft. |
298+
299+
285300
---
286301

287302
## SoftDeleteConfig

content/docs/references/integration/connector.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ entries with `enabled: false`. Provider-bound declarative instances that
6565

6666
a generic executor (connector-openapi / connector-mcp) materializes at
6767

68-
boot are tracked in #2977 (ADR-0096).
68+
boot are tracked in #2977 (ADR-0097).
6969

7070
Authentication is now imported from the canonical auth/config.zod.ts.
7171

@@ -177,9 +177,9 @@ Circuit breaker configuration
177177
| **description** | `string` | optional | Connector description |
178178
| **icon** | `string` | optional | Icon identifier |
179179
| **authentication** | `Object \| Object \| Object \| Object \| Object` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
180-
| **provider** | `string` | optional | Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0096). |
181-
| **providerConfig** | `Record<string, any>` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi). Requires `provider`. |
182-
| **auth** | `Object \| Object \| Object \| Object` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0096). |
180+
| **provider** | `string` | optional | Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097). |
181+
| **providerConfig** | `Record<string, any>` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
182+
| **auth** | `Object \| Object \| Object \| Object` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
183183
| **actions** | `Object[]` | optional | |
184184
| **triggers** | `Object[]` | optional | |
185185
| **syncConfig** | `Object` | optional | Data sync configuration |
@@ -303,9 +303,9 @@ Connector type
303303
| **description** | `string` | optional | Connector description |
304304
| **icon** | `string` | optional | Icon identifier |
305305
| **authentication** | `Object \| Object \| Object \| Object \| Object` | optional | Authentication configuration (runtime shape with inline secrets). Provider-bound declarative instances use `auth.credentialRef` instead. |
306-
| **provider** | `string` | optional | Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0096). |
307-
| **providerConfig** | `Record<string, any>` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi). Requires `provider`. |
308-
| **auth** | `Object \| Object \| Object \| Object` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0096). |
306+
| **provider** | `string` | optional | Generic-executor key that materializes this declarative entry at boot (e.g. openapi/mcp/rest). Omit for a catalog-only descriptor. Unknown provider ⇒ hard boot error (ADR-0097). |
307+
| **providerConfig** | `Record<string, any>` | optional | Provider-specific config validated by the provider factory at boot (e.g. `{ spec, baseUrl }` for openapi, where spec is an inline document, a package-relative file path like './billing-openapi.json', or an http(s) URL). Requires `provider`. |
308+
| **auth** | `Object \| Object \| Object \| Object` | optional | Declarative instance auth — references credentials via `credentialRef` (resolved at boot), never inline secrets. Requires `provider` (ADR-0097). |
309309
| **actions** | `Object[]` | optional | |
310310
| **triggers** | `Object[]` | optional | |
311311
| **syncConfig** | `Object` | optional | Data sync configuration |

0 commit comments

Comments
 (0)