Skip to content

Commit 211df56

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21880-search-companion-scope
2 parents 40618fa + 9e33ee7 commit 211df56

35 files changed

Lines changed: 214 additions & 122 deletions

‎content/docs/data-modeling/drivers.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ Two things live **outside** `config`, because they are not driver-specific:
154154

155155
A plugin-contributed driver (`com.vendor.snowflake`) has no contract in this
156156
repo, so its `config` is left unvalidated rather than judged against a shape the
157-
platform does not have.
157+
platform does not have. The platform also does not guess which of its keys hold
158+
credentials: `config` is stored and served to administrators as written. Keeping
159+
secrets out of it is the plugin author's responsibility; put the credential in
160+
the bound secret (`external.credentialsRef`) instead.
158161

159162
<Callout type="info">
160163
The same schemas are projected to JSON Schema for

‎content/docs/permissions/sso.mdx‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,16 @@ The Console `/login` and `/register` pages will now show a button for each enabl
379379
4. Provider redirects to `/api/v1/auth/callback/google`.
380380
5. better-auth creates a session and redirects to `callbackURL`.
381381

382-
**OIDC/enterprise providers**:
382+
**OIDC/enterprise providers** (`oidcProviders`): better-auth's generic-OAuth plugin
383+
registers each one as a social provider and adds no endpoints of its own, so the flow uses
384+
the same two routes as above.
383385
1. User clicks **Continue with Okta SSO**.
384-
2. Client calls `POST /api/v1/auth/sign-in/oauth2` with `{ providerId: "okta", callbackURL }`.
386+
2. Client calls `POST /api/v1/auth/sign-in/social` with `{ provider: "okta", callbackURL }`,
387+
where `provider` is the entry's `providerId`.
385388
3. Browser redirects to the provider's authorization endpoint.
386-
4. Provider redirects back; better-auth validates the OIDC token and creates a session.
389+
4. Provider redirects to `/api/v1/auth/callback/okta`. better-auth exchanges the code, reads
390+
the user's profile from the ID token or `userInfoUrl`, creates a session and redirects to
391+
`callbackURL`.
387392

388393
## Linking to an existing account
389394

‎packages/qa/dogfood/test/per-file-cwd.global-setup.ts‎

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,34 @@
1010
// left by a developer's earlier run on a tree without this isolation, or by
1111
// a crashed run. The guard judges only what THIS run leaves, so an old
1212
// leftover never reds a run that wrote nothing.
13-
// 2. It creates ONE temporary root for the run and hands it to every worker
14-
// through `provide` / `inject`. Each test file makes its own working
15-
// directory under that root.
13+
// 2. It reserves a TAG for the run, `os-dogfood-run-XXXXXX`, as a directory
14+
// `mkdtempSync` creates under the system temp directory, and hands the tag
15+
// (a name, never a path) to every worker through `provide` / `inject`.
16+
// Each test file makes its own working directory directly under the system
17+
// temp directory, named `<tag>-file-XXXXXX`.
1618
//
17-
// At the END of the run it removes that root, and with it every per-file
18-
// directory. The removal is run-level, not per-file: on the `shared-showcase`
19-
// project (`isolate: false`) one memoized boot serves every file on a worker,
20-
// and its SQLite handles stay open in the directory of the file that booted it.
19+
// At the END of the run it removes every directory whose name starts with this
20+
// run's `<tag>-file-`, then the reservation itself. Another run's directories
21+
// carry another tag, so a concurrent run on the same machine is never touched.
22+
// The removal is run-level, not per-file: on the `shared-showcase` project
23+
// (`isolate: false`) one memoized boot serves every file on a worker, and its
24+
// SQLite handles stay open in the directory of the file that booted it.
25+
//
26+
// Why a tag and not a shared parent path (#21924): every `mkdtempSync` base in
27+
// this tree must be one the tree's scratch-directory scan can read, so that an
28+
// in-tree fixture root can never hide behind an expression
29+
// (`scripts/pm/dispatch-gates.mjs`, "no mkdtempSync site in this tree takes a
30+
// base the scan cannot read"). A path handed over through `inject()` is such an
31+
// expression. `join(tmpdir(), ...)` is not: it is outside the tree by
32+
// construction, whatever name follows it.
2133
//
2234
// ⛔ This teardown never JUDGES anything. On vitest 4.1.11 an error thrown from
2335
// a globalSetup teardown is printed as `error during close` and the run still
2436
// exits 0 (measured), so a guard placed here would be a false green. The guard
2537
// is a throwing `afterAll` in the per-file module, which fails a test file.
26-
import { mkdtempSync, rmSync } from 'node:fs';
38+
import { mkdtempSync, readdirSync, rmSync } from 'node:fs';
2739
import { tmpdir } from 'node:os';
28-
import { join } from 'node:path';
40+
import { basename, join } from 'node:path';
2941
import { fileURLToPath } from 'node:url';
3042
import type { TestProject } from 'vitest/node';
3143

