Skip to content

fix(plugin-auth, driver-sql)!: fail closed on a missing auth secret outside development; compile window functions from closed tables - #25563

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-onzrsq
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-onzrsq

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Clause-②: no (narrowing)
Metadata-protocol: none

Two small hardenings, one commit each, each with its own changeset. Draft: it waits for the maintainer's review.

1 · plugin-auth: the temporary auth secret is admitted on a development boot only

generateSecret (auth-manager-database-secret.ts) fell back to a temporary dev-secret- value whenever NODE_ENV was anything but exactly production. It now admits that fallback only when isDevelopmentBoot from @objectstack/core holds (NODE_ENV=development) or under NODE_ENV=test, the same pair core's own production fuse reads (isCloudManaged). Every other boot without OS_AUTH_SECRET (or its legacy spellings, or a secret option) refuses to start, as production already did.

  • CLI boots are unchanged. os serve sets NODE_ENV=production when it is unset and development under --dev; os start does the same for the child it spawns. So only an embedder that constructs AuthPlugin / AuthManager itself with no secret and no NODE_ENV=development sees the new refusal.
  • Why test is kept. Vitest sets NODE_ENV=test, and the full plugin-auth suite constructs managers without a secret. Core's own fuse treats test as non-production for the same reason.
  • Tests: the existing guard block in auth-manager.test.ts gains an unset NODE_ENV case, four custom values (staging, Production, dev, empty), and a test case.

2 · driver-sql: findWithWindowFunctions() compiles from closed tables and quoted columns

buildWindowFunction assembled FUNC() OVER (PARTITION BY … ORDER BY …) as text. Now:

  • the function name is looked up in a closed table, row_number, rank, dense_rank, percent_rank, cume_dist (any case). These are the argument-less functions the door's flat shape can express. Anything else answers INVALID_QUERY / 400 before a statement is sent.
  • the sort direction is asc or desc (any case, default asc); anything else answers INVALID_QUERY / 400.
  • every partitionBy / orderBy field is emitted with knex.ref(...).toQuery(), the quoting idiom this file already uses for columns.

Why parameterised, not removed. The dispatch preferred deleting the method. Measured first: nothing in objectstack, objectui or hotcrm calls it outside driver-sql's own tests and the turso override. It is still a published door, though. The protocol-17 migration query-window-functions-retired and the query.windowFunctions tombstone in packages/spec/src/data/query.zod.ts both prescribe SqlDriver.findWithWindowFunctions() as the replacement for the retired request key, and the 17.0 release notes and content/docs/data-modeling/queries.mdx document it. Removing it would retire a capability those texts tell embedders to migrate to. This PR keeps it and closes the string assembly instead. If the maintainer prefers removal, that is a follow-up that also rewrites those prescriptions.

  • Tests: a new sql-driver-window-function-compilation.test.ts covers the five functions in mixed case, three refused names, both directions plus the default, a refused direction, the exact compiled text, and a field-slot expression read as a column name.

Red on main, green here

Each fix was committed first, then the source file alone was restored from main (19d3720) and the new tests were run:

  • plugin-auth guard block: 6 failed / 3 passed on main's source; 9 passed with the fix.
  • window-function compilation: 6 failed / 6 passed on main's source (the six passes are the accepted spellings); 12 passed with the fix.

Local verification

  • pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: 191 files, 3613 passed, 10 skipped
  • pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2: 235 files passed, 12 skipped; 3787 passed, 212 skipped
  • Derived gate list (dispatch-gates.mjs --commands) run locally: all 74 derived families exit 0 at 329dd94a (dispatch-gates.mjs --ran reconciles 74 run, 0 NOT-MEASURED). Four needed a real-declaration build or a deeper fetch first and were re-run after it.
  • pnpm --filter @objectstack/plugin-auth --filter @objectstack/driver-sql run typecheck after a real-declaration build of both closures: exit 0
  • Dogfood battery (owed by the narrowing changesets): run on this head after the PR opens; the result goes to the reviewer thread.

Acceptance notes

  • driver-sqlite-wasm and driver-turso (local and replica transports) inherit the builder, so they get the same closed compilation. The turso remote transport already refused this door.
  • No issue is linked: this work was dispatched directly.

Generated by Claude Code

…t only

The ephemeral fallback secret was admitted whenever NODE_ENV was not
exactly `production`, so an unset or custom NODE_ENV booted with a
predictable secret. Gate it on `isDevelopmentBoot` from @objectstack/core
(or NODE_ENV=test, the pair core's production fuse reads) and refuse to
boot otherwise. `os serve` / `os start` default an unset NODE_ENV to
production and `--dev` sets development, so CLI boots are unchanged.

Claude-Session: https://claude.ai/code/session_01SNJgKweepTFKCrYgaKeFZK
Co-authored-by: Claude <noreply@anthropic.com>
…ed columns

`findWithWindowFunctions()` built its OVER clause by string
concatenation. The function name and sort direction now come from closed
tables (anything else is refused with INVALID_QUERY / 400), and every
partitionBy / orderBy field is emitted as a quoted column reference.

Claude-Session: https://claude.ai/code/session_01SNJgKweepTFKCrYgaKeFZK
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/driver-sql, @objectstack/plugin-auth, touching 15 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/data-modeling/queries.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level class), ROW_NUMBER (literal, a string literal in SQL_WINDOW_FUNCTIONS), row_number (literal, a string literal in SQL_WINDOW_FUNCTIONS))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level class))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level class))
  • content/docs/releases/v17/17-6.mdx (via SqlDriver (symbol, a top-level class))

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.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 30 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 041a120b6189e9b174a3f4b8cf6202f22ace2bbd → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f646d778c9c0a077dc1b7f74444c7c3e4606573c — the merge of head 329dd94ac1e36cb9d6f93303a2e5a6d8844c6c33 into base 041a120b6189e9b174a3f4b8cf6202f22ace2bbd, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f646d778c9c0a077dc1b7f74444c7c3e4606573c && git checkout f646d778c9c0a077dc1b7f74444c7c3e4606573c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 041a120b6189e9b174a3f4b8cf6202f22ace2bbd 329dd94ac1e36cb9d6f93303a2e5a6d8844c6c33 && git checkout -B drift-repro 041a120b6189e9b174a3f4b8cf6202f22ace2bbd && git merge --no-ff 329dd94ac1e36cb9d6f93303a2e5a6d8844c6c33

node scripts/docs-audit/affected-docs.mjs --json 041a120b6189e9b174a3f4b8cf6202f22ace2bbd

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 041a120b6189e9b174a3f4b8cf6202f22ace2bbd → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants