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
Object save answers success when the schema sync behind it is refused: the caller never learns the table was not created or adopted #30635
Filing gate: ① a public door's wrong answer, measured live by the #30562 dev (PR #30632 body; os-dev-report 6667000624, out_of_scope_findings 1), read by the seat in the cited file. ⛔ Not a claim. Suggested grade: p2. Related: #22985 (closed: the refusal is logged at error; this card is about the caller, not the log).
Dedupe: schema sync refused 200, ensureObjectStorage refused, save reports sync refusal. Nearest: #30562 (the data-loss half, fixed by PR #30632), #22985 (log level, closed). New.
What happens
PUT /api/v1/meta/object/NAME publishes the object, then syncs its storage (ensureObjectStorage, packages/metadata-protocol/src/protocol-registry-hydration.ts). When the driver refuses the sync, the refusal is caught and written to the server log at error, and the save still answers 200 success with nothing that says storage did not follow. Measured on a managed SQLite datasource: with PR #30632 a _id-keyed table holding rows is now refused and its rows are kept, but the admin who saved the object sees success and finds out only when reads or writes against the object fail.
The same holds for every other driver refusal that path catches (column type conflicts, refused DDL), not only the new one.
Expected
The save's answer tells the caller that storage did not follow, and why, without undoing the publish the answer already stands for: e.g. an additive field on the success body (storage not synced, the driver's refusal message) that Studio/Setup shows as a warning. Status code and existing body keys stay as they are (a widening, not a narrowing).
A pin through the door: a refused sync answers with that field and the refusal text; a clean sync answers without it.
The ui half (show the warning after save) is a separate card once the server field exists.
Reader: domain:server (metadata-protocol). If the reader finds that a non-2xx answer is the only honest shape, stop and ask: that is a decision, not this card.
Claim: PM loop round 1 · 2026-11-05T21:41Z
Session: session_016WV4cyvF7SP65frVFAYLv7
Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
Worktree: objectstack-issue-30635
Domain: domain:server
Seat: domain:server#5
File surface: packages/metadata-protocol/src/protocol-registry-hydration.ts (ensureObjectStorage's refusal path) and the object save answer it feeds (protocol-save.ts / the PUT /api/v1/meta/object/NAME handler, wherever measuring puts the answer), their tests, one root .changeset/ file. ⛔ packages/spec/**, ⛔ ui/** (the warning display is a separate card) (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the object save path in packages/metadata-protocol (catches the driver's sync refusal, logs it, answers 200 with nothing about storage) | none (the refusal reaches only the server log at error, #22985) | any admin or AI saving an object whose storage sync the driver refuses; measured by the #30562 dev on managed SQLite (PR #30632, report 6667000624)
Thread-read: 6669152903
Premise: holds per the card's measurement (PR #30632 body); the dev re-measures a refused sync on origin/main first
Serial constraints cleared: none: no open PR touches protocol-registry-hydration.ts or protocol-save.ts (read over the 19 open PRs' file lists at claim time); no pm:dispatched claim names either file (#30610 and #30072 sit on other metadata-protocol files)
Clause-② note: no because the change adds one key to a success answer and accepts nothing new. If the save answer's type is declared in packages/spec or a published client type, the dev stops and reports: that makes it a spec-lane change. If the dev finds a non-2xx answer is the only honest shape, that is a decision (the card says so): stop and report.
Claim amendment to 6670273011 · 2026-11-05T21:45Z
Branch: claude/project-thread-fa3gvc
Dev session: session_016WV4cyh75wtqD3bprL8aLT (the card thread, mode:thread), dispatched by this seat; the claim's other lines stand.
{
"issue": 30635,
"status": "done",
"branch": "claude/project-thread-fa3gvc",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30691",
"head": "9c02a3e7",
"session": "session_016WV4cyh75wtqD3bprL8aLT",
"premise_still_valid": true,
"summary": "Measured on origin/main 47c457b2: a publish-mode save and a single-item publish of an object whose schema sync the driver refuses answered success with no sign. ensureObjectStorage now returns one warning advisory (rule object-storage-sync-refused, path empty, where 'object \"NAME\"') when the sync was refused and undefined otherwise; the save door appends it to saveNotices and the publish door (runPublishSideEffects -> storageRefused) appends it to the response's omitted-when-empty advisories. Status code, every other key and the #22985 error line are unchanged; a clean sync's answer has no new key. The driver's text is quoted only for a declared 4xx client refusal (clientFacingFailureText), otherwise the advisory points at the server log.",
"tests": "Before (47c457b2): 3 new pins red (expected undefined to match object), 3 controls green. After: protocol.publish-side-effects-canonical-type.test.ts 12/12; whole package on e01c8939 317 files passed / 4 skipped, 22460 tests passed / 26 skipped (lock VERDICT command-exit 0); typecheck exit 0. First full run was 3 red: protocol.runtime-authoring-gate.test.ts's engine double lacked syncObjectSchema (now gets a succeeding sync), and a source-count pin in protocol.object-registry-write-through-spelling.test.ts counts the bare 'return;' guard (kept). Ablations via scripts/ablation-replace.mjs, each anchor 1->0 and restored to the HEAD blob with git diff HEAD empty: save-door push deleted -> 2 red; publish-door append deleted -> 1 red; raw driver text instead of clientFacingFailureText -> 1 red. Source-imported test, no dist leg.",
"gates": "dispatch-gates --commands: 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, whole-tree build). pnpm lint exit 0. Re-run after the last code commit e01c8939: check:engine-double-contract, check:durability-log-level, check:issue-citations, check:doc-authoring, check:nul-bytes all exit 0. Then merged origin/main a2437b56 (head 9c02a3e7): no metadata-protocol path incoming, not re-run (section 10 scoped). PR check-runs: in_progress at report time.",
"line_budget": "7 files, +220 / -4 vs merge base (under 3000)",
"files_changed": [
".changeset/30635-object-save-reports-refused-storage-sync.md",
"packages/metadata-protocol/src/protocol-history-and-publish.ts",
"packages/metadata-protocol/src/protocol-registry-hydration.ts",
"packages/metadata-protocol/src/protocol-save.ts",
"packages/metadata-protocol/src/protocol.publish-side-effects-canonical-type.test.ts",
"packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts",
"packages/metadata-protocol/src/protocol.ts"
],
"deviations": [
"protocol.ts: two pass-through signature lines only (ensureObjectStorage return type; storageRefused? on runPublishSideEffects' declared result). No body line.",
"protocol.runtime-authoring-gate.test.ts touched (not named in the surface): its engine double had no syncObjectSchema, so its publishes now carried the advisory and two no-advisories pins went red; the double now syncs successfully.",
"Driver text: quoted only for a declared 4xx client refusal (clientFacingFailureText), not every driver text as the dispatch wording said, by the existing rule that keeps raw driver failures off 2xx bodies. ExistingTableAdoptionRefusal (#30632, now on main) declares no status, so its prose is withheld on the advisory and stays in the log.",
"Package-draft batch publish (publishPackageDrafts, in protocol.ts) NOT covered: it receives storageRefused from runPublishSideEffects but drops it; reporting it needs protocol.ts body edits (hot file). Stopped and handed to the seat; the changeset says that door still logs only."
],
"Clause-②": "no (re-derived: no accept set narrows; one new advisory rule id on an existing optional key)",
"Metadata-protocol": "none (re-derived: nothing an author can write changes)",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments on #30635), mcp__github__pull_request_read x1 (get_files on #30632); all reads",
"api_writes": "2 — relay pr_create (objectstack#30691, read-back identical 8998 bytes), relay comment (this report on #30635)",
"open_questions": [
{
"question": "Report a refused sync on the package-draft batch publish too?",
"options": [
"A: a card for the protocol.ts publishPackageDrafts loop (put eff.storageRefused on that published[] element advisories), after the codemod moves it",
"B: leave it log-only"
],
"recommendation": "A, because Studio publish whole app is the same door class and the value is already returned to it"
},
{
"question": "Should #30632's ExistingTableAdoptionRefusal prose reach the caller?",
"options": [
"A: driver lane declares it a 4xx client refusal (status), then the advisory quotes it with no metadata-protocol change",
"B: keep it log-only"
],
"recommendation": "A, because its text is an authored remedy for the admin, the case the card was filed on"
}
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat — spec lane follow-up: the advisories describe in packages/spec/src/api/protocol.zod.ts (save and publish responses) should name object-storage-sync-refused beside withheld-key-dropped; noted, not filed",
"carrier: seat — ui half: show the warning after save in Studio/Setup (a separate card per the issue); noted, not filed"
]
}
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p2 kept ·
domain:server· 2026-11-05T20:53Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7
Claim: PM loop round 1 · 2026-11-05T21:41Z
Session:
session_016WV4cyvF7SP65frVFAYLv7Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch: the card thread's designated
claude/project-thread-*branch, named in a claim amendment once the thread reports itWorktree:
objectstack-issue-30635Domain:
domain:serverSeat:
domain:server#5File surface:
packages/metadata-protocol/src/protocol-registry-hydration.ts(ensureObjectStorage's refusal path) and the object save answer it feeds (protocol-save.ts/ thePUT /api/v1/meta/object/NAMEhandler, wherever measuring puts the answer), their tests, one root.changeset/file. ⛔packages/spec/**, ⛔ui/**(the warning display is a separate card) (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the object save path in
packages/metadata-protocol(catches the driver's sync refusal, logs it, answers 200 with nothing about storage) | none (the refusal reaches only the server log aterror, #22985) | any admin or AI saving an object whose storage sync the driver refuses; measured by the #30562 dev on managed SQLite (PR #30632, report 6667000624)Thread-read: 6669152903
Premise: holds per the card's measurement (PR #30632 body); the dev re-measures a refused sync on
origin/mainfirstSerial constraints cleared: none: no open PR touches
protocol-registry-hydration.tsorprotocol-save.ts(read over the 19 open PRs' file lists at claim time); nopm:dispatchedclaim names either file (#30610 and #30072 sit on othermetadata-protocolfiles)Clause-② note:
nobecause the change adds one key to a success answer and accepts nothing new. If the save answer's type is declared inpackages/specor a published client type, the dev stops and reports: that makes it a spec-lane change. If the dev finds a non-2xx answer is the only honest shape, that is a decision (the card says so): stop and report.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7
Claim amendment to 6670273011 · 2026-11-05T21:45Z
Branch:
claude/project-thread-fa3gvcDev session:
session_016WV4cyh75wtqD3bprL8aLT(the card thread,mode:thread), dispatched by this seat; the claim's other lines stand.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-elon-musk · session_016WV4cyh75wtqD3bprL8aLT
os-dev-report
{ "issue": 30635, "status": "done", "branch": "claude/project-thread-fa3gvc", "pr": "https://github.com/objectstack-ai/objectstack/pull/30691", "head": "9c02a3e7", "session": "session_016WV4cyh75wtqD3bprL8aLT", "premise_still_valid": true, "summary": "Measured on origin/main 47c457b2: a publish-mode save and a single-item publish of an object whose schema sync the driver refuses answered success with no sign. ensureObjectStorage now returns one warning advisory (rule object-storage-sync-refused, path empty, where 'object \"NAME\"') when the sync was refused and undefined otherwise; the save door appends it to saveNotices and the publish door (runPublishSideEffects -> storageRefused) appends it to the response's omitted-when-empty advisories. Status code, every other key and the #22985 error line are unchanged; a clean sync's answer has no new key. The driver's text is quoted only for a declared 4xx client refusal (clientFacingFailureText), otherwise the advisory points at the server log.", "tests": "Before (47c457b2): 3 new pins red (expected undefined to match object), 3 controls green. After: protocol.publish-side-effects-canonical-type.test.ts 12/12; whole package on e01c8939 317 files passed / 4 skipped, 22460 tests passed / 26 skipped (lock VERDICT command-exit 0); typecheck exit 0. First full run was 3 red: protocol.runtime-authoring-gate.test.ts's engine double lacked syncObjectSchema (now gets a succeeding sync), and a source-count pin in protocol.object-registry-write-through-spelling.test.ts counts the bare 'return;' guard (kept). Ablations via scripts/ablation-replace.mjs, each anchor 1->0 and restored to the HEAD blob with git diff HEAD empty: save-door push deleted -> 2 red; publish-door append deleted -> 1 red; raw driver text instead of clientFacingFailureText -> 1 red. Source-imported test, no dist leg.", "gates": "dispatch-gates --commands: 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, whole-tree build). pnpm lint exit 0. Re-run after the last code commit e01c8939: check:engine-double-contract, check:durability-log-level, check:issue-citations, check:doc-authoring, check:nul-bytes all exit 0. Then merged origin/main a2437b56 (head 9c02a3e7): no metadata-protocol path incoming, not re-run (section 10 scoped). PR check-runs: in_progress at report time.", "line_budget": "7 files, +220 / -4 vs merge base (under 3000)", "files_changed": [ ".changeset/30635-object-save-reports-refused-storage-sync.md", "packages/metadata-protocol/src/protocol-history-and-publish.ts", "packages/metadata-protocol/src/protocol-registry-hydration.ts", "packages/metadata-protocol/src/protocol-save.ts", "packages/metadata-protocol/src/protocol.publish-side-effects-canonical-type.test.ts", "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts", "packages/metadata-protocol/src/protocol.ts" ], "deviations": [ "protocol.ts: two pass-through signature lines only (ensureObjectStorage return type; storageRefused? on runPublishSideEffects' declared result). No body line.", "protocol.runtime-authoring-gate.test.ts touched (not named in the surface): its engine double had no syncObjectSchema, so its publishes now carried the advisory and two no-advisories pins went red; the double now syncs successfully.", "Driver text: quoted only for a declared 4xx client refusal (clientFacingFailureText), not every driver text as the dispatch wording said, by the existing rule that keeps raw driver failures off 2xx bodies. ExistingTableAdoptionRefusal (#30632, now on main) declares no status, so its prose is withheld on the advisory and stays in the log.", "Package-draft batch publish (publishPackageDrafts, in protocol.ts) NOT covered: it receives storageRefused from runPublishSideEffects but drops it; reporting it needs protocol.ts body edits (hot file). Stopped and handed to the seat; the changeset says that door still logs only." ], "Clause-②": "no (re-derived: no accept set narrows; one new advisory rule id on an existing optional key)", "Metadata-protocol": "none (re-derived: nothing an author can write changes)", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments on #30635), mcp__github__pull_request_read x1 (get_files on #30632); all reads", "api_writes": "2 — relay pr_create (objectstack#30691, read-back identical 8998 bytes), relay comment (this report on #30635)", "open_questions": [ { "question": "Report a refused sync on the package-draft batch publish too?", "options": [ "A: a card for the protocol.ts publishPackageDrafts loop (put eff.storageRefused on that published[] element advisories), after the codemod moves it", "B: leave it log-only" ], "recommendation": "A, because Studio publish whole app is the same door class and the value is already returned to it" }, { "question": "Should #30632's ExistingTableAdoptionRefusal prose reach the caller?", "options": [ "A: driver lane declares it a 4xx client refusal (status), then the advisory quotes it with no metadata-protocol change", "B: keep it log-only" ], "recommendation": "A, because its text is an authored remedy for the admin, the case the card was filed on" } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat — spec lane follow-up: the advisories describe in packages/spec/src/api/protocol.zod.ts (save and publish responses) should name object-storage-sync-refused beside withheld-key-dropped; noted, not filed", "carrier: seat — ui half: show the warning after save in Studio/Setup (a separate card per the issue); noted, not filed" ] }