feat(spec): reject unknown keys on an action param instead of stripping them (#3405) - #3746
Merged
Merged
Conversation
…ng them (#3405) Closes part 3 of #3405 — the item deferred out of #3406 as "evaluate separately". Parts 1 and 2 gave an inline record-picker param a `reference` key and made a targetless one a parse error. That fixed the symptom. The mechanism that caused it stayed: `ActionParamSchema` was zod-default `.strip`, so any key it does not declare was discarded silently and the param went on parsing. An author wrote a correct, clearly intended `reference: 'sys_user'`, the key was eaten, and the dialog rendered a text box asking a human to paste a UUID — no error anywhere. The next mis-spelled key would have failed the same way, just as quietly. An action param is now `.strict()`, with an error map that makes the rejection fixable rather than merely loud: - Case/underscore slips (`help_text` → `helpText`, `default_value` → `defaultValue`) resolve through the shared `findClosestMatches`, bounded by the same length-relative distance `suggestKey` uses in `data/object.zod.ts` — a flat distance of 3 suggests `visible` for `wibble`. - Semantic near-misses edit distance cannot reach are named explicitly, in the `FIELD_TYPE_ALIASES` style: `reference_to` / `referenceTo` / `targetObject` → `reference` (the runtime field shape spells it the first way, objectui's resolved param the second), and `visibleWhen` / `visibleOn` / `visibility` → `visible`. That last one is why this matters beyond typos: ADR-0089 made `visibleWhen` canonical on view/page schemas, so borrowing it here used to strip a param's capability gate and render it unconditionally. Follows ADR-0078 (no-silently-inert-metadata) and ADR-0049 (enforce-or-remove), and matches the precedent set by ADR-0089 D3a for the view/page schemas. Verification: spec 258 files / 6716 tests pass; `tsc --noEmit` clean; app-showcase, app-crm and app-todo all `validate` clean — no existing metadata in the repo carried an undeclared param key. Planting `visibleWhen` on showcase's inline picker param reproduces the new error with the `visibleWhen` → `visible` prescription, and removing it validates again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
|
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:
|
`scripts/build-docs.ts` `getFileDescription()` takes the FIRST `/** */` block in a module, verbatim, as the description of its generated reference page. Placing the new `ACTION_PARAM_KEYS` / error-map helpers above the "Action Parameter Schema" JSDoc therefore replaced the public authoring guide on `content/docs/references/ui/action.mdx` with an internal note about why a key list is kept beside the schema — which is what `check:docs` caught. Moved the helpers back below that JSDoc (after the `lazySchema` import, where they were originally). The generated doc is byte-identical to main again: `check:docs` reports 250 generated files in sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
…3405) Two more generated-artifact gates behind `check:docs`, both tripped by the same commit: - `check:skill-refs` — importing `shared/suggestions.zod.ts` from `ui/action.zod.ts` pulls it into the transitive reference set of the objectstack-data / -ui / -platform skills. Regenerated via `gen:skill-refs`; the diff is the one expected line per skill, and `action.zod.ts` still resolves to "Action Parameter Schema", confirming the JSDoc-order fix held. - `check:api-surface` — `actionParamUnknownKeyError` was exported, which added it to the package's public API. It has no caller outside its own module, so the export was unnecessary: unlike `strictVisibilityError`, which is shared across the view/page schemas, this map is wired into exactly one schema. Made it module-private; the public API surface is now unchanged by this PR. All ten `check:*` gates in packages/spec pass locally, alongside 258 files / 6716 tests and a clean `tsc --noEmit`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
os-zhuang
marked this pull request as ready for review
July 28, 2026 02:01
This was referenced Jul 30, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…emas (#4001 Tier-A) (#4071) Zod's default .strip silently discarded undeclared keys on authorable schemas — the instance kept parsing, so a mis-spelled or wrong-layer key shipped as metadata that quietly ignored the author's config (#3405, #1535). This lands the #4001 Tier-A slice inside the v17 breaking window. - docs/audits/2026-07-unknown-key-strictness-ledger.md: the authorable / wire / open triage (#4001 step 1), classified by "who writes this schema's input", with the ratchet's verified next targets. - shared/suggestions.zod.ts: new strictUnknownKeyError factory generalizing the #3746 hand-rolled map (alias table + length-relative edit distance + tombstone/wrong-layer guidance); ui/action.zod.ts re-homes onto it with byte-identical messages. - security/permission.zod.ts: PermissionSetSchema, ObjectPermissionSchema, FieldPermissionSchema, AdminScopeSchema are .strict(). EffectiveObjectPermissionSchema explicitly .strip()s back — response shapes stay wire-tolerant. - automation/flow.zod.ts: FlowSchema, FlowNodeSchema, FlowEdgeSchema, FlowVariableSchema are .strict(); a node's config record stays open (the executor's configSchema, #4027/#4040, and the ADR-0087 conversion layer own it). Three real defects the gate caught in its first two runs, all previously invisible — each key written by real code, silently dropped at parse: PermissionSetSchema could represent neither `description` (authored by the built-in default sets, read by the Setup projection) nor the ADR-0010 runtime protection envelope (stamped by applyProtection on every metadata type and round-tripped through getMetaItemLayered -> saveMetaItem; every sibling registered type already spread MetadataProtectionFields, permission was the outlier); and a dogfood fixture declared the object-level `sharingModel` on a flow. Two of the three are inverse drift — the direction the per-property liveness ledger cannot see. Migration is behavior-preserving by construction: any key now rejected was previously stripped and therefore never had a runtime effect.
os-zhuang
added a commit
that referenced
this pull request
Jul 30, 2026
…4001 step 3) (#4119) Third click of the unknown-key strictness ratchet (flow + permission in #4071, RLS / sharing / position in #4099). Approval is a v17-new authoring surface — tightened while young, before stored volume exists: - ApprovalNodeConfigSchema, ApprovalNodeApproverSchema, ApprovalEscalationSchema and DecisionOutputDefSchema are .strict() with fixable errors via the shared strictUnknownKeyError factory. - ApprovalNodeConfig's guidance is the ADR-0019 re-home map: steps -> successive approval nodes on the canvas, entryCriteria -> the entering edge's condition, onApprove/onReject -> the approve/reject out-edges, rejectionBehavior -> a declared back-edge (ADR-0044) with maxRevisions. - The published JSON schema (getApprovalNodeConfigJsonSchema) carries additionalProperties:false into the Studio property form AND registerFlow's per-node config validation (#4027/#4040). Verified the #3746 hazard: z.toJSONSchema on the strict lazySchema does not throw. Verified: spec 6995 tests + tsc clean; all 12 check gates; plugin-approvals 326 / service-automation 457; dogfood 72 files / 418 tests; showcase / crm / todo validate clean.
This was referenced Jul 30, 2026
Merged
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 2, 2026
…te name it references (objectstack-ai#4457) (objectstack-ai#4508) A selection-bar bulk action was `z.array(z.record(z.string(), z.any()))` — no shape at all. The real contract lived in objectui's `BulkActionDef` interface and in the executor that reads it, so every authoring mistake landed as a silent runtime downgrade: `opeartion` parsed and the executor hit `Unknown operation: undefined` per row; `excution: 'aggregate'` parsed and left the def per-record, so the endpoint written for ONE `_selectedIds` call got N calls instead. `ui/bulk-action.zod.ts` types it with the treatment `ActionParamSchema` got in objectstack-ai#3746/objectstack-ai#4001: a strict def whose unknown-key error names the offending key and the canonical spelling. It also refuses the combinations the executor never reads (`patch` outside an update, `execution` outside a custom, `params` on a delete, `batchSize` on an aggregate) and a hand-written `actionDef`, which the renderer attaches and which authored by hand would smuggle an action definition past the action registry. One shape that parsed before is now rejected: `operation: 'custom'` without `execution: 'aggregate'`. `resolveBulkActions` attaches a dispatcher for exactly one authored shape; every other custom def falls to `Promise.resolve()` per row — a button that reports success for every selected record and does nothing. The error names both legal forms. `params[]` stays `.passthrough()` (the renderer declares a widget-config catch-all), and the bulk-param/action-param spelling divergence is documented rather than converged — that needs a cross-repo change, and typing them as they are is what makes the divergence visible. Lint: `validate-action-name-refs` now covers `bulkActionDefs`, for the entries that are references rather than button ids (`execution: 'aggregate'`). The walk also reaches an object's own `listViews` for the first time, and the hint no longer tells a bulk-surface author to add a `locations` entry the selection bar does not read. Verified zero new findings against app-showcase / app-crm / app-todo. Claude-Session: https://claude.ai/code/session_01S9aiswZBzoVYsyLKRuGByE Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 2, 2026
…s` stops pretending to be a gate (objectstack-ai#4001) (objectstack-ai#4528) Mechanical work on the registered-type line with `strictObject`: `report`, `dataset`, `email_template`, `skill`, `job`, `book`. One call each, plus the aliases that fit the surface's own vocabulary — `sections`/`chapters`/`toc` → `groups` on a book, `cron`/`interval` → `schedule` on a job, `title`/`content`/ `html` → `subject`/`body` on an email template. One of the six is not mechanical, and it is the class this campaign exists for. `skill` accepted a `permissions` key and dropped it. Skill invocation was never permission-gated, so an author who wrote `permissions: ['order.manage']` believed they had restricted who could invoke the skill, and had not. A silent permission hole — the same shape as `visibleWhen` → `visible` in objectstack-ai#3746, where the most valuable alias was not a typo but a key that READS as a security control and silently is not one. A test pinned that strip as correct behaviour. Its comment even carried the right answer — gate at the AGENT via `access`/`permissions`, enforced since objectstack-ai#1884 — but a comment in a test file reaches everyone except the author who got it wrong. The rejection now carries the prescription; the test asserts the rejection. That is the fourth test in this campaign found codifying a strip-era fiction as expected behaviour (`position.parent`, `object.namespace`, the retired `compactLayout` alias, and now this one). The pattern is consistent enough to state: when a schema is silently lenient, its tests eventually assert the leniency, and the assertion then reads as intent. Registered types closed at the top level: 16 of 25, up from 9 when this line started. Still open: action, agent, dashboard, field, mapping, page, translation, view. The warning layer's covered population drops 12 roots → 6, which is the parse taking over rather than coverage rotting; nested strip sites under a closed root still report, unchanged. Verified: spec 284 files / 7187 tests, `tsc --noEmit` clean, all 8 generated artifacts current, all 15 `check:*` gates green. Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 2, 2026
…ectstack-ai#4533) * feat(spec)!: action closes, and the ADR-0010 envelope debt list reaches zero (objectstack-ai#4001) `ActionParamSchema` has been strict since objectstack-ai#3746 — the template this campaign was generalized from, and the source of its sharpest lesson: `visibleWhen` → `visible` showed the most valuable alias entry is rarely a typo but a key that reads as a control and silently is not one. The action AROUND the param stayed open for three more releases. The AI exposure block is why this one mattered. `ActionAiSchema` is the governance gate — its own doc says a half-finished or unreviewed action must never be silently armed — yet `requireConfirmation`, one letter off, was dropped in silence, so an author who asked for a human-in-the-loop gate on an AI-invoked action did not get one and was not told. The root tombstones the key this campaign cares most about. `permissions` on an action is NOT a permission gate: authorization comes from the object's permission sets and, on the AI surface, from the agent's access/permissions (objectstack-ai#1884). The rejection says so and names the trap beside it — `visible` and `disabled` are UI predicates, they hide or grey a button, they do not stop a request. Third time in this campaign after skill.permissions and agent.visibility, always a different surface, always reading like a control. The undeclared-envelope debt list is now EMPTY. The structural walk opened it with eight names after replacing a probe that had been hiding seven; `action` was the last. The empty set is kept, not deleted: with no exemptions the DECLARES case runs over every registered type, so a new type shipping without the spread fails immediately instead of being quietly added to a list. Registered types closed: 24 of 25. Only `view` remains. Two lint-layer facts recorded rather than papered over: the array-index test has run out of subject (no strip-mode array-of-objects remains anywhere), and `view` is the last open root — when it closes, change the floor to 0 and assert the empty set deliberately rather than deleting the test, because an empty result nobody chose is indistinguishable from a derivation that broke. Verified: 284 files / 7240 tests, tsc clean, 8 generated artifacts current, all spec gates green, and CRM/Todo/showcase/platform-objects build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY * fix(spec): an action's capability gate is real — point the near-misses AT it (objectstack-ai#4001) The first draft of this batch's guidance claimed `permissions` on an action is not a permission gate and sent authors to the object's permission sets. That is wrong: `requiredPermissions` (ADR-0066 D4) is a declared key enforced with a 403 on the platform action route, and `ui/actions.mdx` teaches it by name. Anyone following the prescription would have been invited to delete a working gate. - `permissions` / `capabilities` / `requiresPermissions` / `acl` now RENAME onto `requiredPermissions` instead of pointing away from it. - The dead `requiredPermissions` guidance entry is removed — the key is declared, so strict never reaches it; the entry was wrong AND unreachable. - `location` → `locations` was backwards: `locations` is the declared key, so the alias would have suggested renaming a correct key to a nonexistent one. - The `visible`/`disabled` warning survives on `hidden`, where it belongs: hiding is not gating, and a headless action is `locations: []`. Ledger finding 7 for the fourth time — this campaign's own prescriptions are a surface that can be confidently wrong. Caught by checking the docs the drift report flagged against the schema rather than against memory of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY * test(metadata-protocol): a stored-row fixture wrote `object` where the key is `objectName` (objectstack-ai#4001) `ActionSchema` has never declared `object` — the key is `objectName`. Two stored-row fixtures wrote it anyway, `.strip` ate it, and the tests passed. With the shape closed the row now fails spec validation after conversion and the protocol reports `invalid: 1`. Worth naming because of where it was: these fixtures stand in for pre-17 rows in a database, so a typo in one reads as a claim about real legacy data — "stored actions carry `object`" — when no such key ever existed. There is no missing conversion here; `git log -S` finds no commit that ever declared it. Sixth strip-era fiction this campaign has found in a test, and the first dressed as a stored ROW rather than an authored literal. Also the reason this was caught by CI rather than locally: the previous pass ran only `packages/spec`. The blast radius of closing a registered type is every package that parses that type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY --------- Co-authored-by: Claude <noreply@anthropic.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
Aug 3, 2026
`gen:docs` only — the property tables for `automation/Webhook` and the `integration/WebhookConfig` extension gain the `protection` block and the seven `_lock*`/`_provenance`/`_package*` rows. Checked for the #3746 trap: no page description or frontmatter moved, so no internal comment leaked onto a public reference page. Part of #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
This was referenced Aug 3, 2026
xuyushun441-sys
pushed a commit
that referenced
this pull request
Aug 4, 2026
getFileDescription() 用裸正则在原始源码上找第一个 doc block,所以在 // 行里 引用那个双星开头的字面量也会被匹配成文件的首个 doc block —— 于是这条警告 自己把 theme/chart 两个参考页的描述清空了。check:docs 抓到;改写措辞后 两页与 main 逐字节相同。 同时把 clickAction 的 finding-7 说明从 JSDoc 移到 // 注释:它是给下一个 维护者的话,不是协议文档,不该发布到公开参考页。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
This was referenced Aug 4, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 4, 2026
…objectstack-ai#4974) * feat(spec)!: close nine authorable automation shapes against unknown keys (objectstack-ai#4001 batch 11) zod's default `.strip` discards an undeclared key and parses on. Batch 11 closes the nine remaining shapes in `automation/`'s main body, each rejection naming the surface, the key, and — where the word is recognisable — the canonical spelling. `flow.zod.ts`: the four OUTER shapes were closed earlier; their six nested authoring blocks were not, so the gate rejected `nodee:` at node level while `connectorConfig: { connectorID }` parsed clean and dispatched against an undefined connector id. Now strict: `FlowNode.connectorConfig` / `.position` / `.inputSchema` / `.waitEventConfig` / `.boundaryConfig`, `Flow.errorHandling`. Deliberately still open, now pinned in code AND tests so a later sweep stops rather than "finishing" the file: the node `config` slot (ADR-0018) and `FlowVersionHistorySchema` (emitted, not authored — its `definition` is `FlowSchema`, so the authored half is gated anyway). Every alias is a real in-repo spelling of the same knob on a neighbouring surface, taken from an AST census over the repo's own payloads, not guessed: `backoffMs` from `shared/retry-policy.zod.ts` (objectstack-ai#4661), `initialDelayMs` / `maxDelayMs` from connector `RetryConfig`, `attachedToRef` / `cancelActivity` from BPMN 2.0. Two cases get a prescription instead of a rename because a rename would be wrong: `inputSchema.optional` is the opposite polarity of `required`, and `errorHandling.maxAttempts` counts the first attempt while `maxRetries` does not — renaming it alone would quietly run one attempt fewer. `time-relative-trigger.zod.ts`: the schema objectstack-ai#4852 made visible (its only site was line-wrapped, the old counter read zero, and a zero-site file is skipped by the coverage walk). It sits under the open node `config` slot, so it is the only key gate there is — and it is `safeParse`d at BIND time, so `offsetDay` beside a valid mode used to bind a sweep with the author's narrowing discarded and report itself configured. `flow-function.zod.ts`: binds at authoring only (boot reads `normalizeFlowFunctionEntry`), stated in the code rather than implied. That is why it matters: the reader takes two keys and ignores the rest by construction, so a misspelled `effect` was dropped and then not looked for — the function ran and its writes were counted as none, keeping objectstack-ai#4354's broken-sweep query silent. `webhook.zod.ts`: the ledger's `(p)` resolved to the opposite of "spec-only" — three parse doors, one of them `bootstrapDeclaredWebhooks` at BOOT, which warns and SKIPS on failure. Hence the ADR-0010 envelope in the same commit: `applyProtection` stamps `_packageId`/`_provenance` on every type, so closing the shape without declaring them would have turned every package-shipped webhook into a skipped subscription after a redeploy. Strictness also rides `.extend()` onto the connector `WebhookConfigSchema` — verified against real zod and pinned in `connector.test.ts`. Ledger: `automation/` 67 → 58 strip; three rows reached zero and were deleted by the objectstack-ai#4852 reverse pin. Gate re-proved red in both directions before trusting it, and every new assertion was proved to depend on the tightening by neutering `strictObject` (19 red, restored byte-identical). Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(spec): regenerate reference pages for the webhook ADR-0010 envelope `gen:docs` only — the property tables for `automation/Webhook` and the `integration/WebhookConfig` extension gain the `protection` block and the seven `_lock*`/`_provenance`/`_package*` rows. Checked for the objectstack-ai#3746 trap: no page description or frontmatter moved, so no internal comment leaked onto a public reference page. Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * fix(spec): correct three history strings that named the wrong failure Self-review against the actual behaviour, before the claim shipped. The `connectorConfig` / `boundaryConfig` / `position` / `inputSchema` prose named a slip on a REQUIRED key (`connectorID`, `attachedToRef`) as the silent case. It never was: a required key spelled wrong then reads as MISSING, which zod has always rejected loudly. Nothing was silent about it. What `.strip` actually swallowed is the OPTIONAL half — the mapped `input` map (dispatched as `input ?? {}`: a successful connector call carrying nothing), BPMN's `cancelActivity: false` (so `interrupting` fell to its `true` default and a NON-interrupting event cancelled the host anyway), `optional: false` (so `required` fell to false and `validateNodeInputSchemas` had nothing to require), a canvas hint beside x/y. I.e. exactly the keys an author adds to CONSTRAIN behaviour, replaced by a permissive default. The corrected version is the stronger argument as well as the true one, and getting it wrong is the failure mode this campaign has already paid for four times (the withdrawn guidance prescriptions): rejection prose is behaviour, not documentation. Fixed in the schemas, the module comment, the tests and the ledger row; a test now pins the required-key case as loud so the distinction cannot quietly re-blur. Part of objectstack-ai#4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 4, 2026
…#4001 批 15) (objectstack-ai#5043) * feat(spec)!: 收紧 theme/chart 的未知键,并按门测量把 chart 的两个站点留开(objectstack-ai#4001 批 15) theme.zod.ts 14 个站点全部 strictObject;chart.zod.ts 5 个站点收紧, ChartAggregate/ChartGroupBy 两个站点按实测(有承载键但无 parse)留开并记录。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * docs(audits): 台账记录 批 15 的门测量与两张表重算(objectstack-ai#4001) ui/ triage: theme 行改判 authorable+strict;chart 行拆出并记 5 收紧 / 2 no gate。 remaining-strip map: theme 行删除(已关闭),chart 7→2,表头 123→104, 小计由存活行重算 102 of 104。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 * fix(spec): 把 objectstack-ai#3746 陷阱的警告本身移出 doc block,并补 批 15 changeset(objectstack-ai#4001) getFileDescription() 用裸正则在原始源码上找第一个 doc block,所以在 // 行里 引用那个双星开头的字面量也会被匹配成文件的首个 doc block —— 于是这条警告 自己把 theme/chart 两个参考页的描述清空了。check:docs 抓到;改写措辞后 两页与 main 逐字节相同。 同时把 clickAction 的 finding-7 说明从 JSDoc 移到 // 注释:它是给下一个 维护者的话,不是协议文档,不该发布到公开参考页。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 --------- Co-authored-by: Claude <noreply@anthropic.com>
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.
Closes part 3 of #3405 — the item #3406 deferred as「单独评估、必要时拆出去做」。
问题
#3406 / objectui#2786 修的是症状:给内联 record-picker 参数加了
reference键,并让缺目标的参数在解析期报错。导致它的机制原样留着 ——ActionParamSchema是 zod 默认.strip,任何它没声明的键都被静默丢弃,参数照常 parse 通过。作者写了一个语义完全正确、且与
FieldSchema.reference同名的键,得到的唯一反馈是一个要求粘 UUID 的文本框。下一个写错键名的作者会以同样的方式失败,同样毫无声音。这一条不修,#3405 修的就只是一个键,不是那个坑。
改动
ActionParamSchema改为.strict(),并配一个让拒绝可修而不只是大声的 error map:help_text→helpText、default_value→defaultValue)交给共享的findClosestMatches,距离上界沿用data/object.zod.ts里suggestKey的长度相对公式 —— 固定距离 3 会给wibble推荐visible。FIELD_TYPE_ALIASES的风格显式列出:reference_to/referenceTo/targetObject→reference(运行时字段形状是前者,objectui 解析后的参数是后者 —— 正是 动作参数弹窗:内联 lookup 参数无法声明引用目标(配置被静默剥离 + 文案谎报「即将上线」) #3405 那次被丢的两种拼法)visibleWhen/visibleOn/visibility→visible最后这条是这个 PR 超出「防错别字」的价值所在:ADR-0089 把
visibleWhen定为 view/page schema 的正统拼法,在这里借用它,过去会把参数的能力开关整个剥掉,让它无条件渲染 —— 一个静默失效的权限门。遵循 ADR-0078(no-silently-inert-metadata)与 ADR-0049(enforce-or-remove),做法与 ADR-0089 D3a 对 view/page schema 的处理一致。
影响面(issue 里担心的那一点)
issue 原文说这条「影响面比前两条大得多,可能踩到其他既有元数据」。实测本仓零破坏:
@objectstack/spec全量 258 文件 / 6716 用例通过tsc --noEmit干净validate通过 —— 仓库里没有任何既有元数据带着未声明的参数键lint467 例、metadata276 例、metadata-core100 例、platform-objects223 例、metadata-protocol70 例、sdui-parser6 例,全过验证
在 showcase 的内联 picker 参数上实种一个坏键(保留合法的
reference,只加一个visibleWhen,以隔离本 PR 的新行为):去掉后
✓ Validation passed。reference_to同样命中→ reference。破坏性说明
带多余键的既有参数现在会 parse 失败。这正是本 PR 的意图 —— 那些键本来就没有生效,只是坏得没声音。错误信息直接点名该键并给出正确拼法,changeset 里附了 FROM → TO 对照表。
遗留(不在本 PR)
#3405 验收里的 PLAT-DEF-005 真机回归仍卡在发版:npm 上
@objectstack/spec最新是 16.1.0(2026-07-22T01:06Z),早于 #3406 合并(同日 20:46Z)。我拉 16.1.0 的 tarball 确认过,dist/里没有reference的校验。天顺 EHR 那行reference: 'sys_user'至今仍被服务端 strip。#3406 的 changeset 也还挂在.changeset/里未消费 —— 同一个佐证。需要发一次 spec 版本才能收尾。🤖 Generated with Claude Code
https://claude.ai/code/session_01LkvDs4y4gveyB5NJZKxaZt
Generated by Claude Code