feat(spec): cover all three fold-and-drop aliases, not just execute (#3743 follow-up) - #3854
Merged
Merged
Conversation
…#3743 follow-up) #3838 introduced `lintDeprecatedAliases` — the pre-parse pass that reports an alias the parse itself is about to consume — with one rule, for `action.execute`. #3743 predicted the pass would earn its keep beyond that rule. It does: `execute` was never special. The spec has exactly THREE transforms that fold an alias into its canonical key and then drop it from the parsed output, and all three share one failure mode — declare both slots with different values and one is discarded with no signal, invisible to every downstream check because the parse already erased the evidence. Two more rules, same shape, same advisory severity, same two surfaces: field-requiredwhen-conditionalrequired-conflict — `FieldSchema` folds `conditionalRequired` into `requiredWhen` (#3754/#3764); the discarded predicate never gates the field. Covers fields on objects AND on object extensions, and compares the predicate TEXT so a bare string and the `{ dialect, source }` envelope it lowers into read as the same predicate. agent-knowledge-sources-topics-conflict — `AIKnowledgeSchema` folds `knowledge.topics` into `knowledge.sources` (#1878/#1891); the discarded list names RAG sources the agent never recruits from. Compares by SET, so the same sources in a different order stay quiet. The three findings now share one builder, so they phrase the common half identically (which slot wins, that the alias is dropped, delete it) while each still names what its own discarded value would have done. Also corrects `content/docs/ai/agents.mdx`, which documented `knowledge` as `{ topics, indexes }` and used `topics` in all three examples — teaching the deprecated alias as the canonical key, and contradicting `skills/objectstack-ai/SKILL.md`, which already had it right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqC9ckaLbmhmytt65Gxi96
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 28, 2026 12:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3838 (closed #3743).
Why
#3838 introduced
lintDeprecatedAliases— the pre-parse pass that reports an alias the parse itself is about to consume — with exactly one rule, foraction.execute. #3743 predicted the pass would earn its keep beyond that rule:It does.
executewas never special. Grepping the spec for fold-and-drop transforms returns exactly three, and the other two are structurally identical:ui/action.zod.tsexecute→target(#3713/#3742)if (execute && !rest.target)— covered by #3838data/field.zod.tsconditionalRequired→requiredWhen(#3754/#3764)if (conditionalRequired && !rest.requiredWhen)ai/agent.zod.tsknowledge.topics→knowledge.sources(#1878/#1891)if (rest.sources === undefined && topics !== undefined)All three: canonical wins, alias vanishes from the parsed output. Declare both slots with different values and one is discarded with no signal — and no downstream check can report it, because the parse already erased the evidence. That is the whole reason the pass exists as a rule set rather than one hard-coded rule.
What's added
Two rules, same shape, same advisory severity, same two surfaces (
defineStackat authoring time;os build/os validatefor stacks that skip strictdefineStack). Neither fails the build; both name the two values and give the one-line fix.field-requiredwhen-conditionalrequired-conflict— the discarded predicate never gates the field.FieldSchema, same transform, same silent discard).'record.paid'and{ dialect: 'cel', source: 'record.paid' }are recognised as the same predicate and stay quiet — the envelope is what a bare string lowers into, so neither form loses anything.agent-knowledge-sources-topics-conflict— the discarded list names RAG sources the agent never recruits from.sources: ['a','b']besidetopics: ['b','a']recruits the same context, so nothing is lost and the rule stays quiet. Order and repetition are not meaningful for either slot.The three findings now share one builder, so they phrase the common half identically (which slot wins, that the alias is dropped, delete it) while each still names what its own discarded value would have done.
A docs bug found on the way
content/docs/ai/agents.mdxdocumentedknowledgeas{ topics: string[], indexes: string[] }and usedtopics:in all three code examples — teaching the deprecated alias as if it were the canonical key, and contradictingskills/objectstack-ai/SKILL.md, which already said "sources(canonical;topicsis a deprecated alias)". The human docs and the AI-authoring skill disagreed, and the human docs were the wrong one. Examples now usesources; the field table names both and says which is which.content/docs/data-modeling/fields.mdxgains the same warning note #3838 added to the objectui actions reference.Changes
packages/spec/src/shared/deprecated-aliases.ts— two rules + the shared finding builder and the expression/list helpers they needpackages/spec/src/index.ts,packages/spec/api-surface.json— two new rule-id exports (0 breaking, 2 added)content/docs/ai/agents.mdx,content/docs/data-modeling/fields.mdxNo CLI change: both call sites already loop over whatever the pass returns, so the new rules reach
os build/os validateanddefineStackwith no rewiring — which is what "severity modelled from day one" bought.Testing
packages/spec/src/shared/deprecated-aliases.test.ts— 26 cases (was 13). New: both-declared for each rule, one-slot-only, envelope-vs-string equivalence, envelope on both slots, object extensions, set-equality in any order, empty list treated as undeclared, missingknowledgeblock, all three rules firing independently in one pass, and a canonical-keys-only stack returning nothing.packages/spec/src/stack.test.ts— a field alias surfaces throughdefineStackon the same terms as an action alias, proving the wiring is rule-agnostic and not action-shaped, and the parse still drops the alias.@objectstack/spec6796 passed ·@objectstack/cli738 passed · all example apps typecheck and pass.lint,check:nul-bytes,check:doc-authoring,check:role-word,check:org-identifier,check:authz-resolver,check:release-notes, spectsc --noEmit,check:docs,check:skill-refs,check:react-blocks,check:api-surface,check:liveness, examples typecheck, downstream-contract typecheck.All three rules against the built spec:
Generated by Claude Code