Repository navigation
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
Activity
Triage: lands in
packages/spec(migrations/entries/semantic/,registry.ts+ two regenerated artifacts) ⇒domain:spec, type Task,pm:blockedwithBlocked-by: #8296added 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
Unlock scan:
Blocked-by: #8296cleared — #8296 closed (PR #8369 merged; both doors now refuse). Premise re-verified on the merged ref:packages/spec/src/migrations/entries/semantic/carries17.engine-find-formula-order-by-refused.tsand no FILTER-axis sibling, so the asymmetry stands. Back topm: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
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, regeneratedpackages/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 touchespackages/spec/src/migrations/**; blocker #8296 closed (PR #8369 merged, verified: its changeset was consumed by the v17.0.0 version cut24c1b91, so the entry belongs under major 17)
Generated by Claude Code
{ "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
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-refusedlands 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.jsonand the upgrade guide are script-generated, never hand-edited; the changeset is an@objectstack/specpatch 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_TYPESisformula-only;plugin-reportsforwardsquery.filterverbatim 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 "rungen: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/mainand are not caused by this diff:check:changeset-gate-self-tests— finding:check-changeset-no-major --self-testgoes red repo-wide after a release exits pre mode — its control requires major-declaring changesets AND apre.jsonthat no longer exists #8654;check-adr-0087-registrationinput assertion — check-adr-0087-registration is red on every PR after a release cut — its input assertion needs a breaking changeset in the real stock (sibling of #8654) #8658 (filed from this card, with the measurement that it does NOT self-heal when the stock refills with non-breaking changesets).
Release condition for flipping ready → merge queue:
Check Changesetgreen at this PR's head — either the gates are repaired per #8654/#8658, or a declared-breaking changeset enters.changesetstock (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
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 28, 2026
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 — topackages/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
whereon aformulafield is now400 INVALID_FIELDinstead 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
formulafield 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.tspackages/spec/src/migrations/registry.tsspec-changes.jsonand the generateddocs/protocol-upgrade-guide.mdThe 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(awherenaming aformulafield, at both doors),replacement(denormalise onto a stored field, written when the source changes, and filter that;summary/autonumberneed no action), andacceptanceCriteria(grep saved reports, flows, dashboards and view filters for a filtered field whose object declares it as aformula).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_metadatarow for the D2 chain to rewrite, so the ledger IS the notification.objectstack migrate meta,spec-changes.jsonand 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/specchanges to the spec seat, and adding an entry means touchingentries/semantic/,registry.tsand two regenerated artifacts. The changeset there is thereforeminorfor 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.findhas already appliedlimit/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.