Skip to content

Commit 5acb58d

Browse files
Elon Muskclaude
andauthored
docs(spec): fix AgentSchema's @example and four stale .strict() tombstone rationales (#10922)
`AgentSchema`'s own `@example` wrote `knowledge: { sources, indexes }` — a key the same file declares as `retiredKey()` about 110 lines below. The canonical example an upgrading author copies taught a key the schema rejects, and typed `never` fails `tsc` at the authoring site. Drop the line; the example keeps `skills`, which is the whole point of the block. Four tombstone rationales still argued from "the schema is not `.strict()`, so a plain deletion would silently strip the key". The #4001 `strictObject` conversion made that false for `AgentSchema`, `FieldSchema` and `ActionSchema`, and for the premise in `shared/retired-key.ts`'s own module docblock. Each now rests on the reason that is load-bearing today: the prescription is the payload, because an unknown-key rejection carries neither the FROM -> TO mapping nor the migration command, and the key is typed `never` so the mistake still fails `tsc` first. Every tombstone stays; only the stated reason changes. Prose only — no schema shape, acceptance behaviour or `.describe()` semantic moves. Claude-Session: https://claude.ai/code/session_01B4h3medzvhB9rpfoja9jcw Co-authored-by: Claude <noreply@anthropic.com>
1 parent 39c682f commit 5acb58d

6 files changed

Lines changed: 64 additions & 24 deletions

File tree

.changeset/olive-pumas-repeat.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Docs accuracy: correct the `AgentSchema` example and four stale `.strict()` tombstone rationales
6+
7+
`AgentSchema`'s own `@example` wrote `knowledge: { sources: …, indexes: … }`, a key the
8+
same schema declares as `retiredKey()` — so the canonical example an author (very often an
9+
AI, ADR-0033) copies taught a key the schema rejects, and typed `never` fails `tsc` at the
10+
authoring site. The line is dropped; the example keeps `skills`, which is the block's point.
11+
12+
Four tombstone rationales still argued from "the schema is not `.strict()`, so a plain
13+
deletion would silently strip the key". The #4001 `strictObject` conversion made that false
14+
for the schemas named: `AgentSchema` (`agent.tools`), `FieldSchema`
15+
(`field.conditionalRequired`), `ActionSchema` (`action.execute`), and the module docblock of
16+
`shared/retired-key.ts` itself. Each now rests on the reason that is load-bearing today —
17+
the prescription is the payload, since an unknown-key rejection carries neither the
18+
FROM → TO mapping nor the migration command, and the key is typed `never` so the mistake
19+
still fails `tsc` first. Every tombstone stays; only the stated reason changes.
20+
21+
Prose only — no schema shape, acceptance behaviour or `.describe()` semantic is touched.

packages/spec/src/ai/agent.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ describe('AIModelConfigSchema', () => {
6868

6969
describe('agent.tools retirement (ADR-0064 / #3820, tombstoned in #3894)', () => {
7070
it('REJECTS a legacy inline tools array, with the fix in the message', () => {
71-
// Tombstoned, not deleted: AgentSchema is not `.strict()`, so a plain
72-
// deletion would silently strip the key and the agent would quietly reach
73-
// none of the tools its author listed. `retiredKey()` makes it audible.
71+
// Tombstoned, not deleted: AgentSchema is `strictObject`, so a plain
72+
// deletion would reject the key with a generic unknown-key error.
73+
// `retiredKey()` is what makes the rejection carry the prescription —
74+
// which is the payload, and what this assertion pins.
7475
expect(() =>
7576
AgentSchema.parse({
7677
name: 'legacy',

packages/spec/src/ai/agent.zod.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ export type StructuredOutputConfigParsed = z.infer<typeof StructuredOutputConfig
136136
* role: 'Help Desk Assistant',
137137
* instructions: 'You are a helpful assistant. Always verify user identity first.',
138138
* skills: ['case_management', 'knowledge_search'],
139-
* knowledge: { sources: ['faq', 'policies'], indexes: ['support_docs'] },
140139
* });
141140
* ```
142141
*/
@@ -223,10 +222,15 @@ export const AgentSchema = lazySchema(() => strictObject({
223222
* resolved `agent.tools[].name` against the FULL registry with no surface
224223
* check, so an `ask`-surface agent could name an authoring tool and get it.
225224
*
226-
* Tombstoned rather than deleted: `AgentSchema` is not `.strict()`, so a
227-
* plain deletion would silently strip the key and the agent would quietly
228-
* reach none of the tools its author listed — the same silent-capability-loss
229-
* shape this whole issue is about (#3820), restored one layer down.
225+
* Tombstoned rather than deleted: `AgentSchema` is `strictObject`, so a plain
226+
* deletion would already REJECT the key — but only with a generic unknown-key
227+
* error. The prescription is the payload. An author who wrote `tools` has to
228+
* be told the specific thing this key's removal means: the capability moves
229+
* into a skill, and ADR-0064's union is the only path from an agent to a tool
230+
* — which this tombstone says and an unknown-key rejection cannot. It also
231+
* types the key `never`, so the same mistake fails `tsc` at the authoring site
232+
* before any parse runs. Those are the two channels an upgrading author, very
233+
* often an AI (ADR-0033), actually reads (`shared/retired-key.ts`).
230234
*/
231235
tools: retiredKey(
232236
'`agent.tools` was removed in @objectstack/spec 17 (#3894) — use `skills`. ' +

packages/spec/src/data/field.zod.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,8 +1177,9 @@ export const FieldSchema = lazySchema(() => strictObject({
11771177
// types, #3726) and `cached`'s (`ComputedFieldCacheSchema` + `ComputedFieldCache`,
11781178
// #3733). Each key was gone from this object while its schema stayed on the
11791179
// published API surface and in the generated reference docs, so an author could
1180-
// still discover the shape and write it. This object is NOT `.strict()`, so that
1181-
// write did not fail loudly: it parsed clean and the key was silently stripped —
1180+
// still discover the shape and write it. This object was NOT `.strict()` then
1181+
// (#4001 has since closed it), so that write did not fail loudly: it parsed
1182+
// clean and the key was silently stripped —
11821183
// the same ADR-0104 failure class as the pre-declaration `accept` / `maxSize`
11831184
// above (accepted in source, dropped in the contract, no feedback). Both schemas
11841185
// are removed as of #3733; all five keys are now dead in both layers, as the
@@ -1205,10 +1206,12 @@ export const FieldSchema = lazySchema(() => strictObject({
12051206

12061207
/**
12071208
* [REMOVED in protocol 17 — #3855] The deprecated alias of `requiredWhen`.
1208-
* Tombstoned rather than deleted: `FieldSchema` is deliberately not
1209-
* `.strict()`, so a plain deletion would silently strip the key and the field
1210-
* would never be required — the ADR-0104 / #3733 failure class this object
1211-
* already carries a comment about.
1209+
* Tombstoned rather than deleted: `FieldSchema` is `strictObject` (#4001), so
1210+
* a plain deletion would reject the key — but with a generic unknown-key
1211+
* error that does not name `requiredWhen`. The prescription is the payload:
1212+
* it carries the rename, so an author who wrote `conditionalRequired` is told
1213+
* where the CEL predicate goes. It also types the key `never`, so the mistake
1214+
* fails `tsc` at the authoring site before any parse runs.
12121215
*/
12131216
conditionalRequired: retiredKey(
12141217
'`conditionalRequired` was removed in @objectstack/spec 17 (#3855) — use `requiredWhen`. ' +

packages/spec/src/shared/retired-key.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
* Tombstones for RETIRED authorable keys (#3855).
55
*
66
* Removing a key an author can write has one hard requirement: the removal must
7-
* be **audible**. None of the schemas that carried a deprecated alias is
8-
* `.strict()` — `FieldSchema` says so in a comment and records that the trap
9-
* already bit once (`dataQuality` / `cached` outlived their keys by a release
10-
* and were silently stripped, #3726 / #3733, the ADR-0104 class). So simply
11-
* deleting the key from the Zod object does not produce an error; it produces a
12-
* **silent strip**, which is the exact failure mode #3713 → #3743 → #3838 →
13-
* #3854 spent four PRs eliminating, reintroduced one layer down.
7+
* be **audible**. When these tombstones were introduced, none of the schemas
8+
* carrying a deprecated alias was `.strict()`, so deleting the key from the Zod
9+
* object produced no error at all — it produced a **silent strip**, the exact
10+
* failure mode #3713 → #3743 → #3838 → #3854 spent four PRs eliminating,
11+
* reintroduced one layer down. `FieldSchema` recorded the trap biting once
12+
* (`dataQuality` / `cached` outlived their keys by a release and were silently
13+
* stripped, #3726 / #3733, the ADR-0104 class).
14+
*
15+
* The #4001 campaign has since closed many of those shapes with `strictObject`,
16+
* and on a closed shape a bare deletion is no longer silent — but it is still
17+
* not enough, which is why tombstones stay. An unknown-key rejection reports
18+
* only that the key is unrecognised; it cannot carry the FROM → TO mapping, the
19+
* ADR the removal rests on, or the migration command. **The prescription is the
20+
* payload**, so both channels below survive the conversion — and on the shapes
21+
* that are still non-strict, the silent strip above is still the alternative.
1422
*
1523
* A tombstone keeps the key declared but makes it unwritable, so the removal
1624
* lands in the two channels an upgrading author — very often an AI (ADR-0033) —

packages/spec/src/ui/action.zod.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -973,9 +973,12 @@ const actionObject = () => strictObject({
973973

974974
/**
975975
* [REMOVED in protocol 17 — #3855] The deprecated alias of `target`.
976-
* Tombstoned rather than deleted: `ActionSchema` is not `.strict()`, so a
977-
* plain deletion would silently strip the key and the action would bind no
978-
* handler at all — the #2169 "Mark Done does nothing" shape, restored.
976+
* Tombstoned rather than deleted: `ActionSchema` is `strictObject`, so a
977+
* plain deletion would reject the key — but with a generic unknown-key error
978+
* that does not name `target`. The prescription is the payload: it carries
979+
* the rename, so an author who wrote `execute` is told where the handler ref
980+
* goes instead of only that the key is unknown. It also types the key
981+
* `never`, so the mistake fails `tsc` before any parse runs.
979982
*/
980983
execute: retiredKey(
981984
'`execute` was removed in @objectstack/spec 17 (#3855) — use `target`. ' +

0 commit comments

Comments
 (0)