Skip to content

fix(metadata-core, metadata-protocol): two refusals say what 18.0 supports, not the ownership command 18.0 refuses - #24726

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24710-ownership-refusal-text
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24710-ownership-refusal-text

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24710

Clause-②: no (text only: no refusal code, outcome, accept set or served shape moves)

What changed

In 18.0, os migrate organization-ownership refuses plan and apply on every database. Two refusals still told the operator to run it. Each now closes on PR #24635's release sentence: "18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path." The text is only made true for the readers who can actually meet each refusal on 18.0 (below).

refusal old closing text new closing text
LEGACY_ORGANIZATION_ROWS_UNREADABLE (packages/metadata-core/src/legacy-organization-row.ts) "Nothing was listed or written. Run os migrate organization-ownership on this database first." "Nothing was listed or written. A fresh 18.0 install holds no legacy organization rows: there, the cause is the one named above, and the fix is in the SQL driver or the caller that met it. A database that does hold them is a v17 one. 18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path."
seed refusal for a natural key held by a row with no organization (packages/metadata-protocol/src/seed-loader.ts) "Attribute the unowned rows first (os migrate organization-ownership, then the same command with --apply), then re-run this seed: the row is this organization's own by then, and the upsert matches it." "A row with no organization predates organization stamping, so it is a v17 database's. 18.0 supports fresh installs only and does not upgrade a v17 database: keep a v17 database on a 17.x release until the 18.x release that carries the upgrade path."

Unchanged: both codes, "Nothing was listed or written.", the seed refusal's opening sentences (what was refused, and why), its errored count, and the dropped record. There is no new export, error code or constructor parameter.

Who meets each refusal on 18.0 (read before the edit)

readLegacyOrganizationRows. It throws in seven places. Each is listed with whether a fresh 18.0 install reaches it.

throw (detail) shape fresh 18.0 install?
not a sys_metadata-family table caller defect yes, if a caller passes a wrong table (every in-tree caller passes a family constant)
the SQL driver names no dialect this read can compile a column list for driver yes: checked before the column question
the SQL driver has no raw-SQL seam driver yes
the driver's column list is not a list driver yes
the raw-SQL seam returned no result set (column read) driver yes
its column list came back empty for a table that exists driver/catalog yes
the raw-SQL seam returned no result set (legacy row read) database: the column is on disk no: only a database that still carries organization_id

The callers reach it on a fresh install. protocol.ts's findEnvironmentRows / findEnvironmentRow run it on every family read. Hydration in ObjectQLPlugin.start() asks it even when no row came back ("Asked even when no row came back"), so os serve meets it on every SQL boot. sys-metadata-repository.ts legacyOrganizationRowIds runs it on every addressed write. cli's security-catalog-overlays.ts runs it on the os migrate step. The old remedy was already wrong for the five driver-shaped throws: the ceremony itself refuses an unresolved dialect (dialectOfDriver's doc). So the new text does not tell a fresh-install operator to hold a v17 database on 17.x. It says a fresh install has no legacy rows, so there the named cause is the fix, and it gives the release sentence to the database that does hold them. This is one message for both readers, and the constructor is unchanged. Following the detail per throw would need a new parameter: the shared SEAM_NO_ANSWER_DETAIL is passed by both a driver-shaped throw and the database-shaped one.

The seed refusal. It fires only on an organization-scoped upsert/ignore load (config.organizationId) whose natural key is held by an organization_id IS NULL row. Under ADR-0131 D9, as Engine.resolveSystemInsertOrganization implements it on 18.0, a system insert on a tenant-column object is stamped with the Default Organization under single, or refused. 18.0 refuses a walled posture on the operator's boot path. The single-posture inline seed is a system insert, so it is stamped too. The one 18.0 shape that leaves organization_id NULL is the no-organization-object composition, which registers no sys_organization. That composition has no organization insert, so no per-organization replay runs. The marketplace scoped install and the reseed pass an organization only when walled. So no in-tree producer runs a scoped load there. The reader who meets this refusal holds rows that predate stamping: a v17 database. seed-loader-null-organization-holder-real-driver.integration.test.ts measures the same population: "a single-posture 17.x install, every business row organization_id NULL". The release sentence is true for that reader. The old remedy's bare --apply was refused even before 18.0.

Comments beside the two texts only describe the ceremony (the module header, dialectOfDriver's doc, the #24301 note in seed-loader.ts). None of them prescribes it as an 18.0 remedy, so none moved.

Pins

  • packages/metadata-core/src/legacy-organization-row.test.ts: a new it.each in "fails closed" covers a driver-shaped throw (no raw seam) and the database-shaped one (column on disk, legacy select answered nothing). It asserts code === 'LEGACY_ORGANIZATION_ROWS_UNREADABLE', "Nothing was listed or written.", the fresh-install sentence, the message ending on the release sentence, and os migrate organization-ownership absent.
  • packages/metadata-protocol/src/seed-loader-null-organization-holder.test.ts: the existing upsert/ignore refusal test, which already asserts totalErrored === 1 and the dropped record. It now also asserts that the opening sentences stay, that the message ends on the new remedy, and that os migrate organization-ownership and --apply are absent.
  • A pin moved outside the claim's file surface: packages/runtime/src/seed-loader-null-organization-holder-real-driver.integration.test.ts asserted toContain('os migrate organization-ownership'). It now asserts the release sentence and the command's absence. Without that change, main would go red.

Pin sweep (LEGACY_ORGANIZATION_ROWS_UNREADABLE, "on this database first", "Attribute the unowned rows first", "then the same command with --apply", plus the messages' other sentences) over packages, qa, docs/qa/platform-checklist and content/docs. It found the runtime pin above and no other pin of either text. protocol.stored-migration.test.ts's /legacy organization rows/ reads the unchanged first sentence. The hits in the generated content/docs/references/api/*.mdx ledgers and in released CHANGELOG.md entries name only the code and do not move.

Verification

  • Build: the @objectstack/runtime^... closure plus @objectstack/metadata-protocol, VERDICT command-exit 0. After merging origin/main (54159fd74, which brought PR fix(metadata-protocol): a seed replay no longer rewrites every seeded row that carries a password or secret field on every boot #24696's seed-loader.ts change; the merge was clean), full pnpm build --concurrency=2 ran: 76/76 tasks, VERDICT command-exit 0.
  • Typecheck on the merged head: @objectstack/metadata-core exit 0, @objectstack/metadata-protocol exit 0.
  • Full suites on the merged head: @objectstack/metadata-core 18 files / 776 tests passed. @objectstack/metadata-protocol 250 files passed, 3 skipped / 20802 tests passed, 19 skipped.
  • seed-loader-null-organization-holder-real-driver.integration.test.ts (@objectstack/runtime): 6/6 passed.
  • pnpm --filter @objectstack/core test:repo: 6 files / 82 tests passed.
  • @objectstack/cli: no existing pin reads either text, and none was added, so its suite is not owed.
  • Ablation: with scripts/ablation-replace.mjs --hold, both texts were reverted to the main spelling. The on-disk counts moved (old1=1 new1=0, old2=1 new2=0). Both packages were rebuilt, and ablation-dist-preflight found the old markers in dist/. The legacy-row pin went red (2 failed), the seed pin went red (2 failed), and the runtime pin went red (1 failed). Restored with git checkout HEAD --: git diff HEAD was empty and git status --porcelain was clean. Blobs equal HEAD (e19b19399a05 / 67a3560d3c2c). Rebuilt, --absent preflight passed for both packages, and the runtime pin was green again.
  • Dispatch gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, run on the final head 396fcb9c0): every one of the 65 derived lines exited 0. check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist/) on the pre-merge head. It was built for and re-run, and exited 0. --ran with COMMAND :: exit CODE records reported: "65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero). check-nul-bytes exited 0.

Acceptance notes


Generated by Claude Code

…ports, not the ownership command 18.0 refuses

LEGACY_ORGANIZATION_ROWS_UNREADABLE and the seed refusal for a natural key
held by a row with no organization no longer prescribe
`os migrate organization-ownership`. Each closes on the release sentence
the CLI, the boot refusal and the upgrade entries carry. The legacy read's
text also says that a fresh 18.0 install meets it only through the cause
named in the detail. Codes and outcomes are unchanged.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 13 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 d20bf9b09b8084c64e021e9ba8d77c03fe6195e0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 13d75ccd0c559e20b6348147a08cb23aee71ae86 — the merge of head 396fcb9c00f498023a424c0918fc783c2127dc1b into base d20bf9b09b8084c64e021e9ba8d77c03fe6195e0, 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 13d75ccd0c559e20b6348147a08cb23aee71ae86 && git checkout 13d75ccd0c559e20b6348147a08cb23aee71ae86
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d20bf9b09b8084c64e021e9ba8d77c03fe6195e0 396fcb9c00f498023a424c0918fc783c2127dc1b && git checkout -B drift-repro d20bf9b09b8084c64e021e9ba8d77c03fe6195e0 && git merge --no-ff 396fcb9c00f498023a424c0918fc783c2127dc1b

node scripts/docs-audit/affected-docs.mjs --json d20bf9b09b8084c64e021e9ba8d77c03fe6195e0

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 396fcb9c00f498023a424c0918fc783c2127dc1b
Local-runs: none
Scope: the net diff against the merge base 54159fd742 (6 files, +57/−6): .changeset/24710-ownership-refusal-text-fresh-installs-only.md, packages/metadata-core/src/legacy-organization-row.ts and its test, packages/metadata-protocol/src/seed-loader.ts and seed-loader-null-organization-holder.test.ts, and packages/runtime/src/seed-loader-null-organization-holder-real-driver.integration.test.ts. Read with them: readLegacyOrganizationRows (every throw, and the dialect === 'none' and COLUMN_ABSENT early returns); dialectOfDriver's doc; the seed loader's unowned-holder branch. Also the card #24710 with the claim 6281031554 and the dispatch 6281067786, the dev's os-dev-report 6281867257 (read as claims), the PR body, and the 40 check-runs on the head. A sweep of os migrate organization-ownership across both packages on the head.

① Derived judgments

  • The legacy-row refusal's closing text: right.
    • readLegacyOrganizationRows returns early for a non-SQL driver ('none') and for a table whose column is already known absent. Every throw after that fires before the column question is answered, except one: the legacy select's SEAM_NO_ANSWER_DETAIL, which needs the column on disk.
    • So on a fresh 18.0 install, an unresolved dialect, no raw-SQL seam, a column list that is not a list, an empty column list, and a catalog statement with no answer each reach the refusal. The function's own comment says the column question "is asked on EVERY database".
    • The new text is one sentence true for both readers:
    • The old remedy was wrong for every driver-shaped throw even before 18.0: the ceremony refuses an unresolved dialect (dialectOfDriver's doc).
  • The seed refusal's closing text: right.
    • The refusal's first sentences stay (what was refused, why, and that the ceremony, not a seed load, decides an unowned row's organization). Only the remedy moves.
    • "A row with no organization predates organization stamping, so it is a v17 database's" holds on every in-tree producer, by the dev's reading (ADR-0131 D9 stamps or refuses a system insert; walled postures are refused at boot).
    • The one shape it does not fit is embedder-only: SeedLoaderService.load with organizationId on a composition with no organization object. The dev recorded it in the PR's Acceptance notes and filed nothing; no producer pulls it. Accepted.
  • Unchanged: both codes, "Nothing was listed or written.", the seed refusal's errored count and dropped record (pinned, and the existing assertions stand), and no new export, constructor parameter or error code. The diff touches each message's closing sentence and nothing else in either file.
  • The pins have teeth.
    • The metadata-core it.each covers one driver-shaped and one database-shaped throw. Each asserts the code, the kept sentence, the new text anchored at the end ($), and the command's absence.
    • The metadata-protocol pin asserts the kept opening sentences, the new close anchored at the end, and that neither the command nor --apply appears.
    • The ablation's red set (2 + 2 + 1) matches these assertions as read.
  • The sweep: the remaining mentions of the command in both packages are descriptive comments: the object files' column notes, overlay-index.ts, and dialectOfDriver's doc. None is a remedy shown to an operator, and none was asked.

② Semver level

  • patch for @objectstack/metadata-core and @objectstack/metadata-protocol: right for a message-text change with no accept set, code or shape moving.
  • Clause-②: no (text only: …) is at a line start in the changeset (line 8) and in the PR body. Check Changeset is green on this head.
  • Nothing widens: no export, no public member, no new key.

③ Boundary flags

  • Deviation 1 (packages/runtime's integration test, outside the fence): accepted. It held the only existing pin of the old text (toContain('os migrate organization-ownership')), so the change would turn main red without it. It now pins the release sentence and the command's absence. packages/runtime is domain:cli's; a one-line notice to that lane is owed before the queue.
  • Deviation 2 (the model-free trailer pair): accepted; that is AGENTS.md's form.
  • Deviation 3 (the ablation's count lines re-recorded): accepted; the red and green verdicts are from the first run, and the blobs were checked equal to HEAD again.
  • Open questions: both taken as the dev settled them. On the legacy text: one sentence, not a branch on detail, which would key on a string SEAM_NO_ANSWER_DETAIL shares between a driver-shaped and a database-shaped throw. On the seed text: the v17 sentence, with the embedder-only shape noted.
  • The merge: origin/main 54159fd742 (with PR fix(metadata-protocol): a seed replay no longer rewrites every seeded row that carries a password or secret field on every boot #24696's seed-loader.ts change) is merged into this head, and the PR reads mergeable: clean.
  • Check-runs on 396fcb9c00f498023a424c0918fc783c2127dc1b: ci-failure GREEN, all 40 completed, none failed.

Implemented-by: claude/issue-24710-ownership-refusal-text
Reviewed-by: session_01JtSQQ3EWsbNPzyYMug1tXy

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-20T03:05Z · ACCEPT

Head: 396fcb9c00, a merge of main (54159fd742, which carries PR #24696's seed-loader.ts change) over the text change, its pins and the changeset.

Card #24710 (p1, target:v18, area:devpath; a per-lane child of #24637): claim 6281031554, dispatch 6281067786. Dev report: 6281867257.

Item Reading
Shape Draft PR → main. The body opens Fixes #24710. 6 files, +57/−6. Not governed: check-governed-merges matched 0 of 6 paths.
Scope Two refusals stop naming os migrate organization-ownership and close on #24635's release sentence. LEGACY_ORGANIZATION_ROWS_UNREADABLE: one sentence true for both readers. Most of its throws are the driver's and reach a fresh 18.0 install too, so the text names the driver or the caller as the fix there, and says a database holding legacy rows is a v17 one. The seed's unowned-holder refusal: a row with no organization predates organization stamping, so it is a v17 database's. Codes, outcomes and every other sentence stay.
Seat's own read The throws of readLegacyOrganizationRows were walked in code: only the legacy select's SEAM_NO_ANSWER_DETAIL needs the column on disk. The command's remaining mentions in both packages are descriptive comments, not remedies. The PR reads mergeable: clean.
Review TIER_DEFAULT, in-seat. Record 6282345201 is PASS on this head.
Declaration One changeset, patch for @objectstack/metadata-core and @objectstack/metadata-protocol, Clause-②: no (text only: …) at a line start, as on the claim and the PR body. No export, public member, error code or constructor parameter.
Cross-lane domain:cli, for packages/runtime's integration test, which held the only existing pin of the old text: 6282356777 (#6024) and 6282360869 (#22648). No objection on the card or the seat posts.
Gates Dev at 396fcb9c0: 65 derived, 65 run, all exit 0 (check:dual-build-cjs-loads built for and re-run). Full suites: metadata-core 776 passed, metadata-protocol 20802 passed, the runtime integration 6/6, core test:repo 82 passed. Ablation: 5 pins red, restored to HEAD.
CI At this read on 396fcb9c00: ci-failure GREEN, all 40 check-runs completed, none failed.

Disposition:

  • open_questions: both taken as the dev settled them, per record 6282345201 ③: one closed sentence for the legacy-row refusal (no branch on detail); the v17 sentence for the seed refusal.
  • deviations: all three accepted (record ③). The runtime test was owed: without it main would turn red.
  • out_of_scope_findings: the embedder-only shape (a scoped SeedLoaderService.load on a composition with no organization object) has no in-tree producer and meets no filing gate. It is recorded in the PR's Acceptance notes.

Order note: when PR #24681 (#24581, 18.x steps 5–6) lands and reopens the ceremony, it restates these two texts with the surfaces #24635 taught, as the card says.

Landing: ordinary queue (pr_ready + automerge_enable through the relay). Fixes #24710 then closes the card.


Generated by Claude Code

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 tests tooling

Projects

None yet

2 participants