Repository navigation
Seam: IObjectQLEngine gains an optional judge-only filter-admission method, run by the engine's own admission pipeline without executing (objectstack#19995 ruling C) #20157
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsSerial note from
domain:specseat 4 (session_01CiCTczDo7tGhafXjf61dUJ, seat post #18917), 2026-09-27T04:17Z. ⛔ Not a claim; the state is unchanged (pm:queue).Reached in this seat's round 1 take order (a contract-face card) and passed over because of a hot region.
- The collision. Item 2 of this card factors the engine's admission doors so that execution and the new judge-only member call the same functions. The
domain:engineclaim on objectql + REST: the per-aggregationfilterstill lacks four ofwhere's doors — a bad date, anaddDaysnumeric pair, an undeclared{ $field }and an unknown key answer200with every count 0 #20148 (session_01Bvd69VPa6puiNzzPUroDBx, claimed today) is editing the same door invocation inpackages/objectql/src/engine.ts. It runswhere's remaining doors (the temporal-comparand door, the declared-field check on a{ $field }referent, theaddDaysclass rule) on eachaggregations[i].filterinsideObjectQL.aggregate. Two devs would be refactoring one door list at the same time. - fold-or-serial: SERIAL. Gate ① fails. objectql + REST: the per-aggregation
filterstill lacks four ofwhere's doors — a bad date, anaddDaysnumeric pair, an undeclared{ $field }and an unknown key answer200with every count 0 #20148 extends admission to a new filter slot; this card adds a non-executing judge overwhere. Same file and same doors, but a different defect shape. - For whoever takes this after objectql + REST: the per-aggregation
filterstill lacks four ofwhere's doors — a bad date, anaddDaysnumeric pair, an undeclared{ $field }and an unknown key answer200with every count 0 #20148 lands: read objectql + REST: the per-aggregationfilterstill lacks four ofwhere's doors — a bad date, anaddDaysnumeric pair, an undeclared{ $field }and an unknown key answer200with every count 0 #20148's merged diff first. If it factors a shared "run everywheredoor" function, the judge calls that function, ⛔ not a second copy. Re-measure the card's premise that the door walks are unreachable through the packageexportson the newmain. - Cross-lane. The implementation half lands in
packages/objectql(domain:engine). The claim must declare that surface and name the engine seat's live claims onengine.tsin its serial line.
Readings:
git greponorigin/main2bbebf5— the door helpers are imported atengine.ts:45and called at:980,:1074and, in the aggregation loop,:16137. The #20148 claim's file surface is its ownClaim:comment.- The collision. Item 2 of this card factors the engine's admission doors so that execution and the new judge-only member call the same functions. The
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01CiCTczDo7tGhafXjf61dUJ
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-20157-engine-judge-filter-admission
Worktree:objectstack-issue-20157
Domain:domain:spec
Seat:domain:spec#4(seat post #18917)
File surface: the contract member inpackages/spec/src/contracts/objectql-engine.ts(one OPTIONAL judge-only member onIObjectQLEngine, plus its docblock) and its tests; the implementation inpackages/objectql/src/— thewhereadmission doors refactored so that execution and the judge call the same functions (engine.tsin itsfind/where-admission path and the module-level door functions;filter-comparand-shape.ts,temporal-comparand-door.tsand the text-operator door file only where the refactor must touch them), with their tests; the generated artefacts that follow (api-surface, export-origins), regenerated and never hand-edited;.changeset/. Cross-lane (packages/objectqlisdomain:engine): declared here because this is aSeam:card that goes vertically to the spec seat. ⛔ NotObjectQL.aggregate's per-aggregation filter loop, which the livedomain:engineclaim on #20176 holds. (stop on breach; explain in the report)
Container & model:M/L,mode:subagent,model: default judgment tier(the default slot is taken: a new public contract member plus a refactor that must keep execution byte-identical). The review of a widened contract runs atCONTRACT_REVIEW_TIERbefore enqueue.
Clause-②: yes
Thread-read: 5852563458
Serial constraints cleared: read at 2026-09-27T07:25Z. The serial predecessor #20148 landed ascfe2387a3(PR #20174:engine.ts+29 lines,having-filter.ts+284). It added no shared "run everywheredoor" function, so this card builds one; it does not reuse one. Open-PR census (15 open PRs besides the release PR, 303 file rows, lit control 158packages/rows): none touchespackages/objectql/src/engine.ts, the door files orcontracts/objectql-engine.ts. In-flight claims:domain:engine#20176 holds the per-aggregation loop ofengine.ts, a different region. Downstream: #19995 and #20158 carryBlocked-by: #20157.Ruling carried: #19995's ruling C (batch #225 item 3, 「同意」), as the card body records it. The engine becomes the single judge of 「can this filter run against this object」 for callers that must know before composing a policy scope. Its Governing text is also in the body.
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20157,
"status": "done",
"branch": "claude/issue-20157-engine-judge-filter-admission",
"pr": "#20213",
"session": "session_01CiCTczDo7tGhafXjf61dUJ — the harness-stamped id in this run's commit trailers (Claude-Session line); a subagent run carries the parent's",
"premise_still_valid": true,
"summary": "IObjectQLEngine gains one OPTIONAL member, judgeFilter(objectName, where, { operation?, context? }), which is synchronous and returns { ok: true } | { ok: false, code, status, message }. ObjectQL implements it through judgeWhereAdmission. That function calls the two stage functions every where-taking verb already calls, in the same order: lowerWhereFilterArray (shape gate, list-comparand shape, materializable dotted then virtual, text operator, temporal, comparand type; array form through parseFilterAST) and then resolveWhereFilterTokens. resolveWhereFilterTokens is now the single spelling behind ObjectQL.resolveWhereTokens and ObjectQL.withResolvedWhere. The judge stops before getDriver and makes no driver, hook or middleware call. Premise 1 held door by door: each door reads only the where value, the registry field map and, for placeholders, the context. PM assumption 2 held: the where sequence is identical on all six verbs, and what differs by verb (getDriver position, option folding, orderBy/projection, per-aggregation/having) is not a where door. The aggregate per-aggregation loop is untouched. Assumption 3, placeholder semantics: the judge expands placeholders against the supplied context. Evidence: #19995's consumer already calls assertReadScopePlaceholdersResolvable(scope, objectName, ctx.context), which runs the engine resolver against the context it forwards to executeAggregate. An unanswerable placeholder answers FILTER_TOKEN_UNRESOLVED/400 and is never read as null (pinned). Assumption 4 held: objectql exports are unchanged ('.' and './core'). The judge is reached through the engine instance; spec/contracts gains two type exports (EngineFilterJudgement, EngineFilterJudgementOptions), with api-surface and export-origins regenerated. Consumers are not wired. The changeset is minor for spec and objectql, with Clause-②: yes in the changeset and on its own line in the PR body.",
"tests": "Final head c797375. New pins: objectql src/engine-judge-filter.test.ts 43/43 (5 classes x 6 verbs; judge and execution agree on code+status+full message; ok path; driver spy zero calls, getDriver not called, hook+middleware not run, each with a positive control; two-defect order; placeholder context; unregistered object). Spec src/contracts/objectql-engine.test.ts 12/12 (5 new type pins). Full suites at 30dadfb (only delta to c797375 is one docblock's wording): objectql 317 files/5671 passed, with existing door tests unmodified; spec 541/15901 passed, 2 todo. Consumers (downstream importers of IObjectQLEngine) at 30dadfb: service-analytics 128/3017; plugin-security 136/2740; metadata-protocol 189 passed+3 skipped files/2700 passed+19 skipped; core 53/1358; rest 197/3339+1 skipped; runtime 279/3906+1 skipped; platform-objects 55/911; plugin-approvals 51/791; plugin-pinyin-search 2/21; plugin-sharing 37/913; trigger-record-change 10/101; service-datasource 34/693; plugin-hono-server 27/324; plugin-auth 114/2440; cloud-connection 30/397; cli --project unit 225/3192 (integration tier declared to CI); dogfood typecheck green plus its 5 IObjectQLEngine importers 5/38 (full dogfood declared to CI). Typecheck green: spec, objectql, service-analytics, plugin-security, metadata-protocol, core, rest, runtime. Ablation via scripts/ablation-replace.mjs from committed state, mutation and restore both proven on disk (anchor 1→0, blob changed; restore blob==HEAD, git diff HEAD empty). Deleting the stage-2 call: 7 failed/36 passed, as predicted (unknown-placeholder on 6 verbs plus the unresolved-context pin). Replacing stage 1 with { where }: 30 failed/13 passed. Reverse type verification: operation 'insert' gave TS2322 against the rebuilt spec dist; control leg 0 errors in both new test files under tsconfig.test.json. Lint, narrowed and proven: eslint --format json over the 4 changed .ts files gave 4 files, 0 errors, 0 warnings; --print-config returns a config for all 4 (none ignored); no parserOptions.project, so not type-aware, and the diff cannot move untouched files' verdicts.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 REST writes, all through scripts/pm tools over the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #20213); (2) label-write --assign os-sales → POST /repos//issues/20213/assignees; (3) this os-dev-report → POST /repos//issues/20157/comments. git push is not REST (7 pushes of the one branch, the first an empty-branch probe).",
"open_questions": [
{
"question": "For #20158 (authoring-time admission): the judge expands placeholders against the supplied context, so a policy that contains a context placeholder such as {current_user_id}, judged with no context at save time, answers FILTER_TOKEN_UNRESOLVED. Keep that, or add a mode that refuses only unknown placeholders?",
"options": [
"A keep today's semantics: #20158 passes a representative context, or treats FILTER_TOKEN_UNRESOLVED at save time as its own verdict",
"B add an option (e.g. placeholders: 'classify') that refuses FILTER_TOKEN_UNKNOWN but admits known context placeholders without a value"
],
"recommendation": "A. RLS policies reach #20158 as CEL lowered to a FilterCondition with concrete values, so no measured policy carries a placeholder today. B widens a just-published contract with no call site behind it (startup-stage narrowness), and A is what execution answers. This is not blocking; the contract review may settle it."
}
],
"out_of_scope_findings": [
"carrier: domain:engine seat (the #20148 lane, which measured it in cfe2387's own table) · noted, not filed: where's { $field } declared-referent and addDays class rule are enforced only by driver-sql (driver-memory answers a count), while the per-aggregation filter now refuses them at the engine. The judge is bounded by the engine's own admission, so it answers ok for these; this is stated in the contract docblock and the PR's Acceptance notes. It is a read of a landed measurement, not a new reproduction by this run."
],
"gates": {
"head": "c797375ab",
"derived": 89,
"ran": 89,
"not_measured": 0,
"unrun": 0,
"reconcile": "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack → exit 0 (89 run, 0 NOT-MEASURED, 0 UNRUN)",
"commands": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
"node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
"node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
"pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
"pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
"pnpm --filter @objectstack/spec run check:docs :: exit 0",
"pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
"pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
"pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
"pnpm --filter @objectstack/spec run check:generated :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
"pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
"pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:durability-log-level :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:merge-driver :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:pm-prior-rulings :: exit 0",
"pnpm check:pm-widening-tells :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:spec-parsed-alias :: exit 0",
"pnpm check:stack-collection-maps :: exit 0",
"pnpm check:swallow-census-controls :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
]
},
"line_budget": "n/a: no skills/** or ledgered governed file touched; diff +626/-3 over 7 files, under the 5000-line human-merge threshold",
"files_changed": [
".changeset/20157-engine-judge-filter.md",
"packages/objectql/src/engine-judge-filter.test.ts",
"packages/objectql/src/engine.ts",
"packages/spec/api-surface/contracts.json",
"packages/spec/export-origins/contracts.json",
"packages/spec/src/contracts/objectql-engine.test.ts",
"packages/spec/src/contracts/objectql-engine.ts"
],
"ci": "in_progress at report time (32 check runs on c797375: 12 success, 3 skipped, 17 in_progress, none failed); not awaited, per contract",
"deviations": [
"check:type-check-debt's first run rebuilds the whole package closure itself, outside the verify lock, and my runner's 280 s timeout killed it partway. That left driver-mongodb dist without .d.ts, so the second run exited 3 PREREQUISITE NOT MET. I rebuilt the closure under the lock and re-ran the gate under the lock: exit 0, 4 entries at their numbers. ran.list records that exit 0.",
"Two test suites were chained in one call (service-analytics, plugin-security). The call hit the 600 s tool cap and the harness moved it to the background. I waited in the foreground with tail --pid on the lock-holder pid and read both verdicts (green) before going on.",
"The consumer suites were run at 30dadfb, not the final c797375. The only delta is the wording of one docblock in objectql-engine.ts. The gate union and the new pins were re-run at c797375.",
"origin/main was not merged into the branch. It is behind by commits disjoint from this diff's files, and CI's merge ref tests the combination.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Per AGENTS.md (the repo's instruction, which the reminder defers to), commits carry the model-free pair, and the PR body ends with the session-URL footer.",
"Header conflict, surfaced rather than chosen silently: IObjectQLEngine's header said members beyond IDataEngine are REQUIRED, and its evidence bar asks for an existing cross-package call site. The ruling orders an OPTIONAL member ahead of its consumers. The ruling wins; the header is amended to name this one exception and why."
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsACCEPT — PR #20213 (2026-09-27T10:33Z)
domain:specseat 4 (session_01CiCTczDo7tGhafXjf61dUJ). This is the review of record for the round claimed in5853787627; the dev report is5854899027.Checklist, read on GitHub:
- PR form: draft, base
main, first lineFixes #20157, no other closing keyword. The bare lineClause-②: yesmatches the diff. Assigneeos-sales. - Scope: 7 files (+626 / −3).
- Source changes:
packages/spec/src/contracts/objectql-engine.ts(one optional member, two types, a header paragraph naming the one exception) andpackages/objectql/src/engine.ts(+127 / −2). - Also: two test files, the regenerated api-surface and export-origins for
contracts, and a changeset (minorfor@objectstack/specand@objectstack/objectql). - No consumer is wired, and the objectql entry points are unchanged, as the claim required.
- Source changes:
- At-tier contract review: PASS, record
5855050723on the PR, at headc797375ab2.- Execution is byte-identical. The only removed lines are the two spellings now routed through
resolveWhereFilterTokens, whose body is the same expression. A 102-pair probe (17 cases × 6 verbs, base vs head) matched onok, code, status, message, driver calls and the where the driver saw. - The judge runs the two stage functions every verb runs, in the same order, and stops before
getDriver: 0 driver calls on all 102 pairs, with spy pins that have positive controls. - A placeholder is expanded against the supplied context and never read as
null. This is the same answer security: the analytics ObjectQL execute face answers a row-level read scope it cannot run withINVALID_FILTER/ 400 whose message echoes the policy's field name and comparands — the disclosure #5367 closed for the native / echo faces #19995's consumer already computes throughassertReadScopePlaceholdersResolvable. - The verdict message is not redacted, and the contract says so. Redaction stays the consumer's job, per the analytics dataset 路由的 message 正则兜底没有退休时间表:六族拒收仍靠措辞分类,改一个字就换一个 HTTP 码 #5367 ruling and engine decision
5823452098.
- Execution is byte-identical. The only removed lines are the two spellings now routed through
- CI at
c797375ab2: 32 success, 3 skipped (Build Docs, Console Pin Gate, the opt-in tarball smoke; all path-filtered or opt-in). No failures. - Merge:
mergeable_state: clean. A no-drivermerge-treeonto today'smain(585c9af587) exits 0. - Governed surface: 0 of 7 paths; 629 changed lines.
- Dev evidence: 89 of 89 derived gates exit 0. Two ablations failed exactly the predicted pins (stage 2 removed: 7 failed; stage 1 replaced: 30 failed). Consumer suites in 16 importing packages are green.
The dev's open question (placeholder mode for #20158): A, keep today's semantics. No change to this PR.
- A is what execution answers, and what security: the analytics ObjectQL execute face answers a row-level read scope it cannot run with
INVALID_FILTER/ 400 whose message echoes the policy's field name and comparands — the disclosure #5367 closed for the native / echo faces #19995's consumer already computes. - B (a
classifymode that admits known placeholders without a value) would widen a contract published in this PR, and no call site needs it. Every RLS policy measured today reaches RLS policies are admitted when they are authored: policy save andobjectstack validate/ compile judge the lowered read-scope filter with the engine's judge-only method (objectstack#19995 ruling C, second consumer) #20158 as CEL lowered to concrete values, so none carries a placeholder. - If RLS policies are admitted when they are authored: policy save and
objectstack validate/ compile judge the lowered read-scope filter with the engine's judge-only method (objectstack#19995 ruling C, second consumer) #20158's own dispatch measures a real policy that needs B, it reports that as a fork there. It does not add B quietly. A pointer is on RLS policies are admitted when they are authored: policy save andobjectstack validate/ compile judge the lowered read-scope filter with the engine's judge-only method (objectstack#19995 ruling C, second consumer) #20158.
Review record, non-blocking items (6), each disposed:
- The
{ $field }referent and theaddDaysrule onwhereare enforced only by driver-sql. They sit below the judge's stated boundary, and the PR's Acceptance notes say so → Acceptance notes; carrier: thedomain:engineseat (the objectql + REST: the per-aggregationfilterstill lacks four ofwhere's doors — a bad date, anaddDaysnumeric pair, an undeclared{ $field }and an unknown key answer200with every count 0 #20148 lane), whose landed measurement it is. - The claim that the eleven withheld classes are stage-1 doors is confirmed by reading and by the probe → dropped — nothing to do.
- The PR body says
mainmoved only in files this PR does not touch; that is inaccurate (e5cf27d65editsengine.ts,4db1bf171edits the two generated JSONs) → dropped — the hunks do not overlap, the merge probe and CI are clean, and the body is not a contract surface. - The docblock's "what differs by verb" list omits
requireFindOnePredicateandexpandSearchOnAst→ dropped — neither is awhererunnability door, and neither reaches a consumer. - The consumer suites ran one docblock-only commit before the head → dropped — CI at the head covers them.
- The RLS policies are admitted when they are authored: policy save and
objectstack validate/ compile judge the lowered read-scope filter with the engine's judge-only method (objectstack#19995 ruling C, second consumer) #20158 question → answered above.
Downstream: #20158 carries
Blocked-by: #20157and unlocks when this card closes. It belongs todomain:services, so this seat does not dispatch it. #19995's analytics consumer is that card's own work.Next: ready → auto-merge through the relay → merge queue. This card closes on the merge through
Fixes.- PR form: draft, base
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsLanded — PR #20213 →
4df101c383(2026-09-27T11:01Z)domain:specseat 4 (session_01CiCTczDo7tGhafXjf61dUJ). PR #20213 merged through the merge queue on 2026-09-27T10:57:18Z, and this card closedcompletedthrough itsFixesline. The readings that confirm the landing:4df101c383has one parent (3875ae6773), is an ancestor oforigin/main, and the queue branch for the PR is gone.- Its
git patch-id --stableequals the reviewed diff's (763bd5d68551on both; 7 files, +626 / −3). - By content:
judgeFilterhas 3 hits inpackages/objectql/src/engine.tsand 5 inpackages/spec/src/contracts/objectql-engine.ts, and 0 in both on the parent.
pm:dispatchedand the assignee come off in the same act.Downstream: #20158 (
domain:services) carriesBlocked-by: #20157and is released by the unlock scan; the placeholder-semantics pointer for its dispatcher is5855081099. #19995's analytics consumer is that card's own work.- added 2 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Seam: spec
IObjectQLEngine(one optional judge-only member) → runtimepackages/objectqladmission pipeline; consumersservice-analyticsObjectQLStrategy.withReadScope/resolveFkAttr(#19995) andplugin-securitypolicy admission (the second consumer card filed with this ruling)Filed by the director seat from the maintainer's ruling on #19995 (batch #225 item 3, letter C, 「同意」). Reader who acts: the
domain:specseat, dispatched vertically (the contract member inpackages/spec, the implementation inpackages/objectql).area:*is left for triage. Consumers wait on this card withBlocked-by:: #19995 (the analytics ObjectQL face pre-judges the read scope) and the authoring-time admission card filed beside this one. Dedupe: REST list of open cards in this repository read forjudge-only,pre-judge,filter admission methodin titles → none; #19995's own dedupe (42 semantic hits, none on the engine face) stands.What the ruling settles
The engine becomes the single judge of 「can this filter run against this object」 for callers that must know before composing a policy scope into a query. Today the engine's admission doors (
text-operator-declared-type-door.ts,temporal-comparand-door.ts, theFILTER_TOKEN_UNKNOWNresolver, the virtual-field and lookup-path refusals) run only inside execution; their walks are not reachable through the packageexports(.and./coreonly:git grep -c -E "findTextOperatorOverNonTextField|findUninterpretableTemporalComparand|assertFilterIsMaterializable" origin/main -- packages/objectql/src/index.ts packages/objectql/src/core.ts→ 0, controlexport .*ObjectQL→ 5);@objectstack/service-analyticsdepends on the engine only optionally (src/plugin.ts:278optionalDependencies) and only at dev time (package.json).Work
packages/spec/src/contracts/objectql-engine.ts: one OPTIONAL member onIObjectQLEnginethat judges awhere(aFilterCondition) against a named object and returns either ok or the same diagnostic the engine would raise at execution (code and message), without executing anything. Name and exact signature are the seat's and the contract review's to settle; the docblock states these semantics.packages/objectql: the implementation runs the engine's own admission pipeline (every door execution runs, in the same order) and stops before any driver call. ⛔ Not a second copy of the walks: the doors are factored so that execution and judgement call the same functions. Premise to verify first: the admission pipeline can run without a driver or data; if a door needs either, stop and report the fork.INVALID_FILTER/ 400 whose message echoes the policy's field name and comparands — the disclosure #5367 closed for the native / echo faces #19995 lists (a text operator over a non-text field, an uninterpretable temporal comparand, an unknown filter token, a filter on a virtual field, a dotted path through a lookup) the judge returns the door's diagnostic and execution raises the same one; a runnable filter returns ok; nothing is executed (a driver spy sees no call).minorfor@objectstack/spec(a new optional contract member) and@objectstack/objectql.Clause-②: yes— a contract review before enqueue.Governing text: the #5367 ruling as recorded in
packages/services/service-analytics/src/read-scope-sql.ts(a read-scope refusal is never a 4xx; the policy's fields and comparands never reach an error body); ADR-0058 D2 (a compile-surface predicate the compiler cannot lower is an authoring-time compile error); ADR-0021 D-C (the analytics read scope is enforced by the engine); thedomain:enginedecision on #20020 (comment 5823452098 on #19995: the doors keep their diagnostics for CRUD callers; the analytics face pre-judges the scope alone).Mainstream shape this follows: PostgreSQL validates a policy at
CREATE POLICYand evaluates security quals before non-leakproof user quals; SQL Server binds security predicates at policy creation; Oracle VPD answers a broken predicate with a generic error and writes the detail to the trace; Hasura validates row permissions when metadata is applied. A judge-only entry point is the platform'sPREPARE/EXPLAINfor filter admission.Dedupe words:
engine judge-only filter admission method·IObjectQLEngine optional member pre-judge where·read scope pre-judge withReadScope 19995Director seat ·
session_01AsCNgFBs8HCjwhyHQsFbx3· filed from the ruling on #19995Generated by Claude Code