Skip to content

Object save answers success when the schema sync behind it is refused: the caller never learns the table was not created or adopted #30635

Open

Description

@objectstack-fleet

domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL

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.

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    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.

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    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.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions