Skip to content

Commit 2de81f2

Browse files
committed
fix(dogfood): auth the search proof via apiAs; strengthen multi-field evidence; flip ADR-0061 to Accepted
- showcase-search proof: use signIn token + apiAs (stack.api is unauthed); prove the industry hit positively via searchFields:['industry'] instead of assuming no account name contains 'retail' (the seed has 'acme retail' — now used as the cross-field OR control). - ADR-0061 -> Accepted (Tier 1): its own conformance bar is now met (ledger + HTTP dogfood proof, 6 tests green).
1 parent 9fe0831 commit 2de81f2

2 files changed

Lines changed: 16 additions & 13 deletions

File tree

docs/adr/0061-record-search-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0061: Record Search Architecture — metadata-driven `$search` resolution
22

3-
**Status**: Proposed — recommended for acceptance (2026-06-21). **Tier 1 (`$search` + `object.searchableFields`) has since been implemented & is live; Tier 2 (FTS/relevance) deferred.** Verified 2026-07-16: Tier 1 claim TRUE (`objectql/src/search-filter.ts` + engine wiring + unit tests, incl. `$searchFields` narrowing and select label-mapping) — but this ADR's own acceptance bar (a `search-conformance` ledger entry + dogfood multi-field HTTP proof, per ADR-0060 closure) is still unmet, so the header stays Proposed until that lands.
3+
**Status**: Accepted (2026-06-21; Tier 1) — implemented and conformance-closed per this ADR's own bar: server-resolved `$search` executor (`objectql/src/search-filter.ts` + engine wiring), `object.searchableFields` + `$searchFields` narrowing, select label→value mapping; **`search-conformance` ledger** (`dogfood/test/search-conformance.ledger.ts`, ADR-0060 `checkLedger`, proof-required) + **dogfood multi-field HTTP proof** (`showcase-search.dogfood.test.ts`: "retail"→Northwind via `industry`, label mapping, narrowing, terms-AND). Tier 2 (FTS / relevance / external engines) remains deferred — add its ledger row with a proof when a driver ships `fullTextSearch: true`.
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: ADR-0045 (additive materialization & visibility gate — search must be visibility-aware), ADR-0049 (enforce-or-remove / no unenforced declaration), ADR-0054 (runtime proof per enforced surface), ADR-0060 (conformance ledger as a platform pattern)
66
**Consumers**: spec, objectql, driver-sql, driver-memory, driver-mongodb, rest, objectui (`fields`, `plugin-list`, `app-shell`), verify, dogfood

packages/dogfood/test/showcase-search.dogfood.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,36 @@ import { bootStack, type VerifyStack } from '@objectstack/verify';
1515

1616
describe('showcase: $search over the HTTP API (ADR-0061 conformance proof)', () => {
1717
let stack: VerifyStack;
18+
let token: string;
1819

1920
const query = async (body: Record<string, unknown>) => {
20-
const res = await stack.api('/data/showcase_account/query', {
21-
method: 'POST',
22-
headers: { 'content-type': 'application/json' },
23-
body: JSON.stringify(body),
24-
});
21+
const res = await stack.apiAs(token, 'POST', '/data/showcase_account/query', body);
2522
expect(res.status).toBe(200);
2623
const data = await res.json();
2724
return (data?.data?.records ?? data?.records ?? []) as Array<Record<string, unknown>>;
2825
};
2926

3027
beforeAll(async () => {
3128
stack = await bootStack(showcaseStack);
32-
await stack.signIn();
29+
token = await stack.signIn();
3330
}, 60_000);
3431
afterAll(async () => { await stack?.stop(); });
3532

3633
it('multi-field match: "retail" returns Northwind via industry, not name', async () => {
3734
const records = await query({ search: 'retail' });
38-
const names = records.map((r) => r.name);
35+
const names = records.map((r) => String(r.name));
3936
expect(names).toContain('Northwind');
40-
// Guard the premise that makes this a MULTI-FIELD proof: no account is
41-
// NAMED *retail* — the hit can only have come from the industry field.
42-
for (const n of names) {
43-
expect(String(n).toLowerCase()).not.toContain('retail');
44-
}
37+
// Guard the premise: Northwind's NAME does not contain "retail", so its
38+
// presence in the result can only come from a non-name field…
39+
expect(names.find((n) => n === 'Northwind')!.toLowerCase()).not.toContain('retail');
40+
// …and prove it positively: restricting the same search to `industry`
41+
// alone still returns Northwind — the hit IS the industry field.
42+
const viaIndustry = await query({ search: 'retail', searchFields: ['industry'] });
43+
expect(viaIndustry.map((r) => r.name)).toContain('Northwind');
44+
// (The seed also has an account literally NAMED "acme retail" — a useful
45+
// control: the unrestricted search returns it via `name`, proving the
46+
// cross-field OR spans both fields in one query.)
47+
expect(names.some((n) => n.toLowerCase().includes('retail'))).toBe(true);
4548
});
4649

4750
it('select label→value mapping: the capitalized label "Retail" also matches', async () => {

0 commit comments

Comments
 (0)