Skip to content

The FILTER-axis formula refusal (#8296) shipped without the ADR-0087 semantic entry its SORT-axis twin (#7095) carries — the upgrade guide will not mention it #8370

Description

@os-zhuang

Filed from PR #8369 (#8296). Unassigned; the fix lands in packages/spec, which that card's dispatch deliberately fenced off ("if implementation requires a change — not just a read — to packages/spec, stop and route that slice to the spec seat").

Blocked-by: #8296

The asymmetry

PR #8369 gives the FILTER axis its unmaterializable verdict at both doors: a where on a formula field is now 400 INVALID_FIELD instead of 200-with-zero-rows, at the REST ingress and at the engine seam that saved reports / flows / dashboards reach directly.

Its SORT-axis twin, #7095, made exactly the same kind of change — an engine-boundary refusal for a formula field that used to answer 200 — and paired it with an ADR-0087 semantic migration entry:

  • packages/spec/src/migrations/entries/semantic/17.engine-find-formula-order-by-refused.ts
  • registered in packages/spec/src/migrations/registry.ts
  • surfaced through spec-changes.json and the generated docs/protocol-upgrade-guide.md

The FILTER refusal has no such entry. Its entry would be id: engine-find-formula-filter-refused (or similar) with the same three fields the sort entry carries — surface (a where naming a formula field, at both doors), replacement (denormalise onto a stored field, written when the source changes, and filter that; summary / autonumber need no action), and acceptanceCriteria (grep saved reports, flows, dashboards and view filters for a filtered field whose object declares it as a formula).

Why it matters

That entry is the only notification channel for this class. The sort entry says so in as many words: this is a code-path API with no sys_metadata row for the D2 chain to rewrite, so the ledger IS the notification. objectstack migrate meta, spec-changes.json and the upgrade guide all read the registries, so an unregistered behaviour change is a silent gap on the upgrade path — the #6148 shape.

It matters slightly MORE here than on the sort axis: a refused sort returns the same rows in a different order, while a refused filter changes the row SET, and the surfaces that break are author-written (sys_saved_report.query.filter, flow node filters, dashboard widget filters), not code.

Why PR #8369 did not include it

Not an oversight and not a judgement that it is unnecessary — the dispatch fenced packages/spec changes to the spec seat, and adding an entry means touching entries/semantic/, registry.ts and two regenerated artifacts. The changeset there is therefore minor for both packages with no **BREAKING** declaration (the launch-window convention, and what #6994 — the same refusal class at ingress — shipped as), so the ADR-0087 changeset gate is not triggered and nothing currently asks the question.

Not for this issue to decide

Whether the entry is genuinely required, or whether not-required (no-migration-prescription) is the honest disposition, is a real question: no key, symbol or stored value moves, and no mechanical rewrite exists in either direction (the platform cannot invent the stored column, and it must not filter post-hoc — driver.find has already applied limit / offset, so a post-hoc predicate would filter an arbitrary PAGE). The sort axis answered "register it anyway" for the identical shape, which is the strongest available precedent.

Related: #8299 (the disposition catalog has no category for published runtime surfaces with no metadata surface) is the general form of the same question.

Activity

  1. added theissue type on Aug 13, 2026
  2. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Triage: lands in packages/spec (migrations/entries/semantic/, registry.ts + two regenerated artifacts) ⇒ domain:spec, type Task, pm:blocked with Blocked-by: #8296 added to the body (the entry documents behaviour that ships with PR #8369; it must land after that PR merges, and the entry's wording should be written against the merged behaviour). Rationale on the open question ("register vs not-required"): the sort-axis twin #7095 answered "register it anyway" for the identical shape, and this card itself establishes the filter case matters MORE (row-set change, author-written surfaces). Per the same-family rule, the sibling ruling is inherited — this does not enter the decision inbox; a dev who finds a real semantic difference from the sort precedent should report the fork rather than push through. The general disposition-catalog gap stays #8299.


    Generated by Claude Code

  3. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Unlock scan: Blocked-by: #8296 cleared — #8296 closed (PR #8369 merged; both doors now refuse). Premise re-verified on the merged ref: packages/spec/src/migrations/entries/semantic/ carries 17.engine-find-formula-order-by-refused.ts and no FILTER-axis sibling, so the asymmetry stands. Back to pm:queue.

    Size/model suggestion: S; registry-entry-only (documents an already-landed semantic change, no acceptance-behavior change of its own) — the spec seat decides the tier per the standing clause.

    本评论来自分诊座位 Routine。


    Generated by Claude Code

  4. self-assigned this
    on Aug 14, 2026
  5. os-zhuang commented on Aug 14, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01E5tUwGM3LQoqErTfkvRW7W
    Branch: claude/issue-8370-filter-refusal-adr0087-entry
    Worktree: objectstack-issue-8370
    Domain: domain:spec
    File surface: packages/spec/src/migrations/entries/semantic/ (new entry), packages/spec/src/migrations/registry.ts, regenerated packages/spec/spec-changes.json + docs/protocol-upgrade-guide.md, changeset (stop on breach; explain in the report)
    Container & model: S registry-entry-only, mode:subagent, model: opus (records an already-shipped ruling; no acceptance-behaviour change of its own — the fable hard clause does not bind; triage suggestion S adopted)
    Serial constraints cleared: none — no in-flight claim or open PR touches packages/spec/src/migrations/**; blocker #8296 closed (PR #8369 merged, verified: its changeset was consumed by the v17.0.0 version cut 24c1b91, so the entry belongs under major 17)


    Generated by Claude Code

  6. os-zhuang commented on Aug 14, 2026

    @os-zhuang
    ContributorAuthor
    {
      "issue": 8370,
      "status": "done",
      "branch": "claude/issue-8370-filter-refusal-adr0087-entry",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8657",
      "premise_still_valid": true,
      "summary": "Registered the FILTER-axis formula refusal as ADR-0087 semantic entry `engine-find-formula-filter-refused` under protocol 17, beside its SORT-axis twin. The entry is a NEW FILE under `packages/spec/src/migrations/entries/semantic/` — registry.ts's semantic lists are generated (#7297), so `registry.ts`, `spec-changes.json` and `docs/protocol-upgrade-guide.md` were all produced by `gen:migration-registry` / `gen:spec-changes` / `gen:upgrade-guide` and none was hand-edited (this corrects the dispatch prompt's 'register in registry.ts' assumption). Content was written against PR #8369's merged code, not this card's prose: both doors (`assertFilterFieldsExist` at the REST ingress, `assertFilterIsMaterializable` at the engine seam covering find/findOne/count/aggregate/update/delete) answer `400 INVALID_FIELD`, judged by `isVirtualSearchField` / `SEARCH_VIRTUAL_TYPES` — verified `formula`-only, which is what keeps `summary` and `autonumber` out of the refusal — and the author-reachable bypass was verified in-tree (`plugin-reports/src/report-service.ts`, `where: q.filter`). No semantic difference from the #7095 precedent was found, so no fork to report; changeset is `@objectstack/spec` patch carrying `registered engine-find-formula-filter-refused`. Nothing under `content/docs/releases/**` was touched; the v17 release page does not mention this refusal, which is the release seat's call, not filed and not edited.",
      "tests": "Union re-run AFTER the final commit, at HEAD 3c1b2ada0 (includes the merge of current origin/main 189a73225). GREEN, exit 0 each: check:nul-bytes (5713 files, no control bytes) · check-empty-changeset · check-changeset-no-major · check:merge-driver · check:spec-parsed-alias · check:type-source-resolution · check:objectui-changeset · spec check:migration-registry ('registry.ts is current (83 semantic, 29 retired-key, 53 retired-def)') · spec check:spec-changes ('spec-changes.json is up to date') · spec check:upgrade-guide ('protocol-upgrade-guide.md is up to date') · spec check:generated ('All 13 generated artifacts are up to date') · lint check:doc-formula-expressions. Suite: `pnpm --filter @objectstack/spec test` = 397 files / 10527 tests passed (D3 chain replay included), `pnpm --filter @objectstack/spec typecheck` clean. Build closure first: `pnpm --filter '@objectstack/formula...' build` (a bare `spec^...` build is NOT enough — formula's dts needs spec's own dist, and that is what the first red was). RED, all three reproduced on pristine origin/main with this branch absent, so none is caused by this diff: check:changeset-gate-self-tests (exit 1 at origin/main 56724c500 — filed as #8654) · check-adr-0087-registration (exit 1, input assertion 'not one of 3 changeset(s) in stock matches the breaking-change detector'; the sibling gate #8654 does not cover — filed as #8658) · check:dev-prereqs (exit 1, 'The workspace is not built', 67 packages — CI builds first). Gates re-derived with `node scripts/pm/dispatch-gates.mjs` on the actual changed paths; it added three the prompt did not name — check:objectui-changeset, check-changeset-no-major.mjs, check-empty-changeset.mjs — all run and green. No dogfood ablation in this card (data-only registration).",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #8658: check-adr-0087-registration exits 1 on every PR after a release cut — its input assertion requires a breaking changeset in the real .changeset stock, which the version cut consumes; sibling of #8654 (different script, different CI job, same root condition), does not self-heal when the stock refills with non-breaking changesets",
        "not filed, already open as #8654: check:changeset-gate-self-tests red repo-wide for the same post-cut condition — this run adds a datapoint (red at origin/main 56724c500, this branch absent)"
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  7. os-zhuang commented on Aug 14, 2026

    @os-zhuang
    ContributorAuthor

    ACCEPT (spec seat, session session_01E5tUwGM3LQoqErTfkvRW7W) — PR #8657.

    Verified against GitHub, not the report's own claims: 5 changed files, all inside the claimed surface; the entry engine-find-formula-filter-refused lands under protocol major 17 beside its SORT twin (correct — the refusal's changeset was consumed by the v17.0.0 cut, so the behaviour shipped in 17.0.0); registry.ts, spec-changes.json and the upgrade guide are script-generated, never hand-edited; the changeset is an @objectstack/spec patch carrying the ADR-0087 disposition marker. Entry prose is written against the merged behaviour with in-tree verification of the two claims that matter (SEARCH_VIRTUAL_TYPES is formula-only; plugin-reports forwards query.filter verbatim past the ingress). No fork from the #7095 precedent found, matching the inherited ruling. One dispatch-prompt assumption was falsified and corrected on the record: the registry's semantic lists are generated (#7297), so "register in registry.ts" meant "run gen:migration-registry" — good falsification, no harm done.

    Landing state: draft-parked with a signature-level expected-red list (in the PR body). The two red checks reproduce on pristine origin/main and are not caused by this diff:

    Release condition for flipping ready → merge queue: Check Changeset green at this PR's head — either the gates are repaired per #8654/#8658, or a declared-breaking changeset enters .changeset stock (note: in-flight #8495 is acceptance-narrowing and may do exactly that when it lands). This seat re-checks on each CI event and on patrol; the PR stays visibly parked, not forgotten.


    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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions