Skip to content

objectql: the unlanded-write refusal from #29291 leads with an inner object on a nested cascade, reaches 500 by message heuristics, and claims "rolled back" when the rollback itself failed #29421

Closed

Description

@objectstack-fleet

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

Filing gate: carrying three should-fix findings from the CONTRACT_REVIEW_TIER record 6579773378 on PR #29381 (#29291), head 2af3815d. None is a must-fix, and the seat lands #29381 without them. Filed by domain:server seat 3. ⛔ Not a claim. Suggested-grade: p3.

What is wrong (after #29381 lands)

unlandedWriteRefusal, cascadeStoppedRefusal and rolledBackRefusal are in packages/objectql/src/summary-errors.ts. They are called from runByIdDelete, the predicate delete arm and ObjectQL.transaction in engine.ts.

  1. A nested cascade leads with the inner object. Suppose a grandchild refuses. The child's own runByIdDelete converts first, naming the child. The parent's walk then receives a code-less error and passes it through. So the parent's caller reads The delete of "child" did not complete, and the action door relays that sentence to the client. Status and rows are right; only the lead is wrong.
  2. The data door's 500 rests on phrasing. The converted Error reaches 500 INTERNAL_ERROR only because its text falls through every heuristic in classifyDataError (packages/types/src/data-error-classification.ts). That includes a documented double-quote dodge of the unknown-object rule. Declaring status: 500 on the converted error would take the structural arm and stop depending on wording.
  3. "Rolled back" can be false. ObjectQL.transaction swallows a rollback failure and still converts to "The transaction rolled back, so nothing written inside it was stored". ScopedContext.transaction does not swallow, so only ObjectQL.transaction is affected.

Wanted

Declaration expected: Clause-②: no, Metadata-protocol: none, unless (2) changes a status, which it should not.

Dedupe: searched unlandedWriteRefusal, cascadeStoppedRefusal; nearest #29291. New.

Links back: #29291, #29381.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p3 · domain:server · pm:queue · 2026-11-02T14:47Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop · card thread · family #29422 + #29421 (summary-refusal answers after #29381) · 2026-11-02T15:57Z

    Branch: pending (one shared branch for the family, named after the head card #29422; a "branch named" claim supersedes this one)
    Clause-②: no
    Metadata-protocol: none

    Handed over by seat 3 (at its cap); #29291 / PR #29381 has landed (32912139), so nothing serial holds this. Order: #29422 first (measure through a real door, pin red, then make the outer caller's answer describe the outer write); #29421's three should-fixes in the same PR, one commit each. One PR, a Fixes line per card.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop · card thread · family #29422 + #29421 · branch named · 2026-11-02T15:59Z (supersedes the pending-branch claim above)

    Branch: claude/project-thread-vzrurx (shared by the family; session cse_01ADHkmPCSqt8uDw17RmX9hu)
    Clause-②: no
    Metadata-protocol: none

    Order: as in the claim above. One PR, a Fixes line per card, one commit per card.

  4. added a commit that references this issue on Nov 2, 2026
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmPCSqt8uDw17RmX9hu
    os-dev-report

    {
      "issue": 29421,
      "family": [
        29422,
        29421
      ],
      "status": "done",
      "branch": "claude/project-thread-vzrurx",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29519",
      "head": "b81dcfa4fa0d3f7ab32be0f586e32481ff31132d",
      "session": "session_01ADHkmPCSqt8uDw17RmX9hu",
      "premise_still_valid": true,
      "summary": "#29422: measured on main 8ca9dd7b through the REST data door. POST of a record whose afterInsert hook wrote a line through ctx.api (line roll-up refusing) answered 201 with the LINE's id and row, for a metadata-bound hook (unit committed both rows) and a code hook alike. Fixed per seat option A at the one seam a handler's error leaves (triggerHooks + the unscoped multi-write dispatch): hookStoppedRefusal converts only the ERR_SUMMARY_RECOMPUTE family into an internal-failure refusal leading with the outer write (cause kept). The aborting after* unit now rolls both rows back; with no unit the text is hedged; before* is covered by the same seam; onError 'log' is unchanged. #29421: the converted error is a module-private UnlandedWriteRefusal that cascade walks and hooks re-lead (a three-level cascade names the root, cause = original at every depth). It declares status 500 + code INTERNAL_ERROR (seat option B): data-door body byte-identical, action door 400 -> 500 with the same sentence. ObjectQL.transaction says rollbackFailedRefusal (not 'rolled back') when its rollback threw.",
      "tests": "objectql full suite + typecheck under the lock at b81dcfa4: Test Files 536 passed (536), Tests 10398 passed (10398), typecheck exit 0. New pins: engine-hook-stale-summary-refusal.test.ts (7), engine-unlanded-refusal-lead.test.ts (7), rest data-write-hook-stale-summary-refusal.test.ts (3, real SqlDriver); door suites rest 3 files/10 tests, runtime action pin 2/2 green. Ablations via ablation-replace (restored, git diff HEAD empty each): hook seam -> 4 failed|3 passed ('expected ERR_SUMMARY_RECOMPUTE to be undefined'); re-lead -> 2 failed|5 passed (reads 'The delete of \"line\"'); rollback hedge -> 1 failed|6 passed (reads 'rolled back'); code dropped (dist rebuilt, preflight marker present) -> data-door pins 3 failed ('{ error } to deeply equal { error, code }'); status+code dropped (=#29381 shape, dist rebuilt) -> data-door 5/5 green (byte-identity vs old path), action pin 1 failed ('expected 400 to be 500'); restore leg: rebuilt, preflight --absent both markers.",
      "gates": "dispatch-gates --commands at b81dcfa4: 63 derived, all run; --ran reconciliation exit 0 '63 run, 0 NOT-MEASURED'. First-pass exit 3 x2 re-run green: check-plugin-teardown-shape --self-test (pinned fixture 621a4876 fetched), check:type-check-debt (queued past budget behind this branch's dogfood lock; re-run OK, 0 raw errors). Lint: root eslint --no-inline-config on packages/objectql/src + 3 door test files, exit 0, no output. CI check-runs: in_progress at report time (not waited on).",
      "dogfood_battery": "b81dcfa4 · os-verify-lock: VERDICT command-exit 0 (held 39m04s) · Test Files 397 passed | 3 skipped (400) · Tests 3277 passed | 28 skipped (3305) · no failures",
      "mcp_calls": "4 — mcp__github__issue_read (get x2, get_comments x2); reads only",
      "api_writes": "3 — fleet-write relay pr_create (#29519, 12052 bytes stored identical); post-stamped os-dev-report comment on #29422 and on #29421",
      "files_changed": [
        ".changeset/29422-hook-write-stale-summary-refusal.md (+21)",
        "packages/objectql/src/summary-errors.ts (+95/-12)",
        "packages/objectql/src/engine.ts (+22/-5)",
        "packages/objectql/src/engine-hook-stale-summary-refusal.test.ts (+221, new)",
        "packages/objectql/src/engine-unlanded-refusal-lead.test.ts (+225, new)",
        "packages/objectql/src/engine-cascade-summary-rollback.test.ts (+10/-5, forced)",
        "packages/rest/src/data-write-hook-stale-summary-refusal.test.ts (+149, new)",
        "packages/rest/src/data-delete-cascade-summary-rollback.test.ts (+4/-2, tightened to the whole body)",
        "packages/runtime/src/action-delete-cascade-summary-rollback.test.ts (+7/-5, forced 400 -> 500)"
      ],
      "line_budget": "+754/-29 over 9 files; runtime source +117/-17 (summary-errors.ts, engine.ts), the rest tests and the changeset; well under the 3000-line class",
      "deviations": [
        "File surface beyond packages/objectql/src/**: packages/rest (new door pin + byte-identity tightening) and packages/runtime (forced action pin 400 -> 500), both asked for by the seat's option-B conditions; packages/types untouched.",
        "Commit shape: one fix commit per card (61f49fa5 #29422, c594fd01 #29421) plus a third commit with the REST door pin for #29422 (54eb5656), the changeset commit and the origin/main merge b81dcfa4.",
        "Declarations: the cards' Claim lines still read 'Clause-②: no'; the PR body and changeset carry 'Clause-②: no (narrowing)' per the seat's option-A amendment, with '!' and full dogfood.",
        "PR assignee/reviewer not set: the session's harness asks for an MCP issue_write/update_pull_request, which the seat's standard order (no MCP writes) and os-dev (no PR assignees) forbid.",
        "Two gates first ran exit 3 and were re-run green (see gates)."
      ],
      "open_questions": [
        {
          "question": "The pending #29381 changeset (.changeset/29291-cascade-summary-refusal-rolled-back.md) says the converted error has 'no code'; after #29421 it declares 500 INTERNAL_ERROR. Correct it in place or leave it superseded?",
          "options": [
            "A leave it; this PR's changeset states that it supersedes that sentence (done)",
            "B edit it in place as a DELIBERATE CORRECTION, which reds check-empty-changeset by design and needs a PR-body declaration"
          ],
          "recommendation": "A, because both changesets ship in the same release, the new one names the superseded sentence, and B adds a red gate for a wording the release notes already correct"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": []
    }

    Stamped 2026-11-02T17:25Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Landed · #29421 · 2026-11-02T18:47Z

    reading value
    PR #29519 MERGED by the merge queue at 2026-11-02T18:45:20Z, merge commit f966f0187b (head 0fbbbf79)
    this card closed completed by the PR's Fixes line
    earlier ejection queue run 41499208591 red on the ledger mismatch tracked in #29530 (root #29437, not this PR); main stayed green, re-armed and merged on the next pass

    State: pm:dispatched removed. Follow-ups are on #29527.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions