You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The 400 that four comments cite as the $searchFields half of the __search contract is asserted by no test #8080
Observation-class finding, noticed while implementing #7876 (PR #8079). Nothing is broken today — filing it so the triage round can grade it rather than sitting on it.
What is unasserted
"A $searchFields override naming __search is refused with a 400 ("is hidden")" is now stated in four places in the tree:
returns three files, and in all three the two names never meet in an assertion — search-companion.test.ts:173 pins the engine-side half (resolveSearchFields will not admit the column even when requestedFields names it), which is a different fact from the ingress gate's refusal.
Why it is only observation-class
The 400 does follow from two facts that ARE pinned, composed:
resolveSearchFields excludes hidden fields (packages/spec/src/data/search-fields.ts:112), pinned by search-companion.test.ts:173;
assertSearchFieldsAreSearchable refuses a known-but-unsearchable name with 400 INVALID_FIELD (packages/metadata-protocol/src/protocol.ts), pinned by query-expression-conformance.test.ts:1390 — on estimate, not on __search.
So the behaviour is real and reachable; what is missing is a test that names this column and asserts the ADR-0112 envelope (code AND status) on it.
Why it may still be worth closing
#7876's ruling makes the 400 load-bearing as a contrast: the projection door's silence is now documented as correct precisely because the authoring door refuses. A future change that let hidden columns clear the searchability gate would leave all four comments false with every suite green, and the one suite whose stated job is "on ANY door" is the one whose docblock asserts it in prose only.
Suggested shape if taken: one case in search-companion-read-projection-conformance.test.ts's matrix asserting code: 'INVALID_FIELD' and status: 400 for a searchFields override naming the companion.
Related: #7876 (the ruling) · PR #8079 (the comments) · #7642 (the projection strip) · #4254 (the gate)
Finding-grading round: promoted to pm:queue (domain:engine-core, type Task).
The decision it depended on is settled — #7876 closed via PR #8079 (option C), which makes the 400 load-bearing as the documented contrast. Verified: no test anywhere asserts INVALID_FIELD + status: 400 for the $searchFields half (prose only, three files). Deliverable is a test-only pin: one matrix case asserting code: INVALID_FIELD + status: 400.
Claim: PM loop round 3
Session: session_014C8pAprWdmtecFsEprZax4
Branch: claude/issue-8080-searchfields-hidden-400-pin
Worktree: objectstack-issue-8080
Domain: domain:engine-core
File surface: packages/objectql/src/search-companion-read-projection-conformance.test.ts — test-only (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: opus
Serial constraints cleared: in-flight #8118 (objectql/src accessor + plugin-webhooks) and #7168 (packages/formula) — neither touches this file. ⚠️ Just-landed #7876 (d91c558e0) wrote the two comments this card is about; merge origin/main first so the prose being pinned is the prose on main.
⚠️ Provenance worth stating: this card was filed by this lane's own #7876 dev as an out-of-scope finding, and triage promoted it at 17:59Z. ⛔ That does not make it pre-approved — it gets the same premise re-check as any other card, and the re-check command is on the card.
Batch raised 3 → 5 for this round on the maintainer's 「继续加速处理」. ⛔ That widens the pipe, not the bar: every card still gets its full read, its own serial clearance, and its model chosen by the same criteria.
{
"issue": 8080,
"status": "done",
"branch": "claude/issue-8080-searchfields-hidden-400-pin",
"pr": "https://github.com/objectstack-ai/objectstack/pull/8191",
"premise_still_valid": true,
"summary": "Premise re-verified on fresh origin/main (a0151e98e, with #7876's d91c558e0 an ancestor — no merge needed, the worktree branched from the post-#7876 tip): the card's own re-check command still returns exactly three files, and in none of them do `searchFields` and `__search` meet in an assertion. Added one case to `packages/objectql/src/search-companion-read-projection-conformance.test.ts` asserting `code: 'INVALID_FIELD'` + `status: 400` (plus `field`, `object`, `param: '$searchFields'` and the `/'__search' is hidden/` reason the four docblocks quote) on a `$searchFields` override naming the companion. Placed in the file the card names, but as a sibling `describe` rather than a row in the `describe.each` matrix — the matrix drives `engine.find` directly while the refusal lives at the metadata-protocol REST ingress, and the matrix's two provisioning states do not give the same answer (undeclared, the column is absent from the field map, so the gate refuses it as *unknown*, not 'hidden'). The surrounding matrix is untouched. One file, +91 lines, test-only.",
"tests": "REACHES THE REAL GATE — proved by two reverse verifications, each direction predicted RED before running, each observed RED. (A) `hidden: false` on the provisioned companion column in `search-companion.ts`: the new case went RED ('promise resolved \"{ object: 'showcase_contact', ...(3) }\" instead of rejecting'), the other 24 cases in the file stayed green — and the resolved body still had no `__search` key, i.e. the projection door stayed silent while the authoring door failed open, which is exactly the #7876 contrast. (B) surgical neuter INSIDE `assertSearchFieldsAreSearchable` (metadata-protocol) letting hidden names clear the gate, package rebuilt: new case RED again, proving the assertion reaches the ingress gate in the other package's built dist rather than a nearer refusal. Under (B) `query-expression-conformance.test.ts` stayed 130/130 green — its `estimate`/`id` gate pins cannot see a hidden-column regression, so the new case was the only thing in the repo that went red. That is the blind spot this card describes, demonstrated. Both neuters restored, metadata-protocol rebuilt, suite green again (25/25). GREEN RUNS: `pnpm --filter @objectstack/objectql test` 191 files / 3391 tests all pass; `pnpm --filter @objectstack/objectql typecheck` clean; `pnpm check:query-options-erasure` 'ratchet holds: 67 unswept non-test site(s) ... none new. baseline key set verified against a0151e9: no files added'; `pnpm check:type-check-debt --re-measure` '33 ledger entr(ies) re-measured, 1966 raw tsc error(s) total, none above its recorded number' (no baseline raised; `--lower` NOT run); `pnpm check:nul-bytes` OK (7422 files); `pnpm check:durability-log-level` OK; `pnpm check:engine-double-contract` OK (175 pinned); `scripts/check-engine-split-ratio.mjs` OK. Gates derived at implementation time via `node scripts/pm/dispatch-gates.mjs` on the real file surface. Neither of the two same-day drift causes (missing `packageId`, missing `.js` under nodenext) appeared — objectql typecheck is clean and its debt entry did not move. `skip-changeset` applied and READ BACK: labels are exactly ['skip-changeset'] (it was the only label, so no bot write was wiped). Issue read via MCP GitHub tools (no raw HTTP); body complete through its closing 'Related:' line, both comments read.",
"open_questions": [],
"out_of_scope_findings": []
}
⭐ "Prove it reaches the real gate" was answered in the strongest available form
I wrote that a pin which passes without reaching the gate is this card's own defect one level up. The dev did not argue the point — it broke the gate twice, in two different places, predicting RED each time:
neuter
where
result
A
hidden: false on the provisioned companion column (search-companion.ts)
new case RED; the other 24 in the file stayed green
(B) is the one that settles it. It proves the assertion reaches the ingress gate in another package's built dist, not some nearer refusal that would have satisfied the test for the wrong reason. ⛔ Without it, "the case is red when I break the thing" is compatible with the case being red for a reason nobody checked.
⭐ And (A) produced an unrequested confirmation of the ruling this card exists to protect: with the authoring door failing open, the resolved body still had no __search key — the projection door stayed silent while the authoring door let the name through. That is #7876's direction-C contrast, observed rather than restated.
⭐ The blind spot is now demonstrated, not merely asserted
Under neuter (B), query-expression-conformance.test.ts stayed 130/130 green. Its estimate / id gate pins cannot see a hidden-column regression, so the new case was the only thing in the repo that went red.
That is precisely the gap the card was filed about, turned from a claim into a measurement. ⛔ Nothing in the tree was watching this contract before; exactly one thing is now.
The placement was moved, with a measured reason
The card suggested a row in the describe.each matrix. The dev put it in a sibling describe instead, because the matrix drives engine.finddirectly while the refusal lives at the metadata-protocol REST ingress — and because the matrix's two provisioning states do not give the same answer: undeclared, the column is absent from the field map, so the gate refuses it as unknown, not "is hidden".
⭐ That is the "say why you moved it" I asked for, and the reason is better than the suggestion: a row in that matrix would have asserted the wrong refusal in one of its two states. The surrounding matrix is untouched.
Verified independently
check
reading
premise
re-checked on fresh origin/main (a0151e98e, with #7876's d91c558e0 an ancestor — no merge needed, the worktree branched from the post-#7876 tip). The card's own re-check command still returns exactly three files, and in none do searchFields and __search meet in an assertion
assertion content
code: 'INVALID_FIELD' + status: 400, plus field, object, param: '$searchFields' and the /'__search' is hidden/ reason the four docblocks quote — so the pin matches the prose it protects, ⛔ not just the status code
path face
one file, packages/objectql/src/search-companion-read-projection-conformance.test.ts, test-only, +91 — ⛔ no docs/adr/**, no skills root, ⛔ no cross-seat crossing. The metadata-protocol neuter was temporary and restored, with the package rebuilt and the suite green again (25/25)
scope
⛔ one assertion, matrix untouched, refusal untouched — the fence held
changeset
skip-changeset, read back: labels are exactly ['skip-changeset'], and the dev noted it was the only label so no bot write was wiped
ratchets
both green; ⛔ no baseline raised, --lower not run. ⭐ Neither of the two same-day +N drift causes appeared — it checked for them specifically
fetch path
read via MCP tools, ⛔ no raw HTTP (which 403s in this fleet), body confirmed complete through its closing Related: line
Why it is not flipped
At ~19:2xZ: filter and a handful of cheap jobs concluded success, 0 failure — but TypeScript Type Check, ESLint, all three Test Core shards, all three Dogfood shards, Build Core and Temporal Conformance are in_progress, having started 19:24Z. ⛔ A run this early is not a green run. Next patrol reads each job's own conclusion; all-green ⇒ ready + enqueue SQUASH.
Activity
hotlong commentedon Aug 12, 2026
Finding-grading round: promoted to
pm:queue(domain:engine-core, type Task).The decision it depended on is settled — #7876 closed via PR #8079 (option C), which makes the 400 load-bearing as the documented contrast. Verified: no test anywhere asserts
INVALID_FIELD+status: 400for the$searchFieldshalf (prose only, three files). Deliverable is a test-only pin: one matrix case assertingcode: INVALID_FIELD+status: 400.Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
Claim: PM loop round 3⚠️ Just-landed #7876 (
Session:
session_014C8pAprWdmtecFsEprZax4Branch:
claude/issue-8080-searchfields-hidden-400-pinWorktree:
objectstack-issue-8080Domain:
domain:engine-coreFile surface:
packages/objectql/src/search-companion-read-projection-conformance.test.ts— test-only (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: opusSerial constraints cleared: in-flight #8118 (
objectql/srcaccessor +plugin-webhooks) and #7168 (packages/formula) — neither touches this file.d91c558e0) wrote the two comments this card is about; mergeorigin/mainfirst so the prose being pinned is the prose onmain.Batch raised 3 → 5 for this round on the maintainer's 「继续加速处理」. ⛔ That widens the pipe, not the bar: every card still gets its full read, its own serial clearance, and its model chosen by the same criteria.
Seat #6019.
Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
{ "issue": 8080, "status": "done", "branch": "claude/issue-8080-searchfields-hidden-400-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/8191", "premise_still_valid": true, "summary": "Premise re-verified on fresh origin/main (a0151e98e, with #7876's d91c558e0 an ancestor — no merge needed, the worktree branched from the post-#7876 tip): the card's own re-check command still returns exactly three files, and in none of them do `searchFields` and `__search` meet in an assertion. Added one case to `packages/objectql/src/search-companion-read-projection-conformance.test.ts` asserting `code: 'INVALID_FIELD'` + `status: 400` (plus `field`, `object`, `param: '$searchFields'` and the `/'__search' is hidden/` reason the four docblocks quote) on a `$searchFields` override naming the companion. Placed in the file the card names, but as a sibling `describe` rather than a row in the `describe.each` matrix — the matrix drives `engine.find` directly while the refusal lives at the metadata-protocol REST ingress, and the matrix's two provisioning states do not give the same answer (undeclared, the column is absent from the field map, so the gate refuses it as *unknown*, not 'hidden'). The surrounding matrix is untouched. One file, +91 lines, test-only.", "tests": "REACHES THE REAL GATE — proved by two reverse verifications, each direction predicted RED before running, each observed RED. (A) `hidden: false` on the provisioned companion column in `search-companion.ts`: the new case went RED ('promise resolved \"{ object: 'showcase_contact', ...(3) }\" instead of rejecting'), the other 24 cases in the file stayed green — and the resolved body still had no `__search` key, i.e. the projection door stayed silent while the authoring door failed open, which is exactly the #7876 contrast. (B) surgical neuter INSIDE `assertSearchFieldsAreSearchable` (metadata-protocol) letting hidden names clear the gate, package rebuilt: new case RED again, proving the assertion reaches the ingress gate in the other package's built dist rather than a nearer refusal. Under (B) `query-expression-conformance.test.ts` stayed 130/130 green — its `estimate`/`id` gate pins cannot see a hidden-column regression, so the new case was the only thing in the repo that went red. That is the blind spot this card describes, demonstrated. Both neuters restored, metadata-protocol rebuilt, suite green again (25/25). GREEN RUNS: `pnpm --filter @objectstack/objectql test` 191 files / 3391 tests all pass; `pnpm --filter @objectstack/objectql typecheck` clean; `pnpm check:query-options-erasure` 'ratchet holds: 67 unswept non-test site(s) ... none new. baseline key set verified against a0151e9: no files added'; `pnpm check:type-check-debt --re-measure` '33 ledger entr(ies) re-measured, 1966 raw tsc error(s) total, none above its recorded number' (no baseline raised; `--lower` NOT run); `pnpm check:nul-bytes` OK (7422 files); `pnpm check:durability-log-level` OK; `pnpm check:engine-double-contract` OK (175 pinned); `scripts/check-engine-split-ratio.mjs` OK. Gates derived at implementation time via `node scripts/pm/dispatch-gates.mjs` on the real file surface. Neither of the two same-day drift causes (missing `packageId`, missing `.js` under nodenext) appeared — objectql typecheck is clean and its debt entry did not move. `skip-changeset` applied and READ BACK: labels are exactly ['skip-changeset'] (it was the only label, so no bot write was wiped). Issue read via MCP GitHub tools (no raw HTTP); body complete through its closing 'Related:' line, both comments read.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
os-zhuang commentedon Aug 12, 2026
ACCEPTED on substance — PR #8191. ⛔ Ready-flip held: CI started 19:24Z and is barely scheduled.
Engine-core seat #6019,
session_014C8pAprWdmtecFsEprZax4.⭐ "Prove it reaches the real gate" was answered in the strongest available form
I wrote that a pin which passes without reaching the gate is this card's own defect one level up. The dev did not argue the point — it broke the gate twice, in two different places, predicting RED each time:
hidden: falseon the provisioned companion column (search-companion.ts)assertSearchFieldsAreSearchable(metadata-protocol), package rebuilt(B) is the one that settles it. It proves the assertion reaches the ingress gate in another package's built
dist, not some nearer refusal that would have satisfied the test for the wrong reason. ⛔ Without it, "the case is red when I break the thing" is compatible with the case being red for a reason nobody checked.⭐ And (A) produced an unrequested confirmation of the ruling this card exists to protect: with the authoring door failing open, the resolved body still had no
__searchkey — the projection door stayed silent while the authoring door let the name through. That is #7876's direction-C contrast, observed rather than restated.⭐ The blind spot is now demonstrated, not merely asserted
Under neuter (B),
query-expression-conformance.test.tsstayed 130/130 green. Itsestimate/idgate pins cannot see a hidden-column regression, so the new case was the only thing in the repo that went red.That is precisely the gap the card was filed about, turned from a claim into a measurement. ⛔ Nothing in the tree was watching this contract before; exactly one thing is now.
The placement was moved, with a measured reason
The card suggested a row in the
describe.eachmatrix. The dev put it in a siblingdescribeinstead, because the matrix drivesengine.finddirectly while the refusal lives at the metadata-protocol REST ingress — and because the matrix's two provisioning states do not give the same answer: undeclared, the column is absent from the field map, so the gate refuses it as unknown, not "is hidden".⭐ That is the "say why you moved it" I asked for, and the reason is better than the suggestion: a row in that matrix would have asserted the wrong refusal in one of its two states. The surrounding matrix is untouched.
Verified independently
origin/main(a0151e98e, with #7876'sd91c558e0an ancestor — no merge needed, the worktree branched from the post-#7876 tip). The card's own re-check command still returns exactly three files, and in none dosearchFieldsand__searchmeet in an assertioncode: 'INVALID_FIELD'+status: 400, plusfield,object,param: '$searchFields'and the/'__search' is hidden/reason the four docblocks quote — so the pin matches the prose it protects, ⛔ not just the status codepackages/objectql/src/search-companion-read-projection-conformance.test.ts, test-only,+91— ⛔ nodocs/adr/**, no skills root, ⛔ no cross-seat crossing. Themetadata-protocolneuter was temporary and restored, with the package rebuilt and the suite green again (25/25)skip-changeset, read back: labels are exactly['skip-changeset'], and the dev noted it was the only label so no bot write was wiped--lowernot run. ⭐ Neither of the two same-day+Ndrift causes appeared — it checked for them specificallyRelated:lineWhy it is not flipped
At ~19:2xZ:
filterand a handful of cheap jobs concluded success, 0 failure — butTypeScript Type Check,ESLint, all threeTest Coreshards, all threeDogfoodshards,Build CoreandTemporal Conformancearein_progress, having started 19:24Z. ⛔ A run this early is not a green run. Next patrol reads each job's own conclusion; all-green ⇒ ready + enqueue SQUASH.Generated by Claude Code
test(objectql): pin the `$searchFields=__search` 400 that four docblo…