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

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 9:51am

Request Review

@github-actions github-actions Bot 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 main Jul 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 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