Skip to content

Commit 5c9ab56

Browse files
os-zhuangclaude
andcommitted
docs(audits): recheck the §3 naming-drift list; fix the os doctor lookupFilters hint (#1878)
Re-verified all nine spec-key-vs-consumed-key drifts against current code in both repos (including the os:check TypeScript blocks inside skills/*/SKILL.md, which compile against the spec and are real consumers). Six of nine were already resolved -- the list was stale, not the code: referenceFilters (-> lookupFilters), maxRating (-> max), page visibility (-> visibleWhen), dashboard title/label (dual-read + strict widget schema), flow http, agent knowledge.topics (alias folded #1891), webhook keys (#3489); action disabled was fixed this month (objectui#2863). skill requiredPermissions was never a drift -- the original audit described prose drift in the docs, not a spec-vs-runtime mismatch. Still real: the coupled page pair type->pageType + label->title, which mis-renders every non-record page today (fixed in the companion objectui PR), and two snake-only max_length readers. - Add 2026-07-naming-drift-recheck.md with per-item file:line evidence, the gate-coverage finding (bare z.object silently strips; the dashboard-widget .strict() + naming error map is the only mechanism that teaches), and the three items left as owner decisions (app badgeVariant/separator decayed when NavigationRenderer was replaced by UnifiedSidebar; skill.permissions has no gate; agent.knowledge is inert). - Annotate README §3 with the recheck summary. - Fix os doctor: the reference_filters rule pointed at the REMOVED referenceFilters key instead of the live lookupFilters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6ba3788 commit 5c9ab56

4 files changed

Lines changed: 121 additions & 3 deletions

File tree

.changeset/naming-drift-recheck.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
fix(doctor): point the retired `reference_filters` hint at `lookupFilters` (#1878 §3 recheck)
6+
7+
`os doctor`'s snake→camel rule table advised "Use `referenceFilters`
8+
(camelCase)" — a key REMOVED from `FieldSchema` in #2377/ADR-0049, which the
9+
non-strict schema silently strips. The live successor is `lookupFilters` (read
10+
by the objectui lookup picker). The rule now matches both spellings and names
11+
the right key.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Recheck: "naming drift → silent no-ops" (§3)
2+
3+
**Date**: 2026-07-27 · **Supersedes**: the drift list in
4+
[`README.md` §3](./README.md) (dated 2026-06-15). · **Umbrella**: #1878.
5+
6+
The 2026-06 audit listed nine spec-key ≠ consumed-key drifts. Re-verified every
7+
one against current code in **both** repos (framework + objectui, including the
8+
`<!-- os:check -->` TypeScript blocks inside `skills/*/SKILL.md`, which compile
9+
against the spec and are real consumers).
10+
11+
**Result: six of nine are already resolved** — the list, not the code, was
12+
stale. Three real problems remain, two of them a coupled pair that produces
13+
**visibly wrong rendering today**.
14+
15+
> Vocabulary used below:
16+
> - **FORWARD-DRIFT** — spec declares `A`, the consumer reads `B`. Authoring the
17+
> documented key silently no-ops. The dangerous direction: the author did
18+
> everything right.
19+
> - **REVERSE-DRIFT** — the consumer reads `B`, the spec has no `B`. The
20+
> capability works but is undiscoverable (and rejected outright by a strict
21+
> schema).
22+
23+
## Verdicts
24+
25+
| # | Item (as listed in §3) | Verdict | Evidence |
26+
|---|---|---|---|
27+
| 1 | field `maxLength` / `minLength` | **MOSTLY ALIGNED** — enforcement is fine; two cosmetic readers still snake-only | server: `objectql/src/validation/record-validator.ts:243-247` (rejects on the camelCase keys); client: `fields/src/index.tsx:2013,2022` dual-reads. Residual: `plugin-form/src/ObjectForm.tsx:607-608` and `fields/src/widgets/TextAreaField.tsx:39` read `max_length` only → no HTML `maxlength` cap, no character counter |
28+
| 2 | field `referenceFilters` | **RESOLVED** (item stale) | key removed in #2377/ADR-0049 (tombstone `field.zod.ts:335-337`); successor `lookupFilters` (`:418`) is read by `fields/src/widgets/LookupField.tsx:244` and scopes the picker |
29+
| 3 | field `maxRating` | **RESOLVED** (item stale) | `maxRating` was pruned; the builder emits `max` (`field.zod.ts:747-750`) and `fields/src/widgets/RatingField.tsx:13` reads `max`. Zero `maxRating` occurrences in objectui |
30+
| 4 | **page `type``pageType`** | **🔴 FORWARD-DRIFT — live** | spec `type` (`ui/page.zod.ts:310`); renderer reads `schema.pageType` (`components/src/renderers/layout/page.tsx:391`), falling back to `'record'`. The boundary (`app-shell/src/views/PageView.tsx:123`) passes `type` only — **no mapping exists** |
31+
| 5 | **page `label``title`** | **🔴 FORWARD-DRIFT — live** | spec `label` is required (`page.zod.ts:303`); the region/SDUI renderer reads `schema.title` with no fallback (`page.tsx:503-507`). Zero `schema.label` reads in `page.tsx` |
32+
| 6 | page `visibility` | **ALIGNED** (item stale) | spec folds `visibility``visibleWhen` (`page.zod.ts:116,126`); `react/src/SchemaRenderer.tsx:285` consumes `visibleWhen`, `:294` still honors raw `visibility` |
33+
| 7 | dashboard `title` vs `label` | **ALIGNED** (item stale) | renderer dual-reads `title \|\| label` (`plugin-dashboard/.../DashboardRenderer.tsx:782`, `DashboardGridLayout.tsx:278`). Widget-level keys additionally hard-fail via a `.strict()` schema + naming error map (`dashboard.zod.ts:273,98-123`, #1894) |
34+
| 8 | app `accentColor` / `badgeVariant` / `separator` | **SPLIT**`accentColor` aligned; the other two **decayed** | #1894 declared all three in the spec to match objectui's `NavigationRenderer`. `accentColor` is still consumed (`app-shell/src/layout/ConsoleLayout.tsx:171`, `PageHeader.tsx:46`, `apps/console/src/hooks/useBranding.ts:25`). But **`NavigationRenderer` no longer exists** — the live nav is `UnifiedSidebar`, which renders neither `badgeVariant` **nor `badge` itself**, and has no `separator` branch (`separator` is only recognized as "no href" by `AppContent.tsx:849` and excluded by `nav-target.ts:17`) |
35+
| 9 | action `disabled``enabled` | **RESOLVED 2026-07** | fixed across all six rendering surfaces (objectui#2863); showcase specimen `showcase_archive_task` (#3643) |
36+
|| flow `http` vs `http_request` | **ALIGNED** (item stale) | spec enum carries `http` marked canonical (`automation/flow.zod.ts:32`); the runtime registers `HTTP_TYPE = 'http'` (`service-automation/src/builtin/http-nodes.ts:48`) |
37+
|| skill `requiredPermissions` vs `permissions` | **NOT A DRIFT** — mis-filed | `requiredPermissions` **never existed** on `SkillSchema`; the spec key is and was `permissions` (`ai/skill.zod.ts:121`). The original audit described prose/label drift in the docs, not a spec-vs-runtime mismatch |
38+
|| agent `knowledge.{topics → sources}` | **RESOLVED** (item stale) | #1891 made `sources` canonical and folds the `topics` alias via `.transform()` (`ai/agent.zod.ts:35-48`); `agent.test.ts:105-123` asserts `'topics' in parsed === false` |
39+
|| webhook `object``object_name`, `isActive``active` | **RESOLVED** | #3489 materializes declared webhooks with an explicit mapping (`plugin-webhooks/src/bootstrap-declared-webhooks.ts:22-23`) |
40+
41+
## The one that matters: the page pair (#4 + #5)
42+
43+
These two are **coupled and must be fixed together**. The header block is gated
44+
on `pageType !== 'record'` (`page.tsx:503`) — and because #4 pins `pageType` to
45+
its `'record'` fallback, the gate is *always false*. Wiring `label → title`
46+
alone would still render no title.
47+
48+
Observable today in the showcase: `capability-map` (`type: 'home'`),
49+
`active-projects` (`type: 'list'`) and `command-center-jsx` (`type: 'home'`) all
50+
render as record pages — wrong `max-width` (`page.tsx:486`), wrong
51+
`data-page-type` attribute (`:494`), and their page titles suppressed.
52+
53+
Why it survived a year: the spec's `type` **defaults to `'record'`**
54+
(`page.zod.ts:310`) and the renderer's fallback is *also* `'record'`, so the
55+
most common page kind looks correct by coincidence.
56+
57+
## Gate coverage — the systemic finding
58+
59+
For every item above, authoring the wrong key is **silently stripped** at
60+
runtime. `FieldSchema` and `PageSchema` are bare `z.object` (no `.strict()`),
61+
verified empirically: parsing a page with `pageType` + `title` returns neither
62+
key, with no error.
63+
64+
What does exist:
65+
- **TypeScript authoring is a real gate**`defineField`/`defineSkill` etc. take
66+
`z.input<typeof Schema>`, so excess-property checking rejects a wrong key in an
67+
object literal (`Field.rating(5, { maxRating: 10 })` → TS2353). Bypassed by
68+
`as any`, JSON/YAML, and API-posted metadata.
69+
- **Widget-level dashboard keys hard-fail** via `.strict()` + a naming-aware
70+
error map (#1894) — the pattern worth copying.
71+
- **`check:liveness` does not help here**: it ratchets *declared* spec props into
72+
the ledger; it says nothing about *authored unknown* keys.
73+
74+
The dashboard-widget approach (strict + a fixable error message that names the
75+
right key) is the only mechanism in the codebase that turns a silent no-op into
76+
a teaching error. Extending it to `PageSchema` / `FieldSchema` is the structural
77+
fix for this whole class; the per-item wiring below is the tactical one.
78+
79+
## Disposition
80+
81+
**Fixed in this pass:**
82+
1. **page `type``pageType`** + **page `label``title`** — wired at the
83+
boundary + a `title || label` fallback (objectui).
84+
2. **field `maxLength`/`minLength`** — the two snake-only readers now dual-read,
85+
matching the `buildValidationRules` precedent already in the same repo.
86+
3. **`os doctor`** pointed `reference_filters` at the removed `referenceFilters`
87+
instead of the live `lookupFilters` — corrected (framework).
88+
89+
**Recorded, not fixed (each needs an owner decision):**
90+
4. **app `badgeVariant` + nav `separator`** — a #1894 reverse-drift fix that
91+
**decayed** when `NavigationRenderer` was replaced by `UnifiedSidebar`.
92+
Enforce-or-prune: either the sidebar renders nav badges + separators (they are
93+
ordinary nav affordances), or both keys leave the spec. Note `badge` itself is
94+
also unrendered, so this is "nav badges are gone", not merely a variant key.
95+
5. **`skill.permissions` has no gate** — mis-filed as naming drift; it is an
96+
aspirational-config item (§4). The ledger marks it `live` on the evidence of a
97+
*preview renderer* while the identically-unenforced `tool.permissions` is
98+
`dead` + `authorWarn`. An author writing `skill.permissions` gets silence
99+
where they should get a warning.
100+
6. **`agent.knowledge` is inert at runtime** — naming is fixed, but RAG resolves
101+
its sources from the LLM tool call, not from the authored block. Wire or mark
102+
experimental.

docs/audits/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Spec moved to `dataset`+`values`/`dimensions`, but: the **chart view variant** +
4747
### 3. 🟠 Naming drift → silent no-ops (spec key ≠ consumed key)
4848
field `maxLength`/`minLength`/`referenceFilters`/`maxRating`; page `type``pageType` & `label``title` & `visibility`; dashboard `title` vs `label`; app `accentColor`/`badgeVariant`/`separator` (renderer reads, **not in spec**); action `disabled``enabled`; flow `http` vs `http_request`; skill `requiredPermissions` vs `permissions`; agent `knowledge.{topics→sources}`; webhook `object``object_name`, `isActive``active`.
4949

50+
**✅ Rechecked 2026-07-27 — six of nine were already resolved; the list was stale, not the code.** Closed: `referenceFilters` (→ `lookupFilters`, #2377), `maxRating` (→ `max`), page `visibility` (→ `visibleWhen`), dashboard `title`/`label` (renderer dual-reads; widget keys hard-fail via `.strict()`, #1894), flow `http` (canonical), agent `knowledge.topics` (alias folded, #1891), webhook keys (#3489); action `disabled` fixed across all six rendering surfaces (objectui#2863 + #3643); skill `requiredPermissions` was **never a drift** (mis-filed prose drift). **Still real, fixed in the recheck pass:** the coupled page pair `type`→`pageType` + `label`→`title` (mis-renders every non-record page today) and two snake-only `max_length` readers. **Left as decisions:** app `badgeVariant` + nav `separator` decayed when `NavigationRenderer` was replaced by `UnifiedSidebar`; `skill.permissions` has no gate (belongs in §4); `agent.knowledge` is inert at runtime. Full per-item evidence: [`2026-07-naming-drift-recheck.md`](./2026-07-naming-drift-recheck.md).
51+
5052
### 4. 🟠 Aspirational config (rich spec, zero runtime) — prune or mark `experimental`
5153
field enhanced-type configs (barcode/qr/slider/rating/color/location) + governance (encryption/masking/audit/dataQuality); object `enable`/versioning/partitioning/cdc/softDelete/search; agent autonomy (memory/guardrails/structuredOutput/lifecycle); tool `outputSchema`; job `retryPolicy`/`timeout`; theme rtl/density/touchTarget; translation `messageFormat:'icu'` (no ICU engine); **portal (entire)**; webhook non-HMAC auth.
5254

packages/cli/src/commands/doctor.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,12 @@ const DEPRECATED_PATTERNS: Array<{
321321
replacement: 'Use minLength (camelCase)',
322322
},
323323
{
324-
pattern: /\breference_filters\b/,
325-
description: 'snake_case config key: reference_filters',
326-
replacement: 'Use referenceFilters (camelCase)',
324+
// `referenceFilters` was REMOVED from FieldSchema (#2377 / ADR-0049); its
325+
// successor is `lookupFilters` (read by the objectui LookupField picker).
326+
// Pointing at the removed key sent authors to a silently-stripped spelling.
327+
pattern: /\breference_filters\b|\breferenceFilters\b/,
328+
description: 'retired lookup-scoping key: reference_filters / referenceFilters',
329+
replacement: 'Use lookupFilters (camelCase) — `referenceFilters` was removed in #2377',
327330
},
328331
{
329332
pattern: /\bunique_name\b/,

0 commit comments

Comments
 (0)