@@ -34,19 +46,29 @@ const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
3446

3547
declare module 'vitest' {
3648
export interface ProvidedContext {
37-
/** The run's temporary root; each test file makes its working directory under it. */
38-
dogfoodCwdRoot: string;
49+
/** The run's tag; each test file makes its working directory as `join(tmpdir(), '<tag>-file-')`. */
50+
dogfoodRunTag: string;
3951
}
4052
}
4153

42-
let runRoot: string | undefined;
54+
/** The prefix of every per-file directory a run tagged `tag` creates under the system temp directory. */
55+
export function perFileDirPrefix(tag: string): string {
56+
return `${tag}-file-`;
57+
}
58+
59+
let reservation: string | undefined;
4360

4461
export function setup(project: TestProject): void {
4562
rmSync(join(PACKAGE_ROOT, '.objectstack'), { recursive: true, force: true });
46-
runRoot = mkdtempSync(join(tmpdir(), 'os-dogfood-run-'));
47-
project.provide('dogfoodCwdRoot', runRoot);
63+
reservation = mkdtempSync(join(tmpdir(), 'os-dogfood-run-'));
64+
project.provide('dogfoodRunTag', basename(reservation));
4865
}
4966

5067
export function teardown(): void {
51-
if (runRoot) rmSync(runRoot, { recursive: true, force: true });
68+
if (!reservation) return;
69+
const prefix = perFileDirPrefix(basename(reservation));
70+
for (const name of readdirSync(tmpdir())) {
71+
if (name.startsWith(prefix)) rmSync(join(tmpdir(), name), { recursive: true, force: true });
72+
}
73+
rmSync(reservation, { recursive: true, force: true });
5274
}

‎packages/qa/dogfood/test/per-file-cwd.setup.ts‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@
1919
// ## What it does
2020
//
2121
// At module top level, which runs before the test file's own imports, it makes
22-
// a directory under the run's temporary root and `chdir`s into it. `afterAll`
23-
// restores the previous working directory. The directories are removed at the
24-
// end of the run by the globalSetup, not here: the memoized `shared-showcase`
25-
// boot keeps its SQLite handles open in the first file's directory.
22+
// a directory directly under the system temp directory, named with the run's
23+
// tag (`<tag>-file-XXXXXX`), and `chdir`s into it. `afterAll` restores the
24+
// previous working directory. The directories are removed at the end of the
25+
// run by the globalSetup, which sweeps its own tag, not here: the memoized
26+
// `shared-showcase` boot keeps its SQLite handles open in the first file's
27+
// directory.
28+
//
29+
// The base is spelled `join(tmpdir(), ...)` on purpose (#21924): the tree's
30+
// scratch-directory scan must be able to read every `mkdtempSync` base, and a
31+
// path received through `inject()` is one it cannot read. Only the run's TAG
32+
// comes through `inject()`, as a name component, and it is refused below if
33+
// it could carry a separator.
2634
//
2735
// The invariant for every dogfood author: a file runs in its own temporary
2836
// cwd, so anything cwd-relative it writes is its own and disappears with the
@@ -38,26 +46,31 @@
3846
// what this run leaves.
3947
import { afterAll, inject } from 'vitest';
4048
import { existsSync, mkdtempSync, readdirSync } from 'node:fs';
49+
import { tmpdir } from 'node:os';
4150
import { join } from 'node:path';
4251
import { fileURLToPath } from 'node:url';
52+
import { perFileDirPrefix } from './per-file-cwd.global-setup.js';
4353

