Skip to content

fix(spec,lint): a formula field in searchableFields is refused loudly (#6674) - #7103

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-6674-searchable-formula-fields
Aug 9, 2026
Merged

fix(spec,lint): a formula field in searchableFields is refused loudly (#6674)#7103
os-zhuang merged 4 commits into
mainfrom
claude/issue-6674-searchable-formula-fields

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #6674

The #4254 fail-open, surviving one axis over on names that are perfectly real.
resolveSearchFieldResolution's declared branch filtered entries by EXISTENCE
only, so a formula field declared in searchableFields entered the allowed
set — and the #4254 ingress gate, which reads that same set, accepted it for
exactly that reason.

Ruling this implements (triage promotion, quoted verbatim)

Triage (maintainer-authorized one-off pass, 2026-08-09, registered on #6015): promoted findingpm:queue (domain:spec kept), direction per precedent rather than a new ruling: #4254 already closed this exact fail-open shape one axis over — a declared search entry that can never match must be refused loudly, not admitted verbatim. Mirror that treatment for formula fields in declared searchableFields. Sequencing: corpus-count the would-be-refused declarations first and report in the PR body (same discipline as every enforcement widening); a non-trivial hit count is a STOP.

The wording half (#6673) is a different card and is NOT folded in — its two hint
strings are already fixed on main and are untouched here.

1. Corpus count — the STOP gate, run first

Zero authored searchableFields declarations name a formula-typed field, in
any of the three repos. Nothing in the tree changes verdict, so the STOP does not
fire and no fixture needed correcting.

Two passes per repo, the second deliberately over-inclusive so a hit cannot hide:
A = same-file (the object's fields and its searchableFields in one file,
the authoritative reading); B = every formula field NAME found anywhere in the
repo matched against every searchableFields entry found anywhere, which catches
cross-file declarations pass A cannot see — at the cost of matching unrelated
objects that share a field name.

repo rev files scanned files with searchableFields searchableFields declarations / entries files with a formula field distinct formula names pass A hits pass B hits
objectstack 4e6ca32d8 6412 67 80 / 120 33 34 0 3 (all one false positive)
objectui 5e52495 3708 43 12 / 32 15 12 0 0
cloud 485cbd3 1136 8 11 / 36 5 14 0 0

Control probes (#4878 — proving the scan saw the trees rather than reading an
empty directory): every ingredient count above is non-zero in all three repos —
the scan found searchableFields declarations and formula-typed fields and
resolved entry names in each. A scan that saw nothing would have reported zeros
in those columns, not just in the hit columns.

The three pass-B rows, adjudicated individually — all the same false positive:
packages/objectql/src/query-expression-conformance.test.ts declares
searchableFields: ['title'] on a fixture whose title is type: 'text'; the
formula-typed title lives on an unrelated fixture object in
packages/spec/src/ai/solution-blueprint.test.ts. Different objects, same field
name — which is exactly the over-inclusion pass B trades for cross-file reach.
Not a hit.

The two authored example-app files carrying searchableFields
(examples/app-showcase/src/data/objects/account.object.ts,
examples/app-showcase/src/ui/pages/renewals-pipeline.page.ts) declare no formula
entries.

2. Premise — reproduced before implementing

Predicted directions were written down before any probe ran; all six matched.

# probe predicted measured on 4e6ca32d8
P1 resolveSearchFieldResolution, declared formula admitted verbatim {"allowed":["name","project_name_formula"],"source":"declared"}
P1b same object, no declaration formula absent {"allowed":["name","project_name"],"source":"auto"}
P2 expandSearchToFilter('Apollo', …) $contains over the virtual column {"$or":[{"name":{"$contains":"Apollo"}},{"project_name_formula":{"$contains":"Apollo"}}]}
P3 lint validateSearchableFields silent [] — and the list-view narrowing was silent too, because the declared set had admitted the entry
P4 #4254 ingress gate accepts ✅ accepted; 0 rows returned, control (searchFields=name) 1 row
P5 driver-memory $contains 0 rows ✅ 0 rows (control on a stored column: 1 row)
P6 driver-sql / better-sqlite3 0 rows, silent ✅ 0 rows, err = null (control: 1 row)

P1 reproduces the card's transcript byte for byte. P6 is the one worth naming: the
SQL backend does not error on the missing column, it answers — so the failure is
indistinguishable from "no record matches".

Why it can never match: a formula value is computed on read and no driver
materializes a column for it — driver-sql/src/schema-drift.ts fieldHasColumn,
driver-turso/src/remote-transport.ts ("Virtual — no column"), and
objectql/src/engine.ts buildFormulaPlan, which excludes formula fields from
the projection it sends down precisely because the driver would fail on the name.

3. What changed, and where the deciding face is

The triage routing comment named spec's resolution semantics as the deciding face
and the lint edit as the enforcement tail. That held: the fix is a change to what
a declaration is ADMITTED to say, with two enforcement faces made loud from the
same judgment.

  • packages/spec/src/data/search-fields.ts — the deciding face. The declared
    branch now filters on existence AND scannability. New SEARCH_VIRTUAL_TYPES
    (exactly formula, pinned) and isVirtualSearchField — one judgment, so
    resolution, gate and linter cannot drift about which types have a column, the
    same one-source move REST 读路径:searchFields / groupBy / aggregations 指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴) #4254 made between gate and engine. The function stays
    non-throwing on purpose: it is consulted on every search by internal callers
    (hooks, flows, registry-less hosts) that never pass an ingress, which is exactly
    why REST 读路径:searchFields / groupBy / aggregations 指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴) #4254 put the loudness at the ingress rather than in the resolution.
  • packages/metadata-protocol/src/protocol.ts400 INVALID_FIELD under its
    own reason class, split out before the declared/auto branch because both of
    those messages are wrong for it: "outside the declared set" is false when the
    entry IS in the list, and the auto-default's "declare searchableFields to
    choose the searchable set explicitly" would instruct the author to write the
    very declaration being refused. The message names the field, its type, that the
    value is computed on read and never stored, and the fix.
  • packages/lint/src/validate-searchable-fields.ts — a build error at
    authoring time, on the object's own searchableFields as well as a view's
    narrowing, under the existing searchable-field-unsearchable rule (no new rule
    id, so no new suppression surface to document).

The carve-out that is deliberately kept. #4830 left the canonical surface
existence-only on the grounds that a declaration is the author's choice and the
engine executes it. That reasoning still holds for everything with a column: a
declared json or lookup entry runs a $contains over the stored JSON text or
the stored foreign key — narrow, rarely useful, but a scan that CAN match, so it
is neither a 400 nor a finding. The dividing line this PR draws is storage, not
search quality
, and each of the three packages carries a control pinning it, so
a later reading of #6674 as "the declared branch is type-filtered after all" goes
red rather than silently rejecting metadata the runtime accepts (ADR-0072 D1).

4. Reverse verification — direction predicted first, then measured

Predicted: the new pins go RED with the fix removed; neighbours stay GREEN. Run
per face, in order, each restored before the next.

order face reverted predicted measured
1 (15:18Z) spec/data/search-fields.ts new pins red, #4483/#6934 pins green 6 failed / 18 passed (24)
2 (15:26Z) metadata-protocol/src/protocol.ts the 3 refusal pins red, both CONTROLs green 3 failed / 2 passed
3 (15:31Z) lint/src/validate-searchable-fields.ts the 3 rule pins red, both CONTROLs green 3 failed / 2 passed

The controls staying green in runs 2 and 3 is the informative half: they show the
refusal messages are load-bearing, not merely a side effect of the spec drop — with
the spec change alone the entry is dropped from allowed but the gate answers with
the wrong neighbour message, which the pins reject.

Acceptance/rejection matrix, all measured against a real ObjectQL engine:

case before after
declared formula entry, echoed as $searchFields 200, 0 rows 400 INVALID_FIELD (code+status asserted per ADR-0112/#6142)
undeclared formula field named directly 400, auto-default advice 400, virtual reason (no longer advises declaring it)
CONTROL — declared non-formula entry (name) 200, 1 row unchanged 200, 1 row
CONTROL — plain ?search= on the same object 200, 1 row unchanged 200, same row
CONTROL — #4254 unknown name 400 unknown unchanged
CONTROL — #4254 stale declaration 400 stale unchanged
CONTROL — #4254 real-field-outside-declared-set 400 declared unchanged
CONTROL — declared json / lookup entry accepted unchanged accepted

5. Stock compatibility (acceptance tightening)

Measured on the load path, for an already-published object whose
searchableFields carries a formula field:

  • Loading is unaffected. No schema-parse change — searchableFields is still
    z.array(z.string()); this is a resolution and enforcement rule, not a shape
    rule. The object registers, and every non-search operation is untouched.
  • Plain ?search= returns the SAME rows. The dropped entry matched none of
    them before, so the result set is byte-identical (pinned as a control above).
  • Only a request that NAMES the formula field flips, from 200 with no rows
    to 400 INVALID_FIELD. This includes objectui's list search, which echoes
    schema.searchableFields verbatim as $searchFields — that is the real-world
    blast radius, and the corpus count above bounds it at zero.
  • Degenerate case, pinned rather than left to be found: an object whose
    searchableFields is ENTIRELY formula entries filters to empty and falls
    through to the auto-default, exactly as an all-stale declaration has since
    REST 读路径:searchFields / groupBy / aggregations 指向不存在的字段时被静默降级(#4226 收口后剩下的三条轴) #4254. Search widens there, from "matched nothing, ever" to the auto-default
    set. It is not left silent: the linter reports the declaration as a build error.

6. Verification run

All heavy runs under flock /tmp/os-heavy-verify.lock.

pnpm --filter @objectstack/spec              test   354 files / 9260 tests passed
pnpm --filter @objectstack/lint              test    68 files / 1776 tests passed
pnpm --filter @objectstack/metadata-protocol test    65 files /  827 tests passed
pnpm --filter @objectstack/objectql          test   162 files / 2791 tests passed
pnpm --filter @objectstack/platform-objects  test    11 files /  289 tests passed
pnpm --filter @objectstack/rest              test    76 files / 1192 tests passed
typecheck: spec (+ scripts + test layer), lint, objectql   all Done
pnpm lint (eslint . --no-inline-config)                    clean

(Re-run after the merge lap — see section 7.)

Gates from .github/workflows/lint.yml, one by one — all green:
check:nul-bytes (6527 files, plus a manual grep -naP control-byte self-scan of
every file touched), check:adr-0087-registration --base origin/main
("this PR adds no declared-breaking changeset" — the same disposition #4254's
enforcement widening was graded, hence patch), check:empty-changeset,
check:error-code-casing, check:route-envelope, check:doc-authoring,
check:quick-reference-counts, check:skill-frame-sync, check:docs-audit-scope,
check:adr-anchors, and spec's check:generated --reconcile-only,
check:authorable-surface, check:docs, check:skill-docs, check:skill-refs,
check:skill-examples, check:api-surface, check:exported-any,
check:dual-source-exports, check:spec-changes, check:upgrade-guide,
check:liveness, check:export-origins.

pnpm gen:schema && pnpm gen:docs re-run because the searchableFields describe
changed; the only committed generated delta is that one row in
content/docs/references/data/object.mdx.

One CI lap was spent on a gate this list originally missed. The first push
went red on TypeScript Type Check at check:api-surface: the two new public
exports on ./data were judged "0 breaking, 2 added" with the snapshot not
regenerated. Both additions are intentional and are the design's centre —
SEARCH_VIRTUAL_TYPES and isVirtualSearchField are the one judgment the spec
resolution, the ingress gate and the linter all read. Fixed in ce9e454ac by
pnpm --filter @objectstack/spec gen:api-surface; the snapshot delta is exactly
those two names in packages/spec/api-surface/data.json, and the remaining
spec-surface gates were swept in the same lap rather than discovered one at a
time.

One changeset (patch × spec / metadata-protocol / lint).

7. Merge lap onto post-#7090 main

PR #7090 landed check:export-origins after this branch was cut — a required
gate the branch had never satisfied, and one that would have bounced it from the
merge queue. Merged origin/main (f5a9bc2f3) in: no source conflicts;
generated artifacts were regenerated from the merged tree rather than
hand-merged.

Head b3d8e80b4. CI re-converged on it: 26/26 checks green (Console Pin Gate
skipped), ESLint ✓ 18:15:05Z and TypeScript Type Check — the job that runs
check:export-origins — ✓ 18:25:57Z.

Notes for the lander


Generated by Claude Code

…#6674)

#4254 closed the fail-open on the unknown-name axis. The same shape survived
one axis over, on names that are perfectly real: the declared branch of
`resolveSearchFieldResolution` filtered by EXISTENCE only, so a `formula`
field declared in `searchableFields` entered the allowed set — and the #4254
ingress gate, which reads that same set, accepted it for exactly that reason.

A formula value is computed on read and no driver materializes a column for
it, so the `$contains` the engine expands `$search` into has nothing to scan.
Measured: 0 rows on driver-memory, 0 rows WITH NO ERROR on driver-sql. The
declaration read as search coverage and delivered none.

- spec (the deciding face): the declared branch filters on existence AND
  scannability; new `SEARCH_VIRTUAL_TYPES` / `isVirtualSearchField` are the
  one judgment resolution, gate and linter share. The resolution stays
  non-throwing — internal callers never pass an ingress, which is why #4254
  put the loudness at the ingress.
- metadata-protocol: 400 INVALID_FIELD under its own reason, split out before
  the declared/auto branch because both of those messages are wrong for it.
- lint: a build error on the object's own set as well as a view's narrowing,
  under the existing `searchable-field-unsearchable` rule.

The storage-not-taste carve-out is kept and pinned by controls in all three
packages: a declared `json` / `lookup` column is still executed, because it
has a column and CAN match. Corpus sweep of objectstack + objectui + cloud:
zero authored declarations affected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 9, 2026 6:11pm

Request Review

@github-actions github-actions Bot added the size/l label Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

106 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 @objectstack/lint, 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, 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 @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/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/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/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 @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/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @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/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/spec)
  • content/docs/plugins/packages.mdx (via @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/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 @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/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/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/lint, @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-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 9, 2026
…ports

`check:api-surface` (inside the TypeScript Type Check job) judged the public
surface "0 breaking, 2 added" and asked for the snapshot. Both additions are
intentional and are the design's centre: `SEARCH_VIRTUAL_TYPES` and
`isVirtualSearchField` are the ONE judgment the spec resolution, the #4254
ingress gate and the linter all read, so that they cannot drift about which
field types have a stored column.

Snapshot delta is exactly those two names in api-surface/data.json.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
… baseline

PR #7090 landed `check:export-origins` after this branch was cut, so the merge
of `origin/main` brought a required gate the branch had never satisfied: the
`data` shard was stale for the two exports this PR adds.

`pnpm --filter @objectstack/spec gen:export-origins` — one shard rewritten,
two added lines, both resolving to the single declaration site
`src/data/search-fields.ts`. No re-homed origin and no second origin for an
existing name, so this is not the #4411 dual-source trap the gate warns about;
`check:dual-source-exports` agrees (0 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
@os-zhuang
os-zhuang marked this pull request as ready for review August 9, 2026 18:32
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 4ac12ef Aug 9, 2026
28 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6674-searchable-formula-fields branch August 9, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

2 participants