Skip to content

fix(cli): os dev's in-place upgrade line names no origin for a refused development database - #24935

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24833-dev-recover-text
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24833-dev-recover-text

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24833
Clause-②: no

Basis for the Clause-② line (the claim's, comment 6294842538): printed text of os dev only. No accept set, exit code, refusal, recovered boot or IPC message moves.

The seat ruled option A on the measurement in comment 6294804267: the line os dev prints before it upgrades a refused development database names no origin and points at the guard's own refusal. The stack half is an acceptance note (below).

What changed

  • packages/cli/src/commands/dev.ts. The coordinator's recover printed "This development database is still in the v17 organization-ownership shape — upgrading it in place (a copy is kept first)…" for every refusal. It now prints:

      ⟳ The organization-ownership boot guard refused this development database (the refusal above), so os dev upgrades it in place, keeping a copy first…
    

    The line names no origin. recover runs for every OrganizationOwnershipBootRefusal, and the guard throws one at six places (packages/runtime/src/organization-ownership-boot-guard.ts: the shape read failed, the birth write failed on an empty store, and a marker that is absent, unverified, unreadable or from an older shape generation). Only the absent marker means "17.x or an 18.0 pre-release created it". The guard's own text, printed just above by the failed boot, names what it found, so it stays the one source of that wording.

  • The printing moved out of the inline closure into an exported seam, upgradeRefusedDevelopmentDatabase({ print, upgrade }), so a unit test can drive the real line. recover calls it with console.log and the same upgrade as before. The order is unchanged: the line, then the runtime import and the upgrade, then the warnings and the verdict lines. The verdict line still carries the copy's path, from upgradeDevelopmentDatabase's own result.

  • One comment beside the coordinator no longer says "the v17 organization-ownership shape".

  • Changeset: @objectstack/cli patch, Clause-②: no.

Not touched (hold-offs): the guard, its refusal text and rule; the ceremony; utils/dev-organization-ownership-upgrade.ts (its behaviour, its IPC message shape and its "v18" verdict line); serve.ts; packages/core/**; packages/spec/**; the [schema-drift] line (#24638's); which boots os dev recovers; exit codes; content/docs/releases/.

End to end, on the built CLI

Fixture: a one-object define-stack scaffold; os dev --no-watch -d file:A.db -p 0 once; the adr-0131-organization-ownership row deleted from sys_migration (tables, no marker: the guard's "absent" refusal); then os dev again. NODE_ENV unset, bin/run.js, OS_TELEMETRY_DB=0.

  • main 19484af53 (the card's base; packages/cli is byte-identical at b42c621c0):

    TIMESTAMP ERROR Plugin startup failed: com.objectstack.organization-ownership-boot-guard {"error":{"message":"This database carries no organization-ownership marker: 17.x or an 18.0 pre-release (18.0.0-next.*) created it, …
    [Kernel] Plugin startup failed: com.objectstack.organization-ownership-boot-guard This database carries no organization-ownership marker: … (plus 10 stack frames)
    
      ⟳ This development database is still in the v17 organization-ownership shape — upgrading it in place (a copy is kept first)…
      ✓ Upgraded the development database to the v18 organization ownership. The database as it was before the upgrade is copied to backups/organization-ownership-TIMESTAMP, beside the ceremony's log. The plan and the report are there too.
    
  • This head (0f9e4ca00d): the same two kernel lines and frames, then

      ⟳ The organization-ownership boot guard refused this development database (the refusal above), so os dev upgrades it in place, keeping a copy first…
      ✓ Upgraded the development database to the v18 organization ownership. The database as it was before the upgrade is copied to backups/organization-ownership-TIMESTAMP, beside the ceremony's log. The plan and the report are there too.
    

    The server then reached "Server is ready" and printed "✓ server started on the upgraded database", as on main.

Tests

  • New pin, packages/cli/src/commands/dev-organization-ownership-recover.test.ts (unit tier): drives upgradeRefusedDevelopmentDatabase with a fake upgrade and records each printed line. 4 cases:
    • the first line carries no "v17" and names no origin (no 17.x, 18.0, pre-release or "created");
    • it names the refusal above, os dev, "in place" and "copy first", and prints before the upgrade runs;
    • the warnings, then the verdict line with the copy's path, and the server boots again;
    • an upgrade that did not finish prints its ✗ lines and boots nothing.
      The sentence is not pinned verbatim.
  • Red on main: the test file run against main's dev.ts (blob 17fc74e9, restored into the tree from 19484af53 with git restore --source, so not staged): 4 of 4 red, TypeError: upgradeRefusedDevelopmentDatabase is not a function, because main has no seam. Restored with git checkout HEAD; blob back to 3485d18c, git diff HEAD empty.
  • Red with main's sentence in the seam (node scripts/ablation-replace.mjs, the anchor hit once; the replacement is main's two source lines byte for byte): the anchor went from 1 to 0 and the blob from 3485d18c to 4e8a1f86. 2 of 4 red: expected ' ⟳ This development database is stil…' not to match /v17/i and … to match /refus/i. The other 2 stayed green. Restore: blob equals HEAD (3485d18c), git diff HEAD empty.
  • Green on this head: the new pin plus src/utils/dev-organization-ownership-upgrade.test.ts, 2 files, 10 passed.
  • Unit subset: every unit test that imports commands/dev, plus the dev-* utilities, port-contract-single-source, console-route-ledger.conformance and test/vitest-tiers-partition.test.ts: 18 files, 246 passed.
  • The full @objectstack/cli unit project did not finish inside the 10-minute foreground cap here; it is left to CI's Test Core. The integration tier is left to CI as well: the diff touches no integration file and no spawn entry.
  • pnpm --filter @objectstack/cli typecheck (tsc --noEmit and check:test-typecheck): exit 0. The new test is in the tsconfig.json program (--listFilesOnly: 1 hit).

Control: a bare os serve refusal is unchanged

os serve objectstack.config.ts -p 0 over the same unmarked file, OS_DATABASE_URL=file:A.db, NODE_ENV unset. It exited 1 on both trees. Its whole output on 19484af53 and on this head, with timestamps and the temp directory normalised, is byte-identical: 23 lines, diff exit 0. That output is the kernel's ERROR line, the [Kernel] line with its frames, the CLI's ✗ refusal line and the boot diagnostics. serve.ts does not import dev.ts.

Gates

All on the final head 0f9e4ca00d, each exit code captured before any pipe.

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 67 commands from the 3 changed paths (merge base b42c621c0). All 67 exited 0. Two of them, pnpm check:dual-build-cjs-loads and pnpm check:i18n-coverage, first exited 3 (PREREQUISITE NOT MET: no dist for packages outside the CLI closure). After a full turbo run build, both exited 0 (check-i18n-coverage: OK (14 config(s), 621 baselined untranslated string(s), none new)).
  • The 11 families listed under "Declared WIDE population" all exited 0: check:init-service-contract, check:live-db-isolation, check:meta-type-normalized, check:optional-error-sink, check:resume-authority-declared, check:route-envelope, check:runner-env-posture, check:settings-bind-window, check:startup-registry-verdict, check:verify-stand-in and check:wildcard-fallthrough.
  • --ran: ✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3).
  • pnpm lint (eslint . --no-inline-config): exit 0.
  • Left to CI, not run here: the four type-check lanes, Test Core (the full @objectstack/cli unit project and the integration tier included) and Build Core.

Acceptance notes

  • The stack half stays, by the seat's ruling (option A). On an os dev over a refused development database, two lines still precede the new one, both from the kernel: packages/core/src/kernel.ts:568 (this.logger.error('Plugin startup failed: …', result.error); the logger writes error level straight to process.stderr, packages/core/src/logger.ts:351 and :386) and :571 (console.error('[Kernel] Plugin startup failed: …', origMsg, origStack), with no level gate). Measured with stdout and stderr captured apart, the two lines and the 10 frames are all on stderr. serve.ts's boot-quiet window replaces only process.stdout.write, console.log and console.debug, and its catch runs after the kernel has printed. A developer meets this once per development database: after the upgrade the marker is verified. If a puller appears, the shape is option D of comment 6294804267: a host-gated kernel change that only the os dev child sets, so the bare os serve output stays as Zone 1 ruled.
  • Bare os serve prints the guard's refusal three times: the kernel's ERROR line (its JSON carries the message and the stack), the kernel's [Kernel] line with 10 frames, and then the CLI's own ✗ line (case C in comment 6294804267, re-measured above). Zone 1 keeps this output as it is, and the same two kernel lines are the ones in the note above.
  • The card's "os migrate apply on an empty database writes no organization-ownership birth marker, so the fresh install's first os serve is refused as a 17.x / 18.0 pre-release database #24827" case no longer reaches this path since 19484af53. Measured: os migrate apply --yes on an empty file writes the birth marker, and os dev over that file reaches "Server is ready" with no refusal and no upgrade.
  • Not edited, outside the surface: the docblock of utils/dev-organization-ownership-upgrade.ts and a comment in serve.ts still say "the v17 organization-ownership shape". Neither is printed.
  • Repro fixture: a one-object scaffold with the marker row deleted, not the card's 18.0.0-next.5 examples/app-showcase database. It takes the same "absent" refusal and the same recover branch. It has no retired column, so no [schema-drift] lines appear.

Generated by Claude Code

…d database

The line os dev prints before it upgrades a development database the
organization-ownership boot guard refused said the database was "still in
the v17 organization-ownership shape" for every refusal. It now names no
origin and points at the guard's refusal printed above it; the copy's path
stays on the verdict line.

Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
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 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 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 anchor(s) matched too much of the corpus to be a work list: os dev (command, 36 pages)
  • 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 — 29 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 71696121a64d8bd057fac969a72a84295890ed37 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5a1c586d32fa215de689bd6cc88f6bcd55f2b08a — the merge of head 0f9e4ca00dcd7c7b4482bdaab3f76886eb9ff9a4 into base 71696121a64d8bd057fac969a72a84295890ed37, 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 5a1c586d32fa215de689bd6cc88f6bcd55f2b08a && git checkout 5a1c586d32fa215de689bd6cc88f6bcd55f2b08a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 71696121a64d8bd057fac969a72a84295890ed37 0f9e4ca00dcd7c7b4482bdaab3f76886eb9ff9a4 && git checkout -B drift-repro 71696121a64d8bd057fac969a72a84295890ed37 && git merge --no-ff 0f9e4ca00dcd7c7b4482bdaab3f76886eb9ff9a4

node scripts/docs-audit/affected-docs.mjs --json 71696121a64d8bd057fac969a72a84295890ed37

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT

domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T16:36Z · head 0f9e4ca00dcd7c7b4482bdaab3f76886eb9ff9a4 · claim 6294348659, amended 6294842538 (option A) → reports 6294804267 (stopped at measurement) and 6295951169. No contract face (Clause-②: no, printed text of os dev), so no review of record is owed.

Check Reading
Shape Draft, base main. First line Fixes #24833; Clause-②: no line-initial. Assignee os-justin = the card's. One changeset, @objectstack/cli: patch, carrying the same Clause-② line.
Surface 3 files, +172 / −25: dev.ts, one new unit pin and the changeset, inside the amended claim. serve.ts, packages/core/**, the guard, the ceremony and the IPC message are untouched. check-governed-merges.mjs --pr 24935: NOT governed.
Mechanism Read in the diff. recover's inline printing moves into upgradeRefusedDevelopmentDatabase({ print, upgrade }) in dev.ts, and the order is the same: the ⟳ line, then the upgrade (the @objectstack/runtime import and resolveStandaloneDatabase now sit inside upgrade, still after the line), then its warnings and verdict lines. upgradedBoot is still set only on ok. The new line names no origin and points at the guard's refusal printed above it. The new export is not reachable from @objectstack/cli's exports: src/index.ts re-exports dev.ts's default only.
Evidence The pin is red on main's dev.ts (no seam), and 2 of 4 cases go red with main's sentence ablated into the seam; each was restored hash-equal. On the built CLI, the end-to-end run prints the new line before ✓ Upgraded … copied to backups/…. The control, a bare os serve over the same unmarked file, gives byte-identical normalised output on both trees.
Gates Local: 67/67 derived families and the 11 Declared WIDE population families exit 0, 0 UNRUN; pnpm lint exit 0. Check-runs on this head (38, latest per name), read at this act: 16 success, 7 skipped, 15 in_progress (Lint, Type Check, Test Core 1–6). Landing waits on all of them.

Accepted deviations:

  • The base fast-forwarded to b42c621c0, which does not touch packages/cli.
  • The pin is a unit seam, not a spawned boot: the literal red on main is an import error, so the assertion-level red is the ablation.
  • The bare-serve control is a measured before/after, not a permanent test, because serve.ts is untouched.
  • The full CLI unit project was narrowed to an 18-file subset; CI's Test Core is the measure.

Acceptance notes (one line each):

  • The ERROR line and stack before the ⟳ line come from packages/core/src/kernel.ts:568/:571, once per development database. A host-gated kernel change (option D) is the shape if a puller appears.
  • A bare os serve prints the guard's refusal three times (the kernel's two lines and the CLI's ✗). It is kept as is.
  • utils/dev-organization-ownership-upgrade.ts's docblock and one serve.ts comment still say "the v17 organization-ownership shape". Neither is printed, and both go with the next touch of those files.

Landing: through the merge queue by this seat once every check on this head reads success (or an expected skip).


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 16:54
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 16:54
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit feb09db Oct 20, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24833-dev-recover-text branch October 20, 2026 17:23
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

2 participants