Skip to content

fix(spec,plugin-sharing,scripts): retire the organization-less text step 7 left behind; check-tenant-chokepoint accepts where(tenant, 'is not', null) - #25140

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-24902-tenant-text-dormant-paths
Oct 21, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-24902-tenant-text-dormant-paths

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24902

Clause-②: no (description and help text, plus a CI gate's rule: no response, status, outcome, boot verdict or export moves)
Metadata-protocol: none (a schema description and a field's help text; nothing a metadata author writes changes)

What changed

1. Text that contradicted behaviour after 18.x step 7

2. The gate nit: scripts/check-tenant-chokepoint.mjs

  • The header says IS NOT NULL is never refused. The rule refused every three-argument where(tenant, …, null), including where(tenant, 'is not', null). Knex compiles that call to IS NOT NULL: knex/lib/query/querybuilder.js:456-457, operator lower-cased and trimmed.
  • The rule now exempts exactly a literal 'is not' operator, matched the way Knex matches it. An operator the gate cannot read is still refused. The header names all three IS NOT NULL spellings.
  • Self-test: there is one new silent case ('is not' and ' IS NOT ') and one new refused case (a non-literal operator). The D8 null-arm rule battery floor goes from 22 to 24.
  • Reverse check: I replaced the new condition with true, and the self-test went red (exit 1) on the new silent case. The file was restored with git checkout HEAD --, and git diff HEAD was empty afterwards.

3. The dormant paths: both are KEPT, with reasons. Neither is retired.

organizationOutOfWriteReach (packages/services/service-storage/src/metadata-store.ts:800) is alive:

  • Callers: storage-routes.ts:780, inside requireStartingOrganization. Three upload doors use it: the presigned commit door, chunked completion, and the progress door when its expiry stamp is due. It is the guard behind the 409 an uploader gets after switching organization. It has a door-level pin in error-envelope.conformance.test.ts:226-245 (the commit door after an organization switch) and unit pins in metadata-store.test.ts:368-405. Probe: vitest run src/error-envelope.conformance.test.ts src/metadata-store.test.ts -t 'switching active organization|organizationOutOfWriteReach' gives 2 files, 7 passed. service-storage is not in this diff, so CI does not run it.
  • Only its organization-less-row branch (metadata-store.ts:809) was written against the retired NULL arm. That branch can still run. The v18 boot guard is composed by os serve alone (packages/runtime/src/organization-ownership-boot-guard.ts, "Where it runs"). An embedding host that does not compose it can boot a database that has not been through the ceremony and still holds a v17 organization-less sys_file / sys_upload_session row.
  • So nothing is removed. One finding is left for the seat (see Acceptance notes).

The sys_record_share backfill apply pass (packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts) is dead in every shipped build, but has a published reach:

  • No production caller. The only callers are tests: backfill-sys-record-share-organizations.test.ts and record-share-organization-stamp.test.ts:315.
  • It does not ship: the tsup entry is src/index.ts alone, files is dist, and the index does not export it.
  • Probe (a throwaway vitest that was not committed): on a sys_record_share table created by the 18.x driver with no relax, storing an organization-less grant is refused (NOT NULL). planSysRecordShareOrganizationBackfill answers {scanned: 0, planned: 0}, and the apply pass writes 0. The existing real-driver pin can only build its legacy rows because it first relaxes the column (record-share-organization-stamp.test.ts:180).
  • External reach, so it is kept: the published 17.3 release notes (content/docs/releases/v17/17-3.mdx:44-48, 284-289) tell operators to run planSysRecordShareOrganizationBackfill / runSysRecordShareOrganizationBackfill(engine, { dryRun: false }). The CLI ceremony inventory cites the file as its derivation (packages/cli/src/utils/organization-ownership-inventory.ts:538). Retiring it also moves two ledgers outside this claim's file surface: scripts/engine-double-contract.pinned.json:3670 and the tenant-audit census counts. Per the dispatch, this is the seat's call.

Acceptance notes

  • For the seat, the open question: the organization-less branch of organizationOutOfWriteReach answers "in reach". Since D8, the driver's scoped write cannot reach such a row, so on a host without the boot guard the door writes, misses, and answers the 500 outage text. Option A: keep it as it is; that state only exists on a database that has not been through the ceremony. Option B: make the door refuse such a row with its own answer. That needs a new refusal text, and it touches storage-routes.ts, which is outside this claim. I recommend A, and noting it for docs(adr): ADR-0131 dated notes for the 18.x line — release scope, the D9 lean-embedding exception, D1 at createTable, D2 deployment-wide declared rules (Tier H) #24897's ADR text.
  • For the seat, the open question: retire the sys_record_share backfill or keep it. Option A: keep it until the 17.x upgrade window is formally closed. Option B: retire the module, its two tests and the two ledger rows now, and add an erratum PR for the 17.3 notes. I recommend A.
  • The claim names packages/platform-objects/src/**/sys-sharing-rule.object.ts. The file is actually at packages/plugins/plugin-sharing/src/objects/. I edited it there.
  • The storage-routes comment (storage-routes.ts:761) "a row stamped with no organization stays in reach" has the same staleness as the branch above. It is outside the file surface, so I left it.

Verification

  • pnpm --filter @objectstack/spec test: 706 files, 21465 tests passed.
  • pnpm --filter @objectstack/plugin-sharing test: 53 files, 1200 passed.
  • pnpm --filter @objectstack/plugin-sharing typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated: exit 0.
  • node scripts/check-tenant-chokepoint.mjs --self-test: exit 0, 12 refused and 7 silent. The scan itself exits 0.
  • node scripts/pm/dispatch-gates.mjs --commands: 130 commands. 130 commands on head 02067f8c, each exit code captured before any pipe: 127 exit 0 on the first pass. Three exited 3 (PREREQUISITE NOT MET, nothing measured). check-plugin-teardown-shape --self-test needed a pinned commit the shallow clone lacked; after fetching it, exit 0. spec check:skill-examples needed client-react built; after building it, exit 0. check:dual-build-cjs-loads needs ten packages this diff does not touch (studio, client-react, connector-slack and others) built, so it is NOT MEASURED locally and declared to CI. --ran reconciliation: 130 derived, 130 run, 0 unrun.
  • node scripts/pm/check-governed-merges.mjs --branch origin/claude/issue-24902-tenant-text-dormant-paths: NOT governed, 95 changed lines.
  • Size S. Not a governed surface, so it lands through the ordinary queue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pjh5f11tyUbYMQmSZaJZFS


Generated by Claude Code

…he sharing-rule organization help

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pjh5f11tyUbYMQmSZaJZFS
… null), as its header says

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-sharing, @objectstack/spec, touching 3 documentable anchor(s).

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

  • content/docs/automation/webhooks.mdx (via DataEventSchema (symbol, a top-level const))
  • content/docs/permissions/system-context.mdx (via sys_sharing_rule (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

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

  • content/docs/releases/v17/17-0.mdx (via sys_sharing_rule (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-1.mdx (via sys_sharing_rule (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

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 — 144 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 1b3b65e643bf31d2cb4764dbfc18a04234194bbf → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 00f8f272c71b4bda89d211e9db6757712b2ff3ab — the merge of head 02067f8ceb0ebc2c75ec4f576055b4948a8c0a17 into base 1b3b65e643bf31d2cb4764dbfc18a04234194bbf, 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 00f8f272c71b4bda89d211e9db6757712b2ff3ab && git checkout 00f8f272c71b4bda89d211e9db6757712b2ff3ab
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1b3b65e643bf31d2cb4764dbfc18a04234194bbf 02067f8ceb0ebc2c75ec4f576055b4948a8c0a17 && git checkout -B drift-repro 1b3b65e643bf31d2cb4764dbfc18a04234194bbf && git merge --no-ff 02067f8ceb0ebc2c75ec4f576055b4948a8c0a17

node scripts/docs-audit/affected-docs.mjs --json 1b3b65e643bf31d2cb4764dbfc18a04234194bbf

⚠️ 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 1b3b65e643bf31d2cb4764dbfc18a04234194bbf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat ACCEPT · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-21T20:15Z. PR #25140 at 02067f8ceb0ebc2c75ec4f576055b4948a8c0a17 (#24902; claim 6323334925; os-dev-report 6325044886). ACCEPT.

Seat check

  • Shape. 9 paths, +71 / -24. check-governed-merges: NOT governed and small, so this is an ordinary queue landing.

  • Review. No contract face is touched:

    • Clause-②: no;
    • Metadata-protocol: none;
    • description and help text only, plus a gate that relaxes exactly one spelling of IS NOT NULL.

    So no review tier is owed. The seat read the whole diff:

    • DataEvent.organizationId: the describe and TSDoc match D9. A single deployment stamps the Default Organization. Absence means a row outside the wall, or a composition with no organization object. The schema is unchanged, and events.mdx is regenerated.
    • sys_sharing_rule.organization_id: the help no longer says "null = global", in all four locales. The comment names the typed deploymentWide marker.
    • check-tenant-chokepoint:
      • only the literal 'is not' operator is exempt (where(col, 'is not', null), which compiles to IS NOT NULL);
      • a non-literal operator stays refused;
      • both directions are pinned, with the battery floor raised to 24.
  • Seat decisions on the two open questions in 6325044886:

  • Accepted deviation: the claim named packages/platform-objects/src/**/sys-sharing-rule.object.ts. The file lives at packages/plugins/plugin-sharing/src/objects/, and the dev edited it there, which is the claim's intent.

  • Gate check-runs on the head:

    • The required checks (TypeScript Type Check, Test Core, Build Core, Lint & Repo Gates, Governed Surface Queue Guard) all succeeded.
    • The latest run of each of the 42 check names is success or skipped.
  • Landing: the seat flips the PR ready and enables auto-merge. Fixes #24902 closes the card.

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/s tooling

Projects

None yet

2 participants