Skip to content

fix(dev-dx): hot-reload new objects end to end, persistent dev-admin hint, doctor container/nav refs - #3108

Merged
os-zhuang merged 1 commit into
mainfrom
fix/dev-dx-p2
Jul 17, 2026
Merged

fix(dev-dx): hot-reload new objects end to end, persistent dev-admin hint, doctor container/nav refs#3108
os-zhuang merged 1 commit into
mainfrom
fix/dev-dx-p2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

问题(15.1 第三方评估 P2 #3/#4/#5,合并修复)

三项 dev-loop DX 毛刺,全部在真实 create-objectstack 脚手架项目上复现,并用本分支 runtime 在同一项目上实测修复。

3. dev 热重载对新增对象不建表不跑 seed

watch 模式新增 *.object.ts 后终端「✓ recompiled — server will auto-reload」全绿,但对象完全不可用直到手动重启。排查发现比评估报告还深一层:MetadataManager.register() 根本不通知 subscribe() 订阅者——artifact 热重载后 ObjectQL 的注册表桥接从未触发,新对象连 SchemaRegistry 都进不去(REST 报 not registered);建表只在 boot 跑;seed 只读 boot 时的 bundle(且 seed 无 name,从不进 MetadataManager)。修复(各层各归其位):

  • metadata:metadata:reloaded 事件载荷携带解析后的 artifact 集合(seeds 只有这条通道)
  • objectql:reload hook 从载荷 ingest 对象定义进 SchemaRegistry(镜像 subscribe handler,保留 _packageId 溯源)+ 重跑幂等 schema sync(尊重 skipSchemaSync,串行化防 DDL 竞争)
  • runtime:dev-gated、单租户的热重载 seeder,给首次出现的对象跑 seed——已加载(可能被用户改过)的数据集绝不 mid-run 重放
  • cli:recompile 行显式列出新对象:✚ new object(s): eval_app_tag — table & seeds sync on reload

实测:server 运行中新增对象+seed → 3 秒内 REST 查询返回 3 行 seed 数据,零重启。kernel hook 按注册顺序串行(objectql 建表先于 runtime seed)。

4. dev 默认管理员账号不可见

终端凭据提示其实已存在,但只在「真正 seed 的那次 boot」显示——os dev 默认持久化 DB,第二次启动起就消失;登录页则完全无信号。修复:

  • maybeSeedDevAdmin 在「已有用户」分支追加探测:seed 账号仍存在且仍是默认密码(better-auth 原生 password.verifysys_account 存储 hash 校验)→ 重新点亮 devSeedResult,startup banner 每次 dev boot 都显示
  • getPublicConfig() 新增 dev-gated devSeedAdmin 字段(NODE_ENV==='development' 双重门,生产由构造保证绝不出现),登录页横幅数据源(objectui PR 另发)

实测三态:seed 首启显示 ✓ / 重启(不再 seed)仍显示 ✓ / 改密后重启消失 + config 字段消失 ✓。新增 3 个门控单测。

5. os doctor 引用分析误报

收集器读的几乎全是死字段(view.object/nav.object/agent.objects 在现行 spec 均不存在)。重写:defineView 容器全路径(list/listViews/form/formViewsdata.object、subform childObject、lookup 表单字段)、app 导航(objectName + 嵌套 children + areas,含 requiresObject)、flow 节点 config.object/objectName;删除死的 agent.objects 读取(消息同步改为 "view, flow, app, or lookup field");findOrphanViews 同步下钻容器。

实测对照(同一项目:task 仅容器视图绑定、tag 仅导航挂载、note 真孤儿):发布版 doctor 误报 3/3;本分支只报 note 1/3

测试

cli 524 / plugin-auth 460 / objectql 905(含新增 reload-ingest 集成测试)/ runtime 544 / metadata 260(含 payload 契约测试)全绿;pnpm build 干净。

🤖 Generated with Claude Code

…hint, doctor container/nav refs
Three P2s from the 15.1 third-party evaluation, all reproduced against a
real create-objectstack scaffold and re-verified fixed on the same
project with this branch's runtime.
1. Hot-added objects (os dev watch) were dead until restart. The
artifact reload path calls MetadataManager.register(), which fires no
subscribe() watchers — ObjectQL's registry bridge never saw the new
object, tables only ever came from the boot-time sync, and seeds only
from the boot-time bundle. Now:
- metadata: the metadata:reloaded payload carries the freshly parsed
artifact collections (seeds have no name and never enter the
manager — the payload is the only channel)
- objectql: the reload hook ingests payload object definitions into
the SchemaRegistry (mirroring the subscribe handler, provenance
preserved) and re-runs the idempotent schema sync, serialized,
honoring skipSchemaSync
- runtime: a dev-gated, single-tenant hot-reload seeder loads seeds
for first-seen objects only — an already-loaded (possibly
user-edited) dataset is never re-upserted mid-run
- cli: the watch recompile line calls out new objects explicitly
2. Dev admin credentials vanished after the first boot. os dev uses a
persistent DB, so the seed (and the banner hint keyed off it) only
fired on the seeding boot, and the login page had no signal at all.
maybeSeedDevAdmin now re-arms the hint whenever the seed account
still verifies against the default password (better-auth native
password.verify), and getPublicConfig() exposes a dev-only
devSeedAdmin field for the Console login page (objectui side ships
separately). Hint disappears the boot after the password changes —
verified live in all three states.
3. os doctor reported objects bound via defineView containers
(list/listViews/form/formViews data.object, subform childObject,
lookup form fields) and app navigation (objectName, nested children,
areas) as 'defined but not referenced'. The collector now walks the
canonical shapes plus flow node config.object/objectName; the dead
agent.objects read is gone and findOrphanViews descends into
containers. Stock CLI on the probe project flags 3/3 objects; patched
flags only the genuinely unreferenced one.
Tests: cli 524, plugin-auth 460, objectql 905, runtime 544,
metadata 260 — all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 17, 2026 9:51am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/cli, @objectstack/metadata, @objectstack/objectql, @objectstack/plugin-auth, @objectstack/runtime.

40 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx(via packages/cli)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli)
  • content/docs/api/index.mdx(via @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via packages/cli, @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/metadata, @objectstack/objectql)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/data-modeling/formulas.mdx(via packages/objectql)
  • content/docs/deployment/backup-restore.mdx(via @objectstack/cli)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx(via @objectstack/objectql)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/self-hosting.mdx(via @objectstack/cli)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/objectql, @objectstack/runtime)
  • content/docs/getting-started/cli.mdx(via @objectstack/cli, @objectstack/plugin-auth)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/cli, @objectstack/runtime)
  • content/docs/kernel/cluster.mdx(via packages/metadata)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/objectql, @objectstack/plugin-auth)
  • content/docs/kernel/services.mdx(via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli, @objectstack/objectql, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/permissions/sso.mdx(via @objectstack/plugin-auth)
  • content/docs/plugins/index.mdx(via @objectstack/objectql, @objectstack/plugin-auth)
  • content/docs/plugins/packages.mdx(via @objectstack/cli, @objectstack/metadata, @objectstack/objectql, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/objectql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/metadata-service.mdx(via @objectstack/metadata)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli, @objectstack/objectql, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/releases/v12.mdx(via @objectstack/metadata)
  • content/docs/releases/v9.mdx(via @objectstack/objectql, @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit fdc244e into mainJul 17, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the fix/dev-dx-p2 branch July 17, 2026 11:28
os-zhuang added a commit that referenced this pull request Jul 17, 2026
…rs (#3112) (#3131)
* fix(metadata): register()/unregister() announce to subscribe() watchers (#3112)
`MetadataManager.register()` updated the in-memory registry, persisted to
writable loaders and published to the realtime service — but never called
`notifyWatchers()`. `unregister()` had the same gap. Watchers only ever fired
from the `saveMetaItem` path (indirectly, via the sys_metadata repository watch
stream) and the filesystem watcher, so `subscribe()` looked like it covered
every write while silently missing all of them.
The consumer that pays for this is ObjectQL's SchemaRegistry bridge — the
component that decides what is queryable. #3108 worked around it for the
`metadata:reloaded` seam by carrying the parsed artifact on the event payload,
but the asymmetry remained for every other `register()` call site and every
other `subscribe()` consumer.
Announce by default, so a new call site is correct without knowing the contract
exists. Two concrete fixes fall out: `unregisterPackage()` now tells cached
consumers to drop uninstalled objects instead of serving them until restart,
and runtime datasource writes reach watchers.
Bulk ingest opts out explicitly via the new `MetadataWriteOptions`
(`{ notify: false }`) at the five boot/batch sites, each of which either runs
before consumers cache anything or announces the whole batch once. ObjectQL's
registry bridge MUST stay silent for a second reason: it copies objects OUT of
the SchemaRegistry, and announcing would feed them back through a handler that
re-registers under `_packageId ?? 'metadata-service'` — overwriting the true
package provenance of every object whose body carries no `_packageId`.
Additive only: the 3-arg forms keep working, and the api-surface ratchet reports
0 breaking / 1 added.
Tests pin both halves of the contract (announce by default, silence only when
asked) and the no-flood invariant on the reload path; both were verified to fail
against the pre-change behavior.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(changeset): state the register() notify change as a contract fix, not a bug fix
unregisterPackage()/bulkUnregister() have no production caller in framework or
cloud today, so their announce is correct-but-latent. The one live behavior
change is runtime datasource writes reaching the HMR SSE stream. Say so instead
of advertising two 'concrete fixes'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang