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
error codes: an undeclared hook onError abort answers the data door with a code-less 400; give it a ledgered ADR-0112 code (#27508 code half) #27597
carries the code half of #27508; #27508 keeps the log half (logUnexpectedRouteError stops logging the abort as unhandled), dispatched in domain:server.
What is wrong
A hook whose onError (or body) aborts with free text, without a declared refusal code, is answered on the /data door with a 400 that carries no code. A client cannot tell it from any other unclassified 400.
Re-check on main: packages/rest/src/error-response.ts (the /data classification door), packages/types/src/data-error-classification.ts (mapDataError), and grep -n -i "hook" packages/spec/src/api/error-code-ledger.zod.ts (no hook-abort code in the ADR-0112 ledger today).
Expected
An undeclared hook abort answers with one ledgered ADR-0112 code, so the client SDK can map it. Declared refusals keep their declared codes.
Clause-②: yes: the code name is the maintainer's at approval. A contract review is owed before approval. ⛔ Declared refusals keep their declared codes, and ruling 6431311681 letter A is not reopened.
Claim: PM loop round 3 · 2026-10-29T15:30Z
Session: session_01WGufzgbJhFoXYsLSe5WSPP
Account: os-support-ai
Branch: claude/project-thread-0evlg0 (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-27597-<slug>)
Worktree: objectstack-27597
Domain: domain:spec
Seat: domain:spec#3
Ruling-ref: ruling 6431311681 (#26170 letter A: hook refusals are declared by code), not reopened; triage 6500443789: one ledgered ADR-0112 code for an undeclared abort, both ends in one claim
File surface: the ADR-0112 ledger row (packages/spec/src/api/error-code-ledger.zod.ts and the wire code enums it needs), the emitter (packages/types/src/data-error-classification.tsmapDataError, packages/rest/src/error-response.ts), the client SDK's ledger-code mapping if any, their tests, one changeset per touched published package (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate (a new public wire code; the contract review is triage's), effort: high
Clause-②: yes (widening)
Metadata-protocol: as measured by the dev under AGENTS.md's rule
Responsibility: an unclaimed p1 domain:spec card; seat 1 is off duty
Thread-read: 6500443789
Premise: to be measured first by the dev on origin/main178b5238: a hook that aborts with free text and no declared code is answered at the /data door with a code-less 400
Serial constraints cleared: #27508's log half (domain:server) is in flight on the same door; the dev runs the in-flight intersection check against its claim and PR before editing, and leaves the log half alone
{
"issue": 27597,
"status": "done",
"branch": "claude/project-thread-0evlg0",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27667",
"head": "339443cd35765cd9220a3ec2f5ce563a7ed6ec7c",
"session": "session_01WGufziZEEbJZ3DUMwdVJWX",
"premise_still_valid": true,
"summary": "Measured on origin/main: mapDataError answered a sandboxed hook's free-text abort (no declared code) as 400 {error, object} with no code. Now both sandbox refusal doors in classifyDataError (innerMessage unwrap and wrapper-regex fallback) and the rest 4xx declared-status passthrough answer the ledgered HOOK_ABORTED (UNDECLARED_HOOK_REFUSAL_CODE, ledger row under @objectstack/types) when thrownCodeFields finds no declared code; a declared code keeps its own spelling. Proposed name HOOK_ABORTED with reasons in the PR body; the maintainer names it at approval. Client SDK has no per-code mapping, so it passes the code through unchanged.",
"tests": {
"new": "packages/rest/src/rest-undeclared-hook-abort-code.test.ts 12/12 (undeclared abort gets HOOK_ABORTED on POST/PATCH/batch, declared 4xx status kept, wrapper fallback byte-identical, CONTROLs: catalogue refusal VALIDATION_ERROR+declaredCode CREDIT_LIMIT, RECORD_LOCKED kept, plain coded-less 400 unchanged, crash 500 INTERNAL_ERROR, ledger row live)",
"before_on_main": "8 red / 4 green (all 4 controls green)",
"types": "798/798 + typecheck pass",
"rest": "336 files, 6599 passed + typecheck pass (29 pins in 11 files flipped with [#27597] notes)",
"spec_ledger_contract_retired_codes": "98/98",
"dogfood_hook_error_format": "4/4",
"runner": "os-verify-lock slot os-dev-27597"
},
"gates": {
"dispatch_gates_commands": "107/107 exit 0 on 339443cd",
"ran_reconcile": "107 derived, 107 run, 0 NOT-MEASURED",
"check_changeset_no_major": "exit 0 with the PR body as event"
},
"declarations": {
"Clause-②": "yes (widening)",
"Metadata-protocol": "none",
"Skills-docs": "none"
},
"dogfood_battery": "n/a — no ! and no (narrowing) arm",
"intersection_27508": "branch claude/issue-27508-hook-abort-not-unhandled at e9d789c2, no PR yet; no textual overlap, but its isSandboxBodyRefusal requires body.code === undefined, so whichever lands second must read HOOK_ABORTED there",
"mcp_calls": 0,
"api_writes": [
"relay pr_create #27667 (draft)",
"relay pr_request_reviewers #27667",
"relay comment #27597 (this report)",
"git push x3 to claude/project-thread-0evlg0"
],
"open_questions": [
"The nested-envelope doors (sharing, security, approvals; 15 routes in the write-route ledger) and the import undo list move from VALIDATION_ERROR to HOOK_ABORTED for an undeclared abort: a value move on an existing door. Does the seat still count it under Clause-② (widening)?"
],
"out_of_scope_findings": [
"class b · resolveThrownHttpError doors (package publish, runtime dispatcher, /actions) still answer an undeclared hook abort as VALIDATION_ERROR · reach POST /api/v1/packages/publish, measured in rest-write-route-hook-refusal-sentence.ledger (REFUSED_400 kept) · carrier: seat"
]
}
Claim amendment: one declaration, one surface addition, one seat ruling · domain:spec seat 3 · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-29T17:00Z · same claim 6500976762, same branch claude/project-thread-0evlg0
Metadata-protocol: none, as measured by the dev: a wire error code is not something a metadata author writes.
File surface adds the teaching and pins the new code makes false: content/docs/protocol/kernel/error-handling.mdx, the generated content/docs/references/api/*.mdx, and packages/dogfood/test/hook-error-format.dogfood.test.ts. The eleven rest suites that pinned the old answer are tests beside the emitter.
Seat ruling on the dev's open question:Clause-②: yes (widening) stands. The only accept set that moves is the published code vocabulary, which gains HOOK_ABORTED. On the nested doors that re-dress /data's classification (sharing rules, security suggested-bindings, approvals) and on the import-undo failures list, the same undeclared abort moves from the status-floor VALIDATION_ERROR to HOOK_ABORTED. That is one abort answered with one code on every door that reads the classification, not a refusal of anything a client sends. The changeset carries it as a named behaviour note so the release text shows it. No ui/** or @objectstack/client reader branches on that value for those routes (measured by the dev).
seat ACCEPT (6507486801) on triage's PASS, then a main merge the seat verified (6510205555); merged through the merge queue at 2026-10-29T23:31Z, head af33de9ca, merge commit 21c4bdd804
origin/main
contains the merge commit (git merge-base --is-ancestor)
Delivered:
A sandboxed hook body that aborts a write with free text, declaring no code, now answers with the ledgered code HOOK_ABORTED beside its sentence. Before, it was the one refusal on the data door a client could not branch on. A refusal that declared a code keeps it, and a crashed body is still the sanitised 500 INTERNAL_ERROR.
Owed elsewhere: none on this card. The seat chose the code name HOOK_ABORTED; a rename would be a new card. pm:dispatched is removed here; the card is closed by the merge.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 错的必须被响亮拒绝并给处方 | none | P②
Triage: p1 ·
domain:spec·area:api· Seam · 2026-10-29T15:08Z⛔ Not a claim.
6498814060(p2): a new public wire code is a protocol addition, and the standing rule is 「协议新增定 p1」. So this card is p1.packages/typesmapDataError,packages/rest/src/error-response.ts). One claim declares both ends and runs the in-flight intersection check against the rest: a hook onError abort answers the data door with a 400 that carries no error code, and the server logs it as an unhandled SandboxError #27508 log half (server). Acceptance: the/datadoor answers an undeclared abort with the code, and the ledger row is live.Clause-②: yes: the code name is the maintainer's at approval. A contract review is owed before approval. ⛔ Declared refusals keep their declared codes, and ruling6431311681letter A is not reopened.enhancement,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim: PM loop round 3 · 2026-10-29T15:30Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-0evlg0(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27597-<slug>)Worktree:
objectstack-27597Domain:
domain:specSeat:
domain:spec#3Ruling-ref: ruling 6431311681 (#26170 letter A: hook refusals are declared by code), not reopened; triage 6500443789: one ledgered ADR-0112 code for an undeclared abort, both ends in one claim
File surface: the ADR-0112 ledger row (
packages/spec/src/api/error-code-ledger.zod.tsand the wire code enums it needs), the emitter (packages/types/src/data-error-classification.tsmapDataError,packages/rest/src/error-response.ts), the client SDK's ledger-code mapping if any, their tests, one changeset per touched published package (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (a new public wire code; the contract review is triage's),effort: highClause-②: yes (widening)
Metadata-protocol: as measured by the dev under AGENTS.md's rule
Responsibility: an unclaimed p1
domain:speccard; seat 1 is off dutyThread-read: 6500443789
Premise: to be measured first by the dev on
origin/main178b5238: a hook that aborts with free text and no declared code is answered at the/datadoor with a code-less 400Serial constraints cleared: #27508's log half (
domain:server) is in flight on the same door; the dev runs the in-flight intersection check against its claim and PR before editing, and leaves the log half aloneobjectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufziZEEbJZ3DUMwdVJWX
os-dev-report
{ "issue": 27597, "status": "done", "branch": "claude/project-thread-0evlg0", "pr": "https://github.com/objectstack-ai/objectstack/pull/27667", "head": "339443cd35765cd9220a3ec2f5ce563a7ed6ec7c", "session": "session_01WGufziZEEbJZ3DUMwdVJWX", "premise_still_valid": true, "summary": "Measured on origin/main: mapDataError answered a sandboxed hook's free-text abort (no declared code) as 400 {error, object} with no code. Now both sandbox refusal doors in classifyDataError (innerMessage unwrap and wrapper-regex fallback) and the rest 4xx declared-status passthrough answer the ledgered HOOK_ABORTED (UNDECLARED_HOOK_REFUSAL_CODE, ledger row under @objectstack/types) when thrownCodeFields finds no declared code; a declared code keeps its own spelling. Proposed name HOOK_ABORTED with reasons in the PR body; the maintainer names it at approval. Client SDK has no per-code mapping, so it passes the code through unchanged.", "tests": { "new": "packages/rest/src/rest-undeclared-hook-abort-code.test.ts 12/12 (undeclared abort gets HOOK_ABORTED on POST/PATCH/batch, declared 4xx status kept, wrapper fallback byte-identical, CONTROLs: catalogue refusal VALIDATION_ERROR+declaredCode CREDIT_LIMIT, RECORD_LOCKED kept, plain coded-less 400 unchanged, crash 500 INTERNAL_ERROR, ledger row live)", "before_on_main": "8 red / 4 green (all 4 controls green)", "types": "798/798 + typecheck pass", "rest": "336 files, 6599 passed + typecheck pass (29 pins in 11 files flipped with [#27597] notes)", "spec_ledger_contract_retired_codes": "98/98", "dogfood_hook_error_format": "4/4", "runner": "os-verify-lock slot os-dev-27597" }, "gates": { "dispatch_gates_commands": "107/107 exit 0 on 339443cd", "ran_reconcile": "107 derived, 107 run, 0 NOT-MEASURED", "check_changeset_no_major": "exit 0 with the PR body as event" }, "declarations": { "Clause-②": "yes (widening)", "Metadata-protocol": "none", "Skills-docs": "none" }, "dogfood_battery": "n/a — no ! and no (narrowing) arm", "intersection_27508": "branch claude/issue-27508-hook-abort-not-unhandled at e9d789c2, no PR yet; no textual overlap, but its isSandboxBodyRefusal requires body.code === undefined, so whichever lands second must read HOOK_ABORTED there", "mcp_calls": 0, "api_writes": [ "relay pr_create #27667 (draft)", "relay pr_request_reviewers #27667", "relay comment #27597 (this report)", "git push x3 to claude/project-thread-0evlg0" ], "open_questions": [ "The nested-envelope doors (sharing, security, approvals; 15 routes in the write-route ledger) and the import undo list move from VALIDATION_ERROR to HOOK_ABORTED for an undeclared abort: a value move on an existing door. Does the seat still count it under Clause-② (widening)?" ], "out_of_scope_findings": [ "class b · resolveThrownHttpError doors (package publish, runtime dispatcher, /actions) still answer an undeclared hook abort as VALIDATION_ERROR · reach POST /api/v1/packages/publish, measured in rest-write-route-hook-refusal-sentence.ledger (REFUSED_400 kept) · carrier: seat" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim amendment: one declaration, one surface addition, one seat ruling ·
domain:specseat 3 · sessionsession_01WGufzgbJhFoXYsLSe5WSPP· 2026-10-29T17:00Z · same claim 6500976762, same branchclaude/project-thread-0evlg0Metadata-protocol: none, as measured by the dev: a wire error code is not something a metadata author writes.content/docs/protocol/kernel/error-handling.mdx, the generatedcontent/docs/references/api/*.mdx, andpackages/dogfood/test/hook-error-format.dogfood.test.ts. The eleven rest suites that pinned the old answer are tests beside the emitter.Clause-②: yes (widening)stands. The only accept set that moves is the published code vocabulary, which gainsHOOK_ABORTED. On the nested doors that re-dress/data's classification (sharing rules, security suggested-bindings, approvals) and on the import-undo failures list, the same undeclared abort moves from the status-floorVALIDATION_ERRORtoHOOK_ABORTED. That is one abort answered with one code on every door that reads the classification, not a refusal of anything a client sends. The changeset carries it as a named behaviour note so the release text shows it. Noui/**or@objectstack/clientreader branches on that value for those routes (measured by the dev).objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
ACCEPT → PR #27667 comment 6507486801
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-29T23:33Zaf33de9ca, merge commit21c4bdd804origin/maingit merge-base --is-ancestor)Delivered:
HOOK_ABORTEDbeside its sentence. Before, it was the one refusal on the data door a client could not branch on. A refusal that declared a code keeps it, and a crashed body is still the sanitised500 INTERNAL_ERROR.Owed elsewhere: none on this card. The seat chose the code name
HOOK_ABORTED; a rename would be a new card.pm:dispatchedis removed here; the card is closed by the merge.