Skip to content

feat(objectql,metadata-protocol): refuse a dotted filter key whose head is a relation, a formula, or a plain scalar — at both doors (#8371) - #8936

Merged
os-project-manager merged 8 commits into
mainfrom
claude/issue-8371-filter-dotted-path-verdict
Aug 16, 2026
Merged

feat(objectql,metadata-protocol): refuse a dotted filter key whose head is a relation, a formula, or a plain scalar — at both doors (#8371)#8936
os-project-manager merged 8 commits into
mainfrom
claude/issue-8371-filter-dotted-path-verdict

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes#8371

Implements the maintainer ruling (Option 2, delegated adjudication, comment 5300373151): a type-directed verdict on the head segment of a dotted filter key, at both doors. The measurement phase was already complete (dev report 5299949142) and is built on, not re-run.

The ruled shape, as landed

  • Refused — a dotted filter key whose head field is a relation (lookup / master_detail / user / tree), a virtual formula, or a plain scalar: 400 INVALID_FIELD, naming the whole offending key exactly as the caller wrote it, closing with the SORT sort 的点号路径(?sort=account.company_name)仍然静默降级为「不排序」——#4226 收口后唯一漏网的 sort 形态 #4256 remedy sentence ("Denormalise the value onto '…' (a stored field, written when the source changes) and filter that."). No new mechanism, no new error class.
  • Both doors: the REST ingress (assertFilterFieldsExist, packages/metadata-protocol/src/protocol.ts) and the engine's own filter seam (assertFilterIsMaterializable, packages/objectql/src/filter-comparand-shape.ts) — the same two doors The FILTER axis has no unmaterializable verdict: a where on a virtual formula field returns 0 rows silently, while sort and search refuse the same field with a 400 #8296 wired its virtual verdict into, structure mirrored exactly. Precedence mirrors the sort axis, verdict for verdict: unknown before dotted before unmaterializable.
  • ⛔ Deliberately unjudged, per the ruling: the structured/JSON head ({'address.city': …}) keeps today's behaviour — live on two of three backends, so a refusal would delete a working capability. Array-valued heads (multiple: true, tag types) and file heads are also unjudged, for the same measured fail-open reason the collectors document (a hole, never a false 400). One deliberate reading of the ruling's letter: a multi-valued relation head (lookup with multiple: true) is still refused — the ruling names the relation class flat, and tag_ids.name is the same traversal error whether the head stores one id or many.
  • The nested-relation OBJECT form { owner: { region: 'NA' } } keeps working — the collectors never descend into a field key's value, and the distinction is pinned as a control at both doors.

One shared classification, not two ladders

Both doors judge the head via a new @objectstack/spec/data export, classifyDottedFilterHead (packages/spec/src/data/filter-dotted-head.ts) — the same one-source move #8296 made with isVirtualSearchField, so the doors cannot drift into answering one spelling two ways. The scalar set is derived from the ADR-0104 value-shape classes, never a list minted at a door; a disjointness pin keeps the classification order-independent.

ADR-0087

Semantic entry engine-dotted-filter-refused (protocol major 18), following the sibling precedents (engine-find-formula-filter-refused, engine-dotted-projection-refused). Registry block regenerated with gen:migration-registry; export-origins and api-surface regenerated for the new spec exports. Changeset carries the disposition marker and the FROM → TO prescription.

os-regen relay — PR #8935 landed under this branch

scripts/pm/os-regen-merge.sh sequence run: merge committed first (one hand-resolved conflict in registry.ts — both branches added a semantic:18 entry; resolved by taking main's side and regenerating from the merged entry FILES), then the regen chain (gen:migration-registry → 96 semantic entries, spec build, gen:export-origins, gen:api-surface, gen:spec-changes, gen:upgrade-guide, gen:openapi restore). check:generated: all 13 artifacts up to date. Asserted surviving on the merged tree: #8935's entry filter-preset-ordering-comparand-refused AND its implementation body (isDateRangePresetName in data/date-range-presets.ts), #8932's ApiKeySchema still absent, this branch's entry and classifyDottedFilterHead at all three consuming files.

Out of scope, deliberately

Verification — union re-run after the final commit, at 20e3132, clean tree

Reverse verification (direction decided in advance: refusal pins go red, resolving 200/empty): fix committed first, both door files reverted to their origin/main versions via git checkout origin/main -- packages/metadata-protocol/src/protocol.ts packages/objectql/src/filter-comparand-shape.ts, both packages rebuilt, conformance suite run: exactly the 20 new refusal pins failed (queries resolved with empty lists), all 175 controls and pre-existing pins stayed green — including the OBJECT-form and structured-head controls. Restored from the committed branch state via git checkout of the same two paths (byte-identical, git status clean), rebuilt, re-run green.

Suites at 20e3132, fully built workspace closure (turbo run build --filter=./packages/* --filter=./packages/*/*):

Gates at 20e3132, re-derived from actual changed paths via node scripts/pm/dispatch-gates.mjs, all PASS: check:nul-bytes, check:filter-alias-parity, check:merge-driver, check:objectui-changeset, check:durability-log-level, check:spec-parsed-alias, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:type-source-resolution, check:test-source-alias, check:query-options-erasure, check:type-check-coverage, check:type-check-debt --re-measure (33 entries, 1,926 raw errors, none above recorded; the lint -1 surplus pre-dates this branch — also measured by PR #8933 — and is deliberately not --lowered here to avoid contending that shared ledger across live branches), check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-dev-prereqs, check-engine-split-ratio, check:doc-formula-expressions. Full packages/speccheck:* battery green except check:react-declaration-parity, which needs an objectui sdui manifest this container cannot produce (needs the vendored console build + a browser) — environmental, unrelated to this diff; CI's own TypeScript job (which covers the spec surface gates) is green.

Beyond the dispatch prompt's gate list, the re-derivation added check:objectui-changeset, check-changeset-no-major, check-empty-changeset, check-dev-prereqs, check-engine-split-ratio, check:durability-log-level, check:spec-parsed-alias, check:doc-formula-expressions and (post-CI-red) check:api-surface — all run.

Generated by Claude Code

…ad is a relation, a formula, or a plain scalar (#8371)
Implements the #8371 maintainer ruling (Option 2, delegated adjudication):
a dotted filter key whose head segment is a relation, a virtual formula, or
a plain scalar is refused at BOTH doors — the REST ingress
(assertFilterFieldsExist) and the engine's filter seam
(assertFilterIsMaterializable) — with the existing INVALID_FIELD/400
identity and the SORT #4256 denormalise remedy sentence. The
structured/JSON head (address.city) stays deliberately unjudged, per the
ruling's carve-out: it is live on two of three backends.
Both doors judge the head by one shared classification
(classifyDottedFilterHead, new @objectstack/spec/data export), the same
one-source move #8296 made with isVirtualSearchField. Precedence mirrors
the sort axis: unknown > dotted > unmaterializable.
ADR-0087: semantic entry engine-dotted-filter-refused (protocol major 18).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
@vercel

vercelBot commented Aug 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 16, 2026 1:29am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx(via @objectstack/objectql)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/deployment/vercel.mdx(via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via packages/objectql, @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/system-context.mdx(via packages/objectql, packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/metadata-protocol, @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 15, 2026
…#8371 verdict
The GUARD pinned exactly the limb the ruling deletes ('a dotted path on a
REAL head passes through — nothing may refuse it after'). owner_id is a
registry-injected lookup, so 'owner_id.name' is now refused with the whole
key named; the surviving guarded properties (nested-relation OBJECT form
not descended into, unknown-head precedence) stay pinned by their own
tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
check:api-surface red at fd8f1eb: DottedFilterHeadClass, DottedFilterHeadMeta,
SCALAR_FILTER_HEAD_TYPES and classifyDottedFilterHead are intentional
additions (the shared dotted-head classification both filter doors read);
gen:api-surface run on a freshly built spec.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
…relay)
gen:migration-registry (96 semantic — both semantic:18 entries stack:
engine-dotted-filter-refused + filter-preset-ordering-comparand-refused),
spec build, gen:export-origins, gen:api-surface, gen:spec-changes,
gen:upgrade-guide, gen:openapi restore. check:generated: all 13 artifacts
up to date. Survival asserted on the merged tree: #8935's entry and its
implementation body (isDateRangePresetName), #8932's ApiKeySchema still
absent, this branch's entry and classifyDottedFilterHead at all three
consuming files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
…8939 relay)
gen:migration-registry, spec build, gen:export-origins, gen:api-surface,
gen:spec-changes, gen:upgrade-guide, gen:openapi restore. check:generated:
all 13 artifacts up to date. Survival asserted on the merged tree, one hit
each in the regenerated registry: engine-dotted-filter-refused (this
branch), driver-sql-unresolvable-where-column-refused (#8927),
filter-preset-ordering-comparand-refused (#8935),
identity-api-key-schema-retired (#8932) - plus implementation bodies:
sql-driver.ts INVALID_FIELD refusal + envelope test (#8927),
isDateRangePresetName (#8935), ApiKeySchema still absent (#8932),
classifyDottedFilterHead at both doors (this branch).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fgvh1iEJfxetei7aNVdtJt
@os-project-manager
os-project-manager marked this pull request as ready for review August 16, 2026 01:48
@os-project-manager
os-project-manager added this pull request to the merge queueAug 16, 2026
Merged via the queue into main with commit a8189aeAug 16, 2026
28 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-8371-filter-dotted-path-verdict branch August 16, 2026 02:05
hotlong pushed a commit that referenced this pull request Aug 16, 2026
…merge (#8807)
Both entries stack: #8936's engine-dotted-filter-refused and this card's
driver-sql-upsert-cross-row-identity-merge-refused. 97 semantic entries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:datasize/lteststooling

Projects

None yet

2 participants

@os-project-manager@claude