Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

clientValidation.ts: three metadata types opt out of client-side Zod validation on premises that are stale or name the wrong schema (sharing_rule / translation / connector) #3561

Closed

Description

@hotlong

Found while running the read-only two-repo metadata-coverage audit for objectstack#6234 / objectstack#2657. Not fixed there (audit card, no code PR). Filed unassigned.

packages/app-shell/src/views/metadata-admin/clientValidation.ts keeps a documented list of metadata types that deliberately fall through to server-only validation (:77-92). Three of those opt-outs rest on a stated reason that does not hold against framework origin/main (objectstack @ 8e2bbba24f76d56daad166b6b776c69d8c3ac0dc).

1. sharing_rule — "declared but has empty shape"

clientValidation.ts:88:

sharing_rule: SharingRuleSchema is declared but has empty shape — server-only.

Against framework main, SharingRuleSchema is CriteriaSharingRuleSchema (packages/spec/src/security/sharing.zod.ts:217), i.e. BaseSharingRuleSchema.extend({ type, condition }) where the base (:148-171) is a .strict() object declaring name / label / description / object / active / accessLevel / sharedWith{type,value}, with a curated unknown-key error map. Not an empty shape.

2. translation — judged by the wrong schema

clientValidation.ts:89:

translation: TranslationBundleSchema is z.object({}) — accepts anything; server-only.

TranslationBundleSchema is indeed unsuitable — but it is not this kind's schema. It is z.record(LocaleSchema, TranslationDataSchema) (packages/spec/src/system/translation.zod.ts:626), the bundle shape. The translation metadata kind resolves TranslationItemSchema (packages/spec/src/kernel/metadata-type-schemas.ts:130), a strictObject with locale / name / label plus the full translation-data shape (translation.zod.ts:737-762), complete with alias and guidance maps.

This is the same class of error the file itself documents having already paid for once, forty lines below, for email_template (clientValidation.ts:130-139): the validator named a schema that was not the one BUILTIN_METADATA_TYPE_SCHEMAS binds, so drafts were checked against the wrong contract.

3. connector — the named blocker is not in the schema, and the schema named is not the authoring shape

clientValidation.ts:90-92:

connector: ConnectorSchema requires an id field that's not in the on-disk metadata shape — the spec models the runtime connector instance, not the file. Wiring it would flag every valid connector definition.

Two problems. ConnectorSchema (packages/spec/src/integration/connector.zod.ts:648) opens with name, label, type — there is no required id. And the shape a stack actually declares under connectors: is DeclarativeConnectorEntrySchema (connector.zod.ts:843, stack.zod.ts:477), which is ConnectorSchema.superRefine(...) carrying the ADR-0097 authoring rules — that is the schema a connector draft should be checked against, and it is exactly the one that would catch the authoring mistakes the ADR-0097 rules exist to catch (inlined secrets, providerConfig without a provider, authored actions on a provider-bound instance).

Why this is worth fixing rather than re-wording

