Skip to content

feat(cli,create-objectstack): os build says when the dev database holds Studio customizations it leaves behind; the starter docs name os package export - #30336

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-uzc5lp
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-uzc5lp

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Refs #30119 (server half). The Studio notice "where a customization is saved" is the ui half and stays open on the card.

Clause-②: yes (widening) — the exported CompileCommand gains a protected reportsLocalCustomizations flag (default false); nothing removed or narrowed
Metadata-protocol: none

维护者速读

  • 改了什么:os build 结束时,若本地开发库里有 Studio 定制(包名.custom 的条目),打印一次提示:几条、在哪个包、库文件在哪,用 os package export 带走;脚手架的 AGENTS.md 和 README 写明 Studio 改动存在数据库里、不进 src/,并说明代理该改 src/。
  • 为什么改:开发者在 os dev 里用 Studio 调好视图,再 os build + Dockerfile 部署,改动丢了且无任何提示(卡上实测,本 PR 在当前 main 复测成立)。
  • 风险与回滚:只读本地 SQLite、只多打印几行,不改构建产物、不改 --json 输出、不新增服务端接口;回滚即 revert 本 PR。
  • 先看:packages/cli/src/utils/local-customizations.ts(读库与提示文字),packages/cli/src/commands/compile.ts(只在 build 上打开的那段),packages/create-objectstack/src/templates/AGENTS.md。
  • 两分钟验证:在 examples/app-todo 跑 os dev,给 todo_task.default 存一个 .custom 覆盖,再跑 os build,结尾应看到「This build does not carry 1 Studio customization…os package export」。
  • 需要你做的:无,席位落地。

What changes

os build says what the artifact leaves behind. After the artifact is written, the text face reads the SQLite file os dev opens for the project and counts the sys_metadata rows whose package_id ends in .custom (ADR-0146 D2: a Studio change to a shipped item is an item of BASE.custom). When there are any, it prints one warning and two dim lines: the count per pair, the database file, and the remedy (make the change in src/, or os package export while os dev runs).

  • Which database: the one shared resolution resolveProjectDatabaseUrl (@objectstack/runtime), over this process's environment layered on the project's development .env* files the way os dev loads them (dotenv-flow, process wins), without loading them into the build's process.
  • Cheap and local: opened read-only (better-sqlite3, the CLI's optional dependency, as sqlite-occupancy.ts already does), one aggregate SELECT. No server door, no network round trip. A database that is not a local SQLite file (Postgres, MongoDB, libSQL, :memory:) is not probed and prints nothing.
  • Never a verdict it did not reach: a file that cannot be read, or a missing better-sqlite3, prints "Could not check FILE … (reason)" plus the remedy, never silence-as-empty. A database with no sys_metadata table yet, or no .custom row, prints nothing. The probe never fails the build.
  • Only on os build: Compile gains a protected reportsLocalCustomizations = false, and Build sets it true. os dev compiles through os compile on every boot and rebuild, so the notice never prints there. os build --json is unchanged: no new key.

The starter's docs. The generated AGENTS.md gains "Change the app in src/ — a Studio change stays in its database", and the blank README's "Deploy" section gains "A Studio change is not in the image". Both say:

  • Studio saves into that environment's database, as an item of PACKAGE_ID.custom, never into src/, so build and the Dockerfile leave it behind.
  • App changes are made in src/, never through Studio or the metadata API of a running server. A change a person tried in Studio is made again in src/ when it belongs to the app. No write-back is built, as the card asked.
  • A customization that should stay one moves as a package: os login and then os package export against the dev server, commit objectstack-export/, then os package install … --runtime URL on the deployment, in the numbered order.

Tests

  • packages/cli/src/utils/local-customizations.test.ts (unit tier): the .env layering (process wins, nothing loaded), which URLs name a local file, not_applicable for a remote, unbooted or refused database, the notice text for found / unknown / none, and that Build turns the notice on while Compile keeps it off.
  • packages/cli/src/utils/local-customizations.sqlite.test.ts (integration tier, because it imports better-sqlite3 to write its fixture): counts per .custom package and nothing else (a non-pair package, a package-less row, com.custom.tools and com.example.customs are not counted), none for no pair rows or no table, the file is byte-identical after the read, unknown for a file that is not a database, and OS_DATABASE_URL is followed.
  • packages/create-objectstack/src/studio-customization-disclosure.test.ts: both docs carry the section and say .custom, database, src/, Dockerfile, os package export, os package install; AGENTS.md tells the agent to make app changes in src/.
Local runs on 0baf7e9

Head 0baf7e9a4 (after merging origin/main once, rebuilding the workspace, pnpm --filter @objectstack/spec check:generated exit 0).

  • pnpm --filter @objectstack/cli typecheck exit 0; pnpm --filter create-objectstack typecheck exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit: 385/385 files, 5435/5435 tests.
  • pnpm --filter @objectstack/cli exec vitest run src/utils/local-customizations.sqlite.test.ts (integration tier): 5/5. The rest of the CLI integration tier is declared to CI.
  • pnpm --filter create-objectstack test: 24/24 files, 369/369 tests.
  • node scripts/pm/dispatch-gates.mjs --commands: 62 families, each run with its exit code captured before any pipe, all exit 0; --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN.
  • pnpm lint exit 0.
  • fd70306c1 changes the changeset's Clause-② line only (the seat's ruling: the exported CompileCommand gains a protected member). Re-run on it: check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests, all exit 0.
  • The first unit run caught test/validate-build-gate-parity.test.ts red: the closed roster of names compile.ts calls did not classify the two new calls. They are now a NOT_A_GATE row with its reason (an advisory about the dev database, judges no metadata, refuses nothing).

Acceptance notes

  • Re-measured on current main (959c26b), app-todo, real os dev: POST /packages/com.example.todo/ensure-customization then PUT /meta/view/todo_task.default?package=com.example.todo.custom with overrides: { package: 'com.example.todo', basedOn: '2.0.0' } is served (label changed). The row is in sys_metadata with package_id = com.example.todo.custom, state = active. src/: 0 hits for the change; os build artifact: 0; os package export: 1, in 01-com.example.todo.custom/objectstack.json. The card's premise holds.
  • The notice, measured on this branch: os build on that project printed ⚠ This build does not carry 1 Studio customization stored in your dev database: com.example.todo.custom (1). and the two lines under it, both with os dev running (WAL, read-only open alongside the live server) and after it stopped. os dev's own boot compile printed nothing new.
  • Scope of the read: only a local SQLite dev database is probed. A developer whose dev database is Postgres gets the docs, not the build notice: reading it would be a network round trip, which the dispatch ruled out.
  • What counts: stored .custom items, all states. An ensured pair with no item has no row and nothing to lose, so it is not counted. A package authored wholly in Studio (not a pair) is not counted either; os package export carries it, and the docs' "every package the database holds that src/ does not register" covers it.
  • ui residue (out of scope, ui lane paused): the card's first Expected bullet, Studio saying where a customization is saved and how it moves. No ui/** edit here.

Verification

  1. cd examples/app-todo && os dev, sign in, POST /api/v1/packages/com.example.todo/ensure-customization, then save any view override into com.example.todo.custom.
  2. os build in the same directory: the last block names com.example.todo.custom (1), the database file, and os package export.
  3. os compile (or os build --json): no such block.

Generated by Claude Code

…udio change stays in its database and name os package export (WIP)

Claude-Session: https://claude.ai/code/session_01Utp6CbrroXG7sXSzmQs4uj
Co-authored-by: Claude <noreply@anthropic.com>
…tomizations it leaves behind (WIP)

Claude-Session: https://claude.ai/code/session_01Utp6CbrroXG7sXSzmQs4uj
Co-authored-by: Claude <noreply@anthropic.com>
…ogin before os package export (WIP)

Claude-Session: https://claude.ai/code/session_01Utp6CbrroXG7sXSzmQs4uj
Co-authored-by: Claude <noreply@anthropic.com>
…ate parity roster

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

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, create-objectstack, objectstack-blank, touching 13 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/AGENTS.md, packages/create-objectstack/src/templates/blank/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

36 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json bfca0efb79ac5df2049f392cdecd3bbeeea9ec61.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/AGENTS.md, packages/create-objectstack/src/templates/blank/README.md) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: os build (command, 63 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 41 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 bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 bfca0efb79ac5df2049f392cdecd3bbeeea9ec61 → 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

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

席位意见:ACCEPT(#30119 服务端一半)。os build 结束时若本地开发库里有 .custom 定制,会提示一次并指向 os package export;脚手架的 AGENTS.md 与 README 写明 Studio 改动存在数据库里、不进 src/。Studio 端「定制存在哪里」的提示属 ui 车道(暂停),卡保持打开。

席位核对

Head-sha: fd70306c18a3f467837edcf9e5c4c8f9e6fc0844 · read 2026-11-05T06:56Z

Item Reading
Shape Draft → main, author objectstack-fleet[bot] (relay pr_create); attribution block, then signature line, Refs #30119 (server half) (no closing keyword: the ui half stays open), Clause-② / Metadata-protocol at line start. 10 files, +549/−0, all under packages/cli/**, packages/create-objectstack/** and .changeset/.
Ruling The card's origin 「第一组:会卡住人或误导人,立卡」 (maintainer, 2026-11-04) and triage audit 6635627917 ("CLI output and the scaffold's AGENTS.md"). No write-back built, as the card asked.
Gates (check-runs on this head) 38 success, 16 skipped, 0 failure, 0 pending (Check Changeset, Governed Surface Queue Guard, Lint · ESLint, every Type Check · *, Test Core 1–8, Dogfood Regression Gate included). Dev's report 6651337647 on #30119.
Seat review (declaration face, TIER_DEFAULT) Read at this head: local-customizations.ts opens the SQLite file os dev resolves (resolveProjectDatabaseUrl over the process env layered on the development .env*, nothing loaded into process.env), read-only with fileMustExist, one aggregate SELECT with the suffix's LIKE wildcards escaped and an endsWith re-check; a remote or :memory: database is not_applicable; an unreadable file or a missing better-sqlite3 answers unknown and says so, never "nothing". The notice runs only when reportsLocalCustomizations is on, which Build sets and Compile (what os dev runs) leaves off, and only on the text face: --json gains no key. The build never fails on the probe. The docs sections match the code and name os login → os package export → os package install … --runtime. No finding.
Semver Clause-② amended at review to yes (widening): CompileCommand is exported from the @objectstack/cli entry (src/index.ts:11) and gains a protected flag defaulting to false (precedent #30015). Changeset (fd70306c), PR body and claim 6649971668 all carry the same line. Metadata-protocol: none.
Residue ui half, recorded here: the Studio notice "where a customization is saved" stays on #30119 for the ui lane when it reopens.
Landing Not governed (check-governed-merges --pr 30336: 0 of 10 paths), 549 lines ≤ 3000. Seat lands: pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 06:58
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 06:58
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 1c53783 Nov 5, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-uzc5lp branch November 5, 2026 07:20
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant