Repository navigation
Commit e29a1b1
fix(objectql): an artifact hot reload refreshes every registry entry the boot registered, so an edited email template is what the next send renders (#27596)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu96YrKSitCWAfPtJGCAS6LD)_
Fixes #27488
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:`os dev` 热重载 artifact
后,引擎把它已装的包重新登记一遍,邮件模板、流程、仪表盘、页面、按钮等所有类型的列表读与发送读都跟上新版本。
- **为什么改**:开发者改了邮件模板源码,按名字读已经是新文案,但列表和实际发出的邮件还是旧文案,要冷重启才生效;流程热重载也绑的是旧定义。
- **风险与回滚**:只走开发热重载路径,冷启动和 `PUT /meta`
保存不受影响(有测试钉住);只刷新已装的包,禁用状态保留;回滚即还原 `packages/objectql/src/plugin.ts` 一处。
- **先看**:`packages/objectql/src/plugin.ts`(`refreshReloadedPackages` 与它在
`metadata:reloaded`
钩子里的调用)、`packages/runtime/src/standalone-stack-artifact-reload-registry-refresh.test.ts`、`packages/plugins/plugin-email/src/template-artifact-reload.integration.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/runtime exec vitest run
src/standalone-stack-artifact-reload-registry-refresh.test.ts`,预期 3
条全绿;去掉钩子里那一行调用则第一条转红,列表读停在 v1。
- **需要你做的**:无,席位落地。
## What changes
The boot registers an artifact twice: the artifact door
(`MetadataPlugin`) into the metadata service, and `AppPlugin` into the
SchemaRegistry through the `manifest` service. A hot reload
(artifact-file watcher or HMR POST, both `_reloadAndAnnounce`)
re-registered only the first, and ObjectQL's `metadata:reloaded` hook
re-ingested objects alone. The by-name read asks the metadata service
first, so it served the edit; the list read and its execution face
(`getMetaItemsForExecution`) lay the registry over the metadata service,
so they kept the boot copy until a cold restart. The email plugin
renders a send from that execution face, and the automation engine
re-binds flows from it on the same reload.
The hook now calls `refreshReloadedPackages` before the object ingest:
each package body on the payload that the registry already holds (source
`code` or `marketplace`) is re-registered through `registerApp`, the
body door the boot used, under its installed source, with
`resolveArtifactPackageEntries` for the order (the call the `manifest`
service makes). `installPackage` keeps the lifecycle on a re-install, so
a disabled package stays disabled. A package the boot never put in the
registry is left to the artifact door as before (nothing of it is stale
there). A body that fails to re-register is named in one `warn` line and
keeps its previous entries; the reload goes on.
Scope (the card's first deliverable): measured on the real
`createStandaloneStack` composition, every registry-backed type was
stale, not only `email_template`. With each type at v1 at boot and v2 in
the file, before the fix:
| type | list | execution face | by name |
|---|---|---|---|
| `email_template`, `dashboard`, `flow`, `page`, `action` | v1 | v1 | v2
|
| `object` | v2 | v2 | v2 |
So the fix refreshes every collection `registerApp` decomposes. That
changes no semantics for the other types: it makes their list and
execution reads agree with the by-name read, the same defect. The flow
re-bind on reload (service-automation's `metadata:reloaded` re-sync) now
binds the edited flow, which is what that re-sync was written for.
File surface: `packages/objectql/src/plugin.ts` only, plus a test in
`packages/runtime` and one in `packages/plugins/plugin-email`. No
`packages/spec`, no `objectql/src/engine.ts`. Branch is the card
thread's harness-pinned `claude/project-thread-mtq6wj` (the claim is the
identity record).
## Tests
-
`packages/runtime/src/standalone-stack-artifact-reload-registry-refresh.test.ts`
(new): the real `os dev` composition and the real reload body. Pins
list, execution face and by-name for six types after one and two reloads
(v1 positive control at boot = cold boot), the package record (source,
enabled, status) across a reload, and that an environment save through
`saveMetaItem` still wins on all three reads after a reload re-registers
the package beneath it.
-
`packages/plugins/plugin-email/src/template-artifact-reload.integration.test.ts`
(new): real `ObjectQLPlugin`, the app registered through the `manifest`
service at init as `AppPlugin` does, the artifact door's reload payload,
and a real `EmailService` send over the loader `EmailServicePlugin`
wires. The send renders the reloaded subject and the list serves it.
Red before the fix: plugin-email `expected 'Task done (v1): Ship it' to
be 'Task done (v2): Ship it'`; runtime: the five types above listed v1
on both list faces.
## Acceptance notes
- Items removed from the artifact stay registered until restart, the
lifecycle objects already have on this path. Not measured as a defect
here; noted only.
- A reload that adds a brand-new package to the artifact does not
install it into the registry (refresh only); the artifact door serves
it, as before.
## Verification
<details><summary>Ablation (fix commit 41d95a7): drop the refresh call,
both pins go red; restore, both green</summary>
`scripts/ablation-replace.mjs --hold` replaced
`this.refreshReloadedPackages(ctx, payload);` in
`packages/objectql/src/plugin.ts` with a marker statement (on-disk
counts: marker 1, call 0), then `pnpm --filter @objectstack/objectql
build`, and `scripts/ablation-dist-preflight.mjs objectql
'__ABLATE_27488'` found the marker in `dist/index.js` and
`dist/index.mjs` (the JS reached dist; the DTS step reported the
now-unused method, which does not change the JS the suites load).
- plugin-email pin: red, `expected 'Task done (v1): Ship it' to be 'Task
done (v2): Ship it'`.
- runtime pin: red, `1 failed | 1 passed (2)` (the reload case; list and
execution face back on v1).
Restore: `--restore` gave blob after restore == blob at HEAD
(`4aa979799172`), `git diff HEAD` empty; rebuilt, `--absent` preflight:
marker absent from all 14 built files, tree clean. Both pins green again
(`1 passed (1)`, `2 passed (2)`). The preservation pin for an
environment save was added after the ablation; it is a keep-alone pin,
green with the fix.
</details>
<details><summary>Suites (after merging origin/main at 37e2caf, closure
rebuilt)</summary>
- `pnpm --filter @objectstack/objectql test`: Test Files 486 passed
(486), Tests 9128 passed (9128).
- `pnpm --filter @objectstack/plugin-email test`: Test Files 45 passed
(45), Tests 747 passed (747).
- `pnpm --filter @objectstack/runtime exec vitest run
src/standalone-stack-artifact-reload-registry-refresh.test.ts`: 3 passed
(3). The rest of the runtime suite is left to CI (`Test Core`): the diff
adds one test file there and changes no runtime source.
- typecheck: `@objectstack/objectql`, `@objectstack/plugin-email` (incl.
`check:test-typecheck`), `@objectstack/runtime`: all exit 0.
- eslint `--no-inline-config` on the three changed files: exit 0.
</details>
<details><summary>Derived gates on head ba97ffc: 63 derived, 62 run
(all exit 0), 1 NOT MEASURED</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this tree, every command run with its
exit code captured before any pipe, then `--ran`: "63 derived famil(ies)
accounted for — 62 run, 1 NOT-MEASURED".
- NOT MEASURED: `pnpm check:dual-build-cjs-loads`, exit 3 (prerequisite:
35 workspace packages outside this diff's build closure have no `dist/`
in this container). The diff changes no export and no package entry;
CI's `Lint & Repo Gates` runs it on a full build.
- `check-plugin-teardown-shape --self-test` needed its pinned fixture
commit fetched into this shallow clone, then 48 cases pass.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9c51314 commit e29a1b1
4 files changed
Lines changed: 449 additions & 0 deletions
File tree
- .changeset
- packages
- objectql/src
- plugins/plugin-email/src
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1100 | 1100 | | |
1101 | 1101 | | |
1102 | 1102 | | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
1103 | 1110 | | |
1104 | 1111 | | |
1105 | 1112 | | |
| |||
1376 | 1383 | | |
1377 | 1384 | | |
1378 | 1385 | | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
1379 | 1451 | | |
1380 | 1452 | | |
1381 | 1453 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
0 commit comments