4454
/** `packages/qa/dogfood`, resolved from this module's own location. */
4555
const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
4656
/** What a file must never leave in the package directory. */
4757
const LEFTOVER = join(PACKAGE_ROOT, '.objectstack', 'data');
4858

49-
const runRoot = inject('dogfoodCwdRoot');
50-
if (!runRoot) {
59+
const runTag = inject('dogfoodRunTag');
60+
if (!runTag) {
5161
throw new Error(
52-
'per-file-cwd.setup.ts: no run root was provided. The globalSetup ' +
62+
'per-file-cwd.setup.ts: no run tag was provided. The globalSetup ' +
5363
'`test/per-file-cwd.global-setup.ts` must be wired in packages/qa/dogfood/vitest.config.ts; ' +
5464
'without it this file would run in the package directory.',
5565
);
5666
}
67+
if (/[\\/]|\.\./.test(runTag)) {
68+
throw new Error(`per-file-cwd.setup.ts: the run tag ${JSON.stringify(runTag)} is not a plain directory name.`);
69+
}
5770

5871
const previousCwd = process.cwd();
5972
const presentAtStart = existsSync(LEFTOVER);
60-
process.chdir(mkdtempSync(join(runRoot, 'file-')));
73+
process.chdir(mkdtempSync(join(tmpdir(), perFileDirPrefix(runTag))));
6174

6275
afterAll(() => {
6376
process.chdir(previousCwd);

‎packages/qa/dogfood/vitest.config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ runProjectCliOverridePreflight({
143143
// `.objectstack/data` exists in the package directory: that throw is the guard.
144144
// - The `globalSetup` below is ROOT-level: one run, one call, covering both
145145
// projects and each `OS_TEST_SHARD` slice (measured). It clears a stale
146-
// `.objectstack` at the start and removes the run's temporary root at the end.
146+
// `.objectstack` at the start and removes the run's per-file directories at the end.
147147
// Its teardown judges nothing, because a throw there exits 0 on vitest 4.1.11.
148148
// Both modules' headers carry the rest, including what a dogfood author owes.
149149
const PER_FILE_CWD = './test/per-file-cwd.setup.ts';

‎packages/rest/src/meta-state-route-engine-outage.test.ts‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,39 @@
101101
* same 501 instead of escaping to the route's own `500 EMAIL_SEND_FAILED`.
102102
*/
103103

104+
// [#21920] Pay the state route's `@objectstack/objectql` load at MODULE LOAD,
105+
// never inside a clocked window.
106+
//
107+
// The route reaches `legalNextStates` through a dynamic
108+
// `await import('@objectstack/objectql')` in `rest-server.ts`, kept dynamic on
109+
// purpose (a devDependency there: a host without the data engine degrades to
110+
// 501 rather than failing to load). This package's tests resolve that specifier
111+
// through `dist/`, so the first request to reach that line pays the vite
112+
// transform and evaluation of objectql's whole module graph, inside whichever
113+
// `it()` first gets PAST the gate with a schema: §0's multi-kernel case. Measured
114+
// on a 4-vCPU container, this file run alone, before this import existed:
115+
//
116+
// * idle: that case cost 3574-3661 ms of the 5000 ms `testTimeout`. Phase-timed,
117+
// the handler call is all of it (2.7-2.8 s in a stripped probe); wiring and
118+
// boot are under 1 ms each, and the cold KERNEL branch answering a 404 before
119+
// the load costs 1.5 ms. So it is the load, not a multi-kernel first-request
120+
// cost: under plain Node the same cold import is ~0.6 s on top of the core
121+
// and spec this server already loads, and a host whose engine IS objectql
122+
// has it loaded before any request.
123+
// * confined to one core beside two busy loops: `Test timed out in 5000ms` on
124+
// 3 of 3 runs, and the load, still in flight, then landed on §3's served
125+
// CONTROL (3714-4330 ms), the next case to reach the same line.
126+
//
127+
// A module-top import is paid during COLLECTION, which vitest clocks against
128+
// nothing (AGENTS.md § Build & Test; `scripts/check-test-source-alias.mjs`
129+
// carries the runner measurement). ⛔ Not a `beforeAll` with a budget, and not a
130+
// raised timeout: either only moves the window around the cost, and
131+
// `dev-plugin-security-enforcement-warning.test.ts` (plugin-dev) records such a
132+
// hook budget being exhausted on a heavier shard. The dynamic call in
133+
// `rest-server.ts` stays where it is; this only decides where the first load is
134+
// paid. §0's multi-kernel case pins it with a budget on its own work.
135+
import '@objectstack/objectql';
136+
104137
import { describe, it, expect, vi } from 'vitest';
105138
import {
106139
AUTHZ_STORE_UNAVAILABLE_CODE,
@@ -299,9 +332,25 @@ describe('[#15405] §0 reachability of this consumer, measured', () => {
299332
// and the route's own engine line is what decides. This is the
300333
// precondition every case in §1–§4 depends on; without it they would
301334
// all be measuring the gate.
335+
const started = performance.now();
302336
const seen = await driveStateOnKernelHost(providerHealthy);
337+
const ownWorkMs = performance.now() - started;
303338
expect(seen.outcome).toBe('answered');
304339
expect(seen.status).toBe(200);
340+
// [#21920] PIN — this is the file's FIRST case to reach the route's
341+
// `import('@objectstack/objectql')`, so it is the one that pays that load
342+
// if it ever lands in a clocked window again (file head). Its own work,
343+
// measured on a 4-vCPU container: 3 ms idle, 12-16 ms with this file
344+
// confined to a third of one core, 3-23 ms confined to a fifth. The load
345+
// it must not carry: 3574-3661 ms with the file run alone, 893 and
346+
// 1723 ms in two whole-package runs (an earlier file had already cached
347+
// the transform). 500 ms sits about 20x above the first and below every
348+
// reading of the second, so the regression reads red on an IDLE box,
349+
// well before a loaded shard turns it into the 5000 ms timeout.
350+
expect(
351+
ownWorkMs,
352+
"this case paid a module load inside its clocked window: keep the module-top import of '@objectstack/objectql' at the file head",
353+
).toBeLessThan(500);
305354
});
306355
});
307356

‎packages/spec/src/ui/dataset-filter-nested-relation-list.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const REFUSED: ReadonlyArray<readonly [
113113
// §1 Both carriers refuse, at the list's own path
114114
// ---------------------------------------------------------------------------
115115

116-
describe('#20080 §1 — both analytics carriers refuse a list inside a nested relation', () => {
116+
describe('§1 — both analytics carriers refuse a list inside a nested relation, at save', () => {
117117
it.each(REFUSED)('DatasetSchema.filter refuses %s', (_label, filter, issuePath) => {
118118
const issue = issueAt(DatasetSchema.safeParse(withScope(filter)), `filter.${issuePath}`);
119119
expect(issue.message).toMatch(/requires a single comparable value, but received an array/);
@@ -145,7 +145,7 @@ describe('#20080 §1 — both analytics carriers refuse a list inside a nested r
145145
// §2 The carrier prints the analytics door's sentence
146146
// ---------------------------------------------------------------------------
147147

148-
describe('#20080 §2 — the carrier refusal is the analytics door\'s sentence', () => {
148+
describe('§2 — the carrier refusal is the analytics door\'s sentence', () => {
149149
it.each(REFUSED)('%s', (_label, filter, issuePath, field, list, eq, holder) => {
150150
const door = analyticsDoorRefusal(field, eq ? { $eq: list } : list, holder);
151151
// The door's refusal is the ADR-0112 class-1 envelope the face throws.
@@ -175,7 +175,7 @@ describe('#20080 §2 — the carrier refusal is the analytics door\'s sentence',
175175
// §3 A list the shared schema already refuses is refused once, not twice
176176
// ---------------------------------------------------------------------------
177177

178-
describe('#20080 §3 — outside a nested relation the shared schema answers, once', () => {
178+
describe('§3 — outside a nested relation the shared schema answers, once', () => {
179179
it.each([
180180
['top level, implicit', { region: ['a'] }, 'region', /^The implicit-equality comparand on field "region"/],
181181
['top level, $eq', { region: { $eq: ['a'] } }, 'region.$eq', /^Operator "\$eq" on field "region"/],
@@ -192,7 +192,7 @@ describe('#20080 §3 — outside a nested relation the shared schema answers, on
192192
// §4 CONTROLS — accepted on both carriers, and kept
193193
// ---------------------------------------------------------------------------
194194

195-
describe('#20080 §4 — what a nested relation may still carry', () => {
195+
describe('§4 — what a nested relation may still carry', () => {
196196
const day = new Date('2026-07-01T00:00:00.000Z');
197197
it.each([
198198
['a scalar', { account: { region: 'a' } }],
@@ -217,7 +217,7 @@ describe('#20080 §4 — what a nested relation may still carry', () => {
217217
expect(measure.data!.filter).toEqual(filter);
218218
});
219219

220-
it('[#20116] $ne carrying a list inside a relation is refused on both carriers, in the door\'s $ne sentence', () => {
220+
it('$ne carrying a list inside a relation is refused on both carriers, in the door\'s $ne sentence', () => {
221221
// This row sat in the table above as "not yet at this save door (#20116)".
222222
// #20116 routes every slot this walk reaches through the query faces'
223223
// verdict, so the shape the analytics door refuses on chart is refused here.

‎packages/spec/src/ui/door-reachability.testkit.test.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const defOf = (s: unknown): unknown => (s as { _zod?: { def?: unknown } })?._zod
5858
// derivation or a coincidence. A zod upgrade that changes `clone()` semantics
5959
// changes the bridge's meaning, and that must be LOUD rather than silent.
6060
// ============================================================================
61-
describe('#5056 premise — which zod builders share the `_zod.def` object', () => {
61+
describe('the derived-clone bridge premise — which zod builders share the `_zod.def` object', () => {
6262
it('`.describe()` shares the def of the instance it was called on', () => {
6363
// THE mechanism behind #5056. `clone(inst)` without an explicit def reuses
6464
// `inst._zod.def`, so a described clone is def-IDENTICAL to its receiver.
@@ -104,7 +104,7 @@ describe('#5056 premise — which zod builders share the `_zod.def` object', ()
104104
// ============================================================================
105105
// 2. The three controls, on the instrument itself.
106106
// ============================================================================
107-
describe('#5056 controls — the walker finds doors, and only real ones', () => {
107+
describe('controls — the walker finds doors, and only real ones', () => {
108108
it('positive: live authoring roots resolve, and the graph is really walked', () => {
109109
const { verdict, nodeCount, rootCount } = measureDoors();
110110
expect(rootCount, 'every metadata type plus ObjectStackSchema').toBeGreaterThan(20);
@@ -153,7 +153,7 @@ describe('#5056 controls — the walker finds doors, and only real ones', () =>
153153
// ============================================================================
154154
// 3. The #5056 regression boundary itself.
155155
// ============================================================================
156-
describe('#5056 regression — the any-one-shared-property bridge stays dead', () => {
156+
describe('regression — the any-one-shared-property bridge stays dead; a share of the shape decides', () => {
157157
it('a 2-of-19 shared-leaf shape shares leaves with the live graph but is NOT derived from it', () => {
158158
// The reverse verification, standing rather than one-shot.
159159
//
@@ -213,6 +213,6 @@ describe('#5056 regression — the any-one-shared-property bridge stays dead', (
213213
label: I18nLabelSchema.describe('Display label'),
214214
});
215215
expect(cloneOverlap(allSharedLeaves), '2 shared of 2 keys').toBe(1);
216-
expect(verdict(allSharedLeaves), 'the residual false-reachable case — see #5828').toBe('derived-clone');
216+
expect(verdict(allSharedLeaves), 'the residual false-reachable case — no threshold excludes it').toBe('derived-clone');
217217
});
218218
});

‎packages/spec/src/ui/expression-scope-app-root.pin.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const sentenceContaining = (source: string, anchor: string): string => {
8181
/** Root tokens that are still true on these surfaces and must stay in place. */
8282
const SURVIVING_ROOTS = ['features', 'os.user'] as const;
8383

84-
describe('#17203 — no UI prose face advertises `app` as an expression-scope root', () => {
84+
describe('no UI prose face advertises `app` as an expression-scope root — the renderer no longer mounts it', () => {
8585
describe('published faces (read by authoring tools and republished into the reference docs)', () => {
8686
it('`PageComponentSchema.visibleWhen`.describe() does not name `app` among the mounted roots', () => {
8787
// ⚠️ NOT `.shape` — ADR-0089 D3a made this schema a `.strict().transform(…)`

0 commit comments

Comments
 (0)