You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 55a00ba
Browse filesBrowse the repository at this point in the historyBrowse files
fix(metadata-protocol): an object save or publish whose storage sync is refused says so on its answer (#30691)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DDUHyfgsW9WLX5BxP3bK55c)_
domain:server#5 · os-elon-musk · session_016WV4cyh75wtqD3bprL8aLT
Fixes#30635
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:保存对象(发布模式)和单项发布在建表/改表被数据源拒绝时,照旧答 200 成功,但答复的 `advisories` 里多一条
`warning`(规则 `object-storage-sync-refused`),写明哪个对象的存储没跟上、怎么修。
- 为什么改:实测 main(`47c457b2`):数据源拒绝同步时两个入口都答
200,答复里没有任何迹象,管理员要等读写失败才发现表没建;原因只在服务器日志里。
- 风险与回滚:只在同步被拒时加一条提示;状态码、其它键、发布结果都不变,同步正常时答复字节不变。数据源自带的拒绝原文只在它声明为 4xx
拒绝时才引用,否则提示「原因在服务器日志」,与同类 200 报告一致。回滚即 revert 本 PR。
- 先看:`packages/metadata-protocol/src/protocol-registry-hydration.ts` 的
`objectStorageSyncRefusedNotice` 与 `ensureObjectStorage` 的
catch;`protocol-save.ts` 发布分支;`protocol-history-and-publish.ts` 的
`runPublishSideEffects`。
- 两分钟验证:`pnpm --filter @objectstack/metadata-protocol exec vitest run
src/protocol.publish-side-effects-canonical-type.test.ts`,预期 12
passed(其中 #30635 六条)。
- 需要你做的:无,席位落地。
## What changes
- **`protocol-registry-hydration.ts`.** `ensureObjectStorage` now
returns what it found: `undefined` when the sync ran (or the type has no
storage), and one `RuntimeAuthoringIssue` when the driver refused it.
The new exported `objectStorageSyncRefusedNotice(name, err)` builds it:
`rule` `object-storage-sync-refused` (exported as
`OBJECT_STORAGE_SYNC_REFUSED`), `severity` `warning`, `where` `object
"NAME"`, `path` empty (the finding is about the item as a whole), a
`message` naming the object and saying its storage was not created or
altered, and a `hint` naming the two remedies the log line already names
(publish again, or restart). The #22985 `error` line is unchanged, byte
for byte, and still prints first.
- **The driver's text.** It is quoted on the advisory only when the
driver declared a client refusal (a 4xx `status`), through
`clientFacingFailureText`, the rule every side-effect report that rides
a 2xx on these doors already follows (the materializer's
`materializeApplied.error`, the batch's `failures[].error`). Any other
failure says the reason is in the server log, where the `error` line
carries it whole.
- **Save door** (`protocol-save.ts`, the `mode === 'publish'` branch):
the advisory is appended to `saveNotices`, after the gate's findings and
the save's own notices, so it rides the existing omitted-when-empty
`advisories` key.
- **Publish door** (`protocol-history-and-publish.ts`):
`runPublishSideEffects` returns it as `storageRefused`, and
`publishMetaItem` appends it to the same omitted-when-empty
`advisories`.
- **`protocol.ts`**: the two private pass-through signatures only
(`ensureObjectStorage`'s return type, and one `storageRefused?` line on
`runPublishSideEffects`' declared result). No body line.
- **`.changeset/30635-object-save-reports-refused-storage-sync.md`**:
`@objectstack/metadata-protocol` patch.
No new key on any response schema: `SaveMetaItemResponseSchema` and
`PublishMetaItemResponseSchema` already declare `advisories`
(`RuntimeAuthoringIssue[]`, `severity` `warning` or `info`, `rule` a
free string), and the save already uses it for a non-gate fact about the
write (`withheld-key-dropped`). The status code is unchanged and the
publish is still not failed.
## Tests
`protocol.publish-side-effects-canonical-type.test.ts` gains a top-level
`#30635` block in the file's existing harness (its stub engine already
refuses a named object's sync, for #22985):
1. save door, refused sync: `success` and `state` `active`, exactly one
`object-storage-sync-refused` warning with `path` empty and `where`
`object "ticket"`; the undeclared driver text is not on it and the
message points at the server log; the `error` line still carries the
text.
2. save door, a declared (409) driver refusal: its text is quoted on the
advisory.
3. CONTROL save door, clean sync: no `advisories` key at all.
4. CONTROL save door, draft save: no sync runs and no `advisories` key.
5. publish door, refused sync: `success`, exactly one warning, the
declared text quoted.
6. CONTROL publish door, clean sync: no `advisories` key at all.
**Before (measured at `origin/main` `47c457b2`):** cases 1, 2 and 5 red
(`expected undefined to match object`: a refused sync answered `success:
true` with no `advisories` entry at all), the three controls green.
**After:** the file 12/12. Whole package on the final head `e01c8939`:
`pnpm --filter @objectstack/metadata-protocol test` 317 files passed, 4
skipped; 22460 tests passed, 26 skipped. `typecheck` exit 0.
**One more test file touched.**
`protocol.runtime-authoring-gate.test.ts`'s engine double had no
`syncObjectSchema`, so every publish in it ran a sync that threw
(`syncObjectSchema is not a function`), which on `main` printed the
#22985 `error` line unnoticed and now also adds the advisory; two of its
pins assert "no advisories key" and went red. The double now has a sync
that succeeds, so those pins read the gate's advisories alone. (The real
engine always declares `syncObjectSchema`, `host-engine.ts`.) The
`ensureObjectStorage` guard keeps its bare `return;`, which
`protocol.object-registry-write-through-spelling.test.ts` counts in the
source.
<details><summary>Ablations: each through scripts/ablation-replace.mjs,
anchor 1 to 0, restored to the HEAD blob, git diff HEAD empty</summary>
| mutation | result |
|:---|:---|
| delete the save door's `saveNotices.push(storageRefused)` | cases 1
and 2 red, 10 green |
| delete the publish door's `response.advisories = [...,
effects.storageRefused]` | case 5 red, 11 green |
| quote the raw driver text instead of `clientFacingFailureText` | case
1 red (the undeclared text reached the body), 11 green |
The test imports `./protocol.js` from source, so no `dist/` leg applies.
</details>
## Acceptance notes
- **Not covered here, out of this card's file surface: the package-draft
batch publish** (Studio's "publish whole app", `publishPackageDrafts`).
It also runs the sync through `runPublishSideEffects`, and now receives
`storageRefused`, but its body lives in `protocol.ts` (hot file, codemod
in flight) and builds each `published[]` element before the side effects
run, so reporting it there needs body edits. Until then that door
reports a refused sync in the server log only, as before; the changeset
says so. Handed to the seat.
- **Follow-up, spec lane:** the `advisories` describe in
`packages/spec/src/api/protocol.zod.ts` (save and publish response
schemas) should name the new rule beside `withheld-key-dropped`.
- **Follow-up, ui:** showing this warning after a save in Studio/Setup
is a separate card, as the issue says.
- **The data-loss refusal from #30632 (landed on `main` as `8d7f9065`
while this PR was open) is withheld on the advisory as it stands:**
`ExistingTableAdoptionRefusal` declares no `status`, so the advisory
says the reason is in the server log rather than quoting its remedy
text. If that prose should reach the caller, the driver declares it a
client refusal (a 4xx `status`); that is the driver lane's call, not
this PR's.
- **Deviation from the dispatch wording:** the dispatch said the message
names the driver's refusal text. It does so only for a declared client
refusal, by the existing withhold rule above; quoting every driver text
would put raw driver failures on a 2xx body, which the rule exists to
prevent.
## Verification
Local, on head `e01c8939` (merged with `origin/main` `b77c160a`):
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 60 families derived; `--ran`: 58 run, all
exit 0; 2 NOT MEASURED (`check:dual-build-cjs-loads`,
`check:lean-entry-closure`: exit 3, PREREQUISITE NOT MET, they need a
whole-tree build; CI runs them).
- `pnpm lint`: exit 0.
- `pnpm --filter @objectstack/metadata-protocol test`: exit 0;
`typecheck`: exit 0.
- Re-run after the last commit: `check:engine-double-contract`,
`check:durability-log-level`, `check:issue-citations`,
`check:doc-authoring`, `check:nul-bytes`: exit 0.
Then `origin/main` `a2437b56` was merged (head `9c02a3e7`). It brings
`objectql`, `driver-sql`, `rest` and `plugin-auth` commits and nothing
under `packages/metadata-protocol`, none of which this package imports,
so the local readings above were not re-run (AGENTS.md multi-agent §10,
scoped re-check). Everything else is CI's.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cyh75wtqD3bprL8aLT)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(metadata-protocol): an object save or publish whose storage sync the datasource refuses now says so on its answer
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
`PUT /api/v1/meta/object/NAME` (a publish-mode save) and `POST /api/v1/meta/object/NAME/publish` sync the object's table after the item is written. When the datasource refused that sync, both answered `200` success with nothing about storage, and only the server log said the table was not created or altered. Both answers now carry one `warning` on their existing `advisories` key, with the rule id `object-storage-sync-refused`, naming the object and saying its storage did not follow. The datasource's own text is quoted when it declared a client refusal (a 4xx status); otherwise the advisory says the reason is in the server log, which keeps the `error` line it already printed. The status code and every other key are unchanged, the publish is still not failed, and an answer whose sync ran has no new key.
11
+
12
+
A client that groups or suppresses advisories by rule id sees one new id. The package-draft batch publish (Studio's "publish whole app") still reports a refused sync in the server log only.
0 commit comments