The comment block is not decoration: it is the recorded justification for why a metadata type gets no live validation, and the next reader trusts it instead of re-checking. Two of the three reasons are false and the third names a schema that was never the authoring contract, so all three types silently cost the author a save round-trip to learn about a typo — on connector and sharing_rule specifically, where the authoring rules are security-shaped (ADR-0097 credential handling; the sharing criteria whose least-validating write path objectstack#3896 had to harden after it could seed a permissive match-all).

Verification boundary

Read against framework origin/main. packages/app-shell/package.json pins @objectstack/spec: ^17.0.0-rc.2, so whoever fixes this should re-verify each schema against the resolved published version before wiring a loader — some of these shapes moved during 17.x, which is plausibly how at least the sharing_rule note became stale in the first place. webhook is the worked precedent for the wiring itself (clientValidation.ts:124).

Suggested scope

Point the three loaders at the schemas BUILTIN_METADATA_TYPE_SCHEMAS / stack.zod.ts actually bind (SharingRuleSchema, TranslationItemSchema, DeclarativeConnectorEntrySchema), or — where a shape genuinely still cannot be used client-side — replace the stale reason with the real one.

Filed per AGENTS.md Prime Directive #10 while auditing for objectstack#6234. Not assigned; no PR.

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: finding. ⛔ No domain:* — objectui is a whole-repo seat and carries no domain routing.

    Transfer check — stays in objectui

    The landing site is packages/app-shell/src/views/metadata-admin/clientValidation.ts (:77-92, plus the three loaders). It reads framework schemas but changes none of them, so the "shared contract surfaces have one owner" transfer does not fire, and it is neither feature-level nor cross-repo. It stays on the objectui queue.

    Why finding rather than pm:queue

    Same grade as its sibling from the same sweep (objectstack#6242), and for the same reasons the body itself supplies: it is filed as an audit card with no code PR, and the harm is a missing client-side pre-check rather than a wrong behaviour. Server-side validation still runs and still rejects the bad draft; the cost is a save round-trip, and the drift is in three comment blocks that are stale rather than in code that misbehaves. That is observation-class.

    The part that keeps it open rather than closable is real and worth restating: those comments are the recorded justification for why three metadata types get no live validation, the next reader trusts them instead of re-checking, and two of the three reasons are false against framework main —

    • sharing_rule: "declared but has empty shape" — SharingRuleSchema is CriteriaSharingRuleSchema (packages/spec/src/security/sharing.zod.ts:217) over a .strict() base declaring seven fields;
    • translation: judged by TranslationBundleSchema (z.record(LocaleSchema, TranslationDataSchema)), which is the bundle shape, while the kind resolves TranslationItemSchema (kernel/metadata-type-schemas.ts:130);
    • connector: the named blocker (a required id) is not in ConnectorSchema, and the authoring shape is DeclarativeConnectorEntrySchema (connector.zod.ts:843) carrying the ADR-0097 rules.

    The body's own precedent is the strongest argument for eventual promotion: email_template (clientValidation.ts:130-139) is the same error already paid for once in this file — a validator pointed at a schema BUILTIN_METADATA_TYPE_SCHEMAS does not bind.

    What has to happen before it is dispatchable

    The body states the boundary itself, and it is the reason this is not queue-ready as written: packages/app-shell/package.json pins @objectstack/spec: ^17.0.0-rc.2, while every schema reading above was taken from framework origin/main. Some of these shapes moved during 17.x — plausibly how the sharing_rule note went stale in the first place. So each of the three has to be re-verified against the resolved published version before a loader is wired, and until that is done the acceptance criteria cannot be written. webhook (clientValidation.ts:124) is the worked precedent for the wiring once they are.

    A findings triage round should promote this once someone confirms the three schemas against the pinned release — that check is small, and it is the whole distance between this card and pm:queue.

    Dedup (three repos, open issues and PRs)

    ⚠️ The GitHub search API is not reachable from this seat (/search/issues answers "sessions are bound to their configured repositories", returning a structurally empty result rather than an error). Dedup was done by paginating all 442 open issues and PRs across the three repos and matching locally on clientValidation, TranslationBundleSchema, DeclarativeConnectorEntrySchema, SharingRuleSchema.

    No duplicate. Two neighbours, both distinct: objectstack#5316 (metadata-admin validating a stored view against the authoring schema — the opposite direction: a validator that runs and is too strict, where this is validators that never run) and objectstack#2657 (the Studio coverage programme this audit feeds, pm:queue + pm:blocked, domain:spec). Its sibling card from the same sweep is objectstack#6242, also graded finding this round.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    Triage (maintainer-authorized one-off pass, 2026-08-09, registered on objectstack#6015): promoted finding → pm:queue. Three client-validation opt-outs resting on false or wrong-schema premises, two of them on security-shaped types (sharing_rule, connector with the ADR-0097 credential rules), and the file itself records having paid for this exact error class once already (email_template). Deliverable per the filing: point the three loaders at the schemas the platform actually binds (SharingRuleSchema, TranslationItemSchema, DeclarativeConnectorEntrySchema), re-verifying each against the resolved published spec pin before wiring; webhook is the worked precedent.


    Generated by Claude Code

  3. self-assigned this
    on Aug 10, 2026
  4. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Contributor

    Claim: PM loop round 5 (objectui whole-repo seat, non-v17 queue wave)
    Session: session_017Qqyix2QcnpUC9XeYVDzx3
    Branch: claude/issue-3561-clientvalidation-optouts
    Worktree: objectui-issue-3561
    File surface: packages/app-shell/src/views/metadata-admin/clientValidation.ts + its tests. Serial constraints: disjoint from the other four dispatches of this batch (no other dispatch touches app-shell).
    Container & model: M, mode:subagent, model: opus

    Scope note from the card, binding: every schema claim must be re-verified against the resolved published spec version the workspace actually installs (package.json pins ^17.0.0-rc.2; today's resolve is rc.5), not against framework origin/main — the card's own verification boundary. webhook (clientValidation.ts:124) is the worked wiring precedent. Where a shape genuinely cannot be used client-side on the resolved version, the honest outcome is a corrected reason, not a forced wiring.


    Generated by Claude Code

  5. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Contributor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4107.

    Per-type outcomes, each premise re-verified on the RESOLVED published spec (rc.5) as the card's verification boundary demanded: translation and connector wired on both doors (TranslationItemSchema — the retired note was wrong twice, the bundle schema isn't the kind's schema AND isn't lenient; DeclarativeConnectorEntrySchema with its ADR-0097 superRefine rules — the claimed required id never existed). sharing_rule wired on the CREATE door only — and that split is the judgment call that earned this acceptance: it is the sole wired schema declaring none of the 7 ADR-0010 envelope keys while the metadata read path stamps _packageId type-blind, so a both-doors wiring would 422 stored bodies the platform itself wrote — the objectstack#5316 inversion, independently reached by framework's own #6245 comment. The envelope gap is filed upstream as objectstack#7404 (observation-class, honestly caveated: the echo-back round trip was not executed). If the envelope is later declared, the edit door opens with one set change.

    Also right: hasClientValidator gained a mode argument because ResourceEditPage uses it to pick the diagnostics SOURCE — a mode-blind answer would have suppressed the server's own _diagnostics on the gate-less edit path. Three reverse verifications, each with the red set predicted exactly beforehand; one pin honestly downgraded to non-discriminating in the report rather than smoothed over. Binding-parity safeguards adapted to published-spec reality (structural parity + superRefine count, since UNREGISTERED_KIND_SCHEMAS isn't in the release and cross-bundle === is vacuous) — the safeguard's intent preserved, its mechanism corrected, with the reasoning on the record.

    Card citation note for the archive: the card's ^17.0.0-rc.2 pin reference had already moved to ^17.0.0-rc.5 on main; resolution unaffected. Flipping ready + auto-merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions