fix(cli,runtime): a named artifact that is missing is a broken instruction, not an empty boot — and the ordering claims say what the kernel resolves (#4110 follow-up, #4131) - #4138
Conversation
…he order the kernel actually resolves (#4131) The comment on `defaultDatasourcePlugin`'s slot claimed the position was load-bearing — "MUST precede ObjectQLPlugin: its start() connects the default driver … ObjectQLPlugin.start() runs boot schema-sync right after". Both halves are wrong, and reading the slot as a guarantee is how #4085 happened: - the connect happens in `init()`, not `start()`; - the position cannot deliver that order anyway. `DefaultDatasourcePlugin` declares a hard dependency on ObjectQL, so `resolveDependencies` HOISTS ObjectQLPlugin ahead of it. Measured on a real serve boot: `com.objectstack.engine.objectql` inits at #10, `com.objectstack.runtime.default-datasource` at #16 — the reverse of what the slot reads as. What actually orders them is the phase split (Phase 1 completes before any `start()`) plus that declared dependency — the contract already documented on the plugin itself, under its "Ordering — phase, not list position" note, which records the no-tables boot that taught it to us. This comment now points there instead of contradicting it two files away. Comment only; no behaviour changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ve9HidCGRGtS2UjNPUGHSV
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 30 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
CI 红在 ESLint job,但挂的不是 ESLint —— 是同一 job 里的 这是 base 分支的失败,不是本 PR 造成的。 本 PR 的全部改动是 成因是 #4122(引入这个 guard,并在 本 PR 其余检查在同一轮里是绿的:TypeScript ✅ Build Core ✅ Temporal Conformance ✅ Changeset ✅ PR Size ✅ Auto Label ✅ filter ✅ docs-flag ✅(Test Core / Dogfood 仍在跑)。 #4133 落地后我会把 main 合进来重跑,届时如果还红就是本 PR 的问题,我自己修。 Generated by Claude Code |
…ction, not an empty boot (#4110 follow-up) #4110 made an absent artifact non-fatal all the way down — right for the conventional `<cwd>/dist/objectstack.json`, which is simply "not compiled yet". But `OS_ARTIFACT_PATH` / `{ artifactPath }` skip the existence check by design (the loader was to validate lazily), so that tolerance reached them too: OS_ARTIFACT_PATH=/nope os serve → No objectstack.config.ts found — booting from artifact (default host)... → ✓ Server is ready with the missing path named NOWHERE in the output — serve's boot-quiet window drops the loader's calm ENOENT line, which #4110 had moved from `warn` (buffered and replayed by #4084) to `log` (discarded). Before #4110 this died loudly. The distinction that matters is NAMED vs CONVENTIONAL, not the errno. `createDefaultHostConfig` — the boot with no config, where the artifact IS the deployment — now rejects a named local artifact that does not exist, naming the path and which source named it. The loader keeps its tolerance, so the config-boot path #4085 fixed is untouched, and `createStandaloneStack` is not touched at all. Two more corrections in the same spirit: - `serve`'s "config not found" refusal never said WHERE it looked. The two things that actually happen are a typo'd filename and the wrong working directory, and the second is the common one. It now names the config path, the artifact path, and that OS_ARTIFACT_PATH is unset — and still refuses rather than inventing a zero-object platform, pointing at `objectstack start` for a boot that is app-less on purpose. - That refusal was being TRUNCATED. `this.exit(1)` unwinds to oclif's `process.exit`, which does not drain a piped stdout, so a diagnostic split across several `console.log` calls loses its tail: measured, only the first two lines survived — i.e. exactly the "where to look" part went missing. Both of serve's pre-flight refusals emit one write now. The e2e added here caught it; review had not. Also fixes a test that pinned the wrong thing: `the DefaultDatasourcePlugin precedes ObjectQLPlugin (schema sync needs the driver)` asserted an ARRAY INDEX with a rationale the kernel does not implement — the connect happens in `init()`, and the dependency graph hoists ObjectQLPlugin ahead of the datasource plugin (measured: 6 slots earlier). It now pins the declared dependency that actually orders the two inits, which deleting the array position cannot break and deleting the declaration does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ve9HidCGRGtS2UjNPUGHSV
…act-startup-cgy5aq
createStandaloneStack describes the order the kernel actually resolves (#4131)#4110 follow-up) The artifact resolution-priority line reads as one fall-through list, so `--artifact` / `OS_ARTIFACT_PATH` look like they degrade to `<cwd>/dist/objectstack.json` when they do not resolve. They never did: a named source is used as given, without an existence check. That gap is what let #4110's "an absent artifact is not fatal" reach a case it was never meant to cover, so the doc now states the rule it always followed — named is used as-is and must exist, the fall-through is for the conventional locations only — alongside the fail-loud behaviour restored in this PR. Worth recording where the invariant was already written down: `os start`'s `resolveArtifactSource` deliberately does NOT exit on a missing `--artifact`, with the comment "Defer to serve.ts which already prints a precise error". #4110 silently removed that error; this PR restores what `os start` documents it depends on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ve9HidCGRGtS2UjNPUGHSV
#4110 / #4085 的跟进。一条原则贯穿全部改动:「平台可以没有应用」是关于能力的陈述,它不授权把「缺失的、被指名的输入」猜成「你是想空启动」。
一、我在 #4110 留的洞:被指名的 artifact 缺失会静默空启动
#4110 让「artifact 不存在」在整条链路上都不致命(
loadArtifactBundle记日志返回 null、MetadataPlugin空状态启动)。对约定位置<cwd>/dist/objectstack.json是对的 —— 那只是「还没编译」。但OS_ARTIFACT_PATH/{ artifactPath }按设计跳过存在性检查(交给 loader 惰性校验),于是宽容度也漏给了它们:整个启动输出里那个路径出现 0 次(实测)。serve 的静默启动窗口会丢弃 loader 那句平静的 ENOENT 行 —— 因为 #4110 把它从
warn(会被 #4084 缓存回放)改成了log(被丢弃)。#4110 之前这个 case 是大声死掉的,所以这是我引入的退步。判据是「被指名 vs 约定」,不是 errno。
createDefaultHostConfig(无 config 的启动,artifact 就是整个部署)现在对「被指名的本地 artifact 不存在」抛错,指名路径和指名它的来源。loader 的宽容度不动,createStandaloneStack完全不碰 —— #4085 修好的 config 启动路径不受影响。二、
serve的「找不到」现在说出它找过哪里两种真实情况是文件名拼错和cwd 不对,后者更常见。现在给出 config 绝对路径、artifact 绝对路径、以及
OS_ARTIFACT_PATH is not set.,并指向objectstack start(那才是有意的无应用启动)。行为不变:仍然报错退出。
os serve是被要求去加载某个东西的;凭空变出一个零对象平台会把错误藏在一个跑着的服务器后面 —— 正是 #4085 的形状。三、这条报错原本会被截断(测试逮到的,不是 review 逮到的)
新加的 e2e 第一次跑就红了:输出只剩前两行。
this.exit(1)走到 oclif 的process.exit,不等管道 stdout 排空,多次console.log的尾巴丢失 —— 丢掉的恰好是「去哪儿找」。这就是 #4012 的形状。两处 pre-flight 报错都改成一次 write。其中端口占用那条的验证状态我说明白:靠构造保证(一次 write),没有 driven test —— 触发它需要生产模式下占用端口,我没为它写 e2e。
四、#4131 第一步:顺序声明说实话(含一条钉错东西的测试)
createStandaloneStack的注释声称defaultDatasourcePlugin的数组位置 "MUST precede ObjectQLPlugin: its start() connects the default driver"。两处都不对:连接在init();而依赖图会把 ObjectQLPlugin 拓扑提前到它前面(实测 resolved init:objectql #10、default-datasource #16)。更值得修的是:
standalone-stack.test.ts里那条测试断言的正是这个数组下标,理由写着「schema sync needs the driver」—— 和假注释同一个错误信念。有人把真契约破坏掉(把 connect 从init()挪到start()),它照样绿。现在改成钉真正定序的那条声明依赖:删掉数组位置它不会响,删掉dependencies它会响。真实契约(phase 切分 + 声明依赖)本来就记在
default-datasource-plugin.ts的 "Ordering — phase, not list position" 里,那段还记着当初教会我们这件事的「shipping a server with no tables」。#4131 记录的是病根:同一子系统被同类顺序 bug 咬过两次,两次的修法都是写注释。验证
packages/runtime/src/standalone-stack.test.ts—— 18 passed(12 → 18):OS_ARTIFACT_PATH缺失 → 抛错且指名路径;artifactPath缺失 → 抛错;requireArtifact: false下依然抛错(「artifact 可选」≠「忽略你指名的那个」);No artifact source available报错(守卫不得吞掉它);http(s)://不做文件系统检查。packages/cli/test/serve-no-artifact.e2e.test.ts—— 4 passed(驱动真实os serve,全文件无os compile):无 artifact 的应用能启动并出现在已启动插件集合里 / 裸export default {}能启动 / 无 config 无 artifact 时拒绝并说出找过哪里且不曾 ready / 无法注册的 app 不拖垮平台且打印原因。反向验证:撤掉
default-host.ts的守卫 → 3 条新测试失败、其余 15 条不变;截断那条则是 e2e 先红后绿。自动化:
pnpm build全量通过(含 tsc);@objectstack/runtime926 passed、@objectstack/cli881 passed;改动文件 ESLint 无告警。base 已合入最新 main(含 #4133 对 #4116 ledger 的修复),本地check:wildcard-fallthrough在合并后的树上✓ 7 yielding / 0 ratcheted / 5 exempt。未纳入
不新增
--empty之类用户面开关:OS_BOOT_EMPTY=1已存在、os start已覆盖「有意的无应用启动」。裸os serve不合流到空启动 —— 理由见第二节。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ve9HidCGRGtS2UjNPUGHSV