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
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
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.
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.
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.
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.
"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
(1) Re-lead or chain the message at each outer walk, or carry the root object on the converted error.
(2) Declare the status structurally.
(3) Skip the conversion, or hedge the sentence, when the rollback threw.
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.
{
"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": []
}
Activity
objectstack-fleet commentedon Nov 2, 2026
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
objectstack-fleet commentedon Nov 2, 2026
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, aFixesline per card.objectstack-fleet commentedon Nov 2, 2026
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
Fixesline per card, one commit per card.chore(changeset): #29422 + #29421 patch changeset for @objectstack/ob…
objectstack-fleet commentedon Nov 2, 2026
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
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Landed · #29421 · 2026-11-02T18:47Z
f966f0187b(head0fbbbf79)completedby the PR'sFixeslineState:
pm:dispatchedremoved. Follow-ups are on #29527.