Skip to content

fix(types): an unevaluable hook condition's refusal answers 400 VALIDATION_ERROR at every data door - #24714

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24662-hook-condition-coded-refusal
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24662-hook-condition-coded-refusal

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24662
Clause-②: no

A write that an authored hook condition refused, because the condition compiled and then could not be evaluated on the record it was judging, now answers a coded refusal: 400 with code VALIDATION_ERROR, on the REST data doors and on the runtime dispatcher's /data door. Before, it answered 500 INTERNAL_ERROR, and @objectstack/rest logged it as [REST] Unhandled error. Fail-closed does not move: the row is still not written.

What was wrong (measured on origin/main eadea5720)

The card's repro was driven through a real engine (ObjectQL with a sqlite SqlDriver) and a real ObjectStackProtocolImplementation. The hook was bound by the engine's own bindHooksToEngine: beforeInsert on inquiry, with the condition record.name.startsWith('qa-fc') && int(record.name) > 0. The request was POST /api/v1/data/inquiry with name: "qa-fc-abc".

door before after
RestServer POST /data/:object (mapDataError) 500 {"error":"Internal server error","code":"INTERNAL_ERROR"} plus [REST] Unhandled error: HookConditionError: ... 400 {"error":"Hook 'qa_failclosed' could not evaluate its condition (runtime: int() type error: cannot convert to int) — operation aborted.","code":"VALIDATION_ERROR","object":"inquiry"}, no log line
RestServer POST /data/:object/createMany (handleRouteError) the same 500 and the same log line the same 400 body, no log line
dispatcher /data (the throw leaves dispatch(); @objectstack/hono's catch-all renders it through declaredEnvelopeForThrow) declaredEnvelopeForThrow declines it, so the fallback arm answers 500 INTERNAL_ERROR with the hook's message 400 {"success":false,"error":{"code":"VALIDATION_ERROR","message":"Hook 'qa_failclosed' could not evaluate ..."}}
dispatcher /data (dispatcher-plugin's errorResponseBase reads resolveThrownHttpError) 500 INTERNAL_ERROR 400 VALIDATION_ERROR
POST /data/:object/batch row (toRowApiError) {"code":"INTERNAL_ERROR","message":"The create of this record failed. The reason is in the server log."} {"code":"VALIDATION_ERROR","message":"Hook 'qa_failclosed' could not evaluate ...","httpStatus":400}

In every row above the inquiry table held 0 rows after the request.

Why it happened: HookConditionError (packages/objectql/src/hook-wrappers.ts) carries no status and no code, on purpose. Its docblock rules that a fact on it reaches the wire through the ADR-0112 vocabulary as a decision. No boundary had decided, so the class matched nothing in classifyDataError and nothing in resolveThrownHttpError, and each door fell to its unclassified 500.

The fix: one recogniser, read by both rules

  • packages/types/src/hook-condition-refusal.ts (new). unevaluableHookConditionRefusal(err) recognises the class by its declared name and its declared reason: 'unevaluable', never by message text. @objectstack/types cannot import @objectstack/objectql. It answers { status: 400, code: standardErrorCodeForHttpStatus(400) }. It is not exported from the package index, so the public surface does not move.
  • resolveThrownHttpError (thrown-http-error.ts). The shape is a declaration of both halves there, as the validation shape already is: declaredStatus: 400, declaredCode: 'VALIDATION_ERROR'. This one change serves the dispatcher's exits, both Hono doors (whose declared-envelope gate needs a declared status and a registered code) and the batch rows.
  • structuredCodeAnswer (data-error-classification.ts). A new arm sits beside its precedent, the VALIDATION_FAILED arm. Both REST doors ask it first, so the single-record door and the bulk doors answer alike.

The landing points widen the claim's surface in one place. The claim named data-error-classification.ts and error-response.ts. Measured, the runtime twin does not read the classification at all; it reads resolveThrownHttpError. So thrown-http-error.ts, in the same package, carries the second reader, and the recogniser gets its own module. packages/rest/src/error-response.ts needs no change: the log verdict already reads the declaration off resolveThrownHttpError (isDeclaredClientRefusal). Nothing under packages/objectql/** or packages/spec/** is touched.

The code choice, and why it is not a new code

  • Status 400: the nearest landed precedent. ADR-0058's 2026-08 addendum lists the hook condition (hook 的 condition 求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775) beside the object validation predicate (Script validation rules are silently skipped when their predicate fails to evaluate — fail-open is the wrong direction for a validation #4649) as one family: an unevaluable write gate fails closed. Measured on the same door, a validation rule whose condition is the card's expression answers 400 (code VALIDATION_FAILED, fields[0].constraint.reason: 'unevaluable'). Its sentence comes from the same cel-fault.ts as the hook's. The refusal turns on this record's values: another record can pass the same condition, and retrying the same request cannot.
  • Code VALIDATION_ERROR, not the sibling's VALIDATION_FAILED. VALIDATION_FAILED's contract is the per-field fields[] envelope that a form maps onto its inputs, and a hook's gate addresses no field. VALIDATION_ERROR is the StandardErrorCode member that ADR-0112's status map names for a 400 whose producer declared no more specific code. The batch rows and the nested-envelope doors already derive it for an un-coded 400 refusal. No code is minted and no ledger row is added, so Clause-②: no holds as the claim declared it.
  • The spec seat's branch test. "Does this defect give a consumer a branch it could act on differently? No ⇒ widen the existing member's comment/message; yes ⇒ new code under the pre-approved class." The answer is no. A consumer branches on this exactly as it branches on a generic 400: do not retry the same request, and show the message. The finer fact ("the author's expression faulted") already has a discriminator on the sibling, constraint.reason: 'unevaluable'. Measured on objectui cd4623d, no production file reads that discriminator, so no consumer branch exists that a new top-level code would serve. The existing member's meaning is unchanged, and the new module's docblock records why it covers this case.

Scope: the unevaluable reason only

reason: 'uncompilable' (the condition never compiled) keeps its sanitised 500 and its [REST] Unhandled error line, and it is pinned as a control. It does not turn on the request: every write to the object fails until an author fixes the metadata. The gate already logs it at error when the hook is bound. Its message ends with fix the expression: followed by the condition SOURCE, which no sibling refusal puts on the wire. The platform checklist records that the metadata door refuses such a condition at authoring time (422 INVALID_METADATA), so it reaches this gate only from hooks bound outside that door.

What the client message names

The arm relays the producer's message, as the VALIDATION_FAILED arm relays its own. It names the hook, the CEL fault in one line and, when known, the key the condition read (the shared describeCelFault detail). It never names the condition source or a record value; the e2e pin asserts that neither the source nor qa-fc-abc appears in the body. These are the facts the sibling validation-rule refusal already carries, by construction, because both come from one sentence builder.

The log

The REST doors no longer print [REST] Unhandled error for this refusal, and they print no other line. Precedent: AGENTS.md, "Degradation log levels": "a failure handed to the CALLER is not a degradation at all … Do not bolt a logger.error onto such a site". It also follows isDeclaredClientRefusal (#23932), which reads a declaration off the thrown error rather than growing the isExpectedRouteError list by a name. resolveThrownHttpError now reports this shape's status and code, so the verdict needs no edit. The runtime exits log only 5xx (logServerFault), so a 400 costs no line there either. The operator signal that remains is the uncompilable reason's bind-time error line, which is unchanged. See the acceptance notes for the producer-side gap.

Pins (each asserts status and code, not "it threw")

  • packages/rest/src/rest-hook-condition-unevaluable-refusal.test.ts, real engine and real RestServer handlers:
    • the card's request answers 400 with the exact body, the source and value are absent, the row is absent, and there is no [REST] Unhandled error line;
    • createMany answers the same;
    • CONTROL: a hook that refuses on purpose (403 PERMISSION_DENIED) answers byte for byte as before;
    • CONTROL: the uncompilable reason keeps 500 INTERNAL_ERROR and exactly one [REST] Unhandled error line. This is also the positive control for the log spy, under OS_REST_LOG=info.
  • packages/runtime/src/domains/data-hook-condition-refusal.test.ts, the dispatcher /data door on the data-write-request-gate.test.ts rig:
    • the card's request: declaredEnvelopeForThrow (imported, not restated) answers 400 VALIDATION_ERROR, resolveThrownHttpError answers 400 VALIDATION_ERROR, and the row is absent;
    • CONTROL: the deliberate refusal is returned as 403 with an unchanged body;
    • CONTROL: uncompilable is still declined by declaredEnvelopeForThrow and resolves to 500.
  • packages/types/src/hook-condition-refusal.test.ts: the recogniser, and agreement between the two rules that read it, on source.
  • packages/rest/src/error-response-structured-arm-door-parity.test.ts: one more §1 case, so the single, bulk and route doors stay equal for this class. The arm is name-keyed, so the code-literal census there cannot see it.

Pin sweep (repo-wide, one round)

I searched the repo for the wire answer of an unevaluable hook condition (INTERNAL_ERROR or 500 paired with a hook condition, HookConditionError, and could not evaluate its condition):

  • packages/qa/dogfood/test/hook-after-condition-rollback.dogfood.test.ts: three assertions of { status: 500, code: 'INTERNAL_ERROR' } for an afterInsert condition that reads previous on an insert now assert { status: 400, code: 'VALIDATION_ERROR' }. The header sentence that called the refusal the sanitised 500 is corrected.
  • docs/qa/platform-checklist/areas/records-forms.json, records-forms.object-hook-lifecycle acceptance[8] (revision 6 to 7): the verify text recorded the 500 against 18.0 round-2 sweep framework findings: a paid invoice line's amount/total stays writable while its inputs are locked; an unevaluable hook condition answers 500 INTERNAL_ERROR; remote-tables?schema= never narrows on the SQL drivers #24619 row (b). It now states the coded refusal and scores a 500 as a FAIL, and the server-log evidence is dropped.
  • content/docs/automation/hooks.mdx: one sentence now gives the wire answer of a refused write.
  • Other hits that stay as they are: packages/metadata-protocol/src/protocol.bulk-record-not-found.test.ts throws a bare Error carrying the hook's text (not the class) and asserts that the text is absent; showcase-anonymous-deny-surfaces.dogfood.test.ts asserts that a message does not mention the class; the objectql unit suites pin the engine, not the wire.

Ablation (one leg for the one changed behaviour)

The fix was committed first (3ed86f196). The mutation went through node scripts/ablation-replace.mjs in WRAP mode, so its trap restored the file. The anchor was if (e?.name !== 'HookConditionError' || ... and the replacement used the name 'HookConditionError_ABLATED', so the recogniser matches nothing.

  • Landed: anchor count 1 to 0, blob 6099c6874149 to be145e442da8. @objectstack/types was rebuilt, and node scripts/ablation-dist-preflight.mjs @objectstack/types 'HookConditionError_ABLATED' exited 0 (marker in dist/). The rest suites read types' dist/; the runtime suite aliases it to source.
  • Mutated leg:
    • types pin: 3 failed, 2 passed;
    • rest e2e: 2 failed (the card's request and createMany, "expected 500 to be 400"), 2 passed (both controls);
    • parity: 1 failed (the new case), 65 passed;
    • runtime twin: 1 failed (declaredEnvelopeForThrow answered undefined), 2 passed (both controls).
  • Restored: blob 6099c6874149 equals HEAD, git diff HEAD is empty, and the marker count on disk is 0. After a rebuild, the dist preflight with --absent exited 0 on a clean tree. Restored leg: types 5/5, rest e2e 4/4, parity 66/66, runtime 3/3.

Tests and gates (final head 910352e21)

  • @objectstack/types: vitest run --project local gave 28 files / 764 passed, and typecheck exited 0. These ran at 3ed86f196; the one later commit touches only a rest test file.
  • @objectstack/rest: pnpm --filter @objectstack/rest test (local project) gave 301 files, 6099 passed and 326 skipped, at 3ed86f196. typecheck, including check:test-typecheck, exited 0. The repo project's error-response-structured-arm-door-parity.test.ts and error-response-sandbox-arm-message.test.ts gave 2 files / 156 passed. rest-hook-condition-unevaluable-refusal.test.ts re-ran at 910352e21: 5/5.
  • @objectstack/runtime (targeted; the full suite is CI's): six data-door and error-envelope files gave 108 passed, and the repo project's error-envelope.conformance.test.ts gave 53 passed. typecheck, including check:test-typecheck, exited 0.
  • Readers of the shared rule:
    • @objectstack/metadata-protocol: 6 batch-row files, 94 passed;
    • @objectstack/core: 4 import-runner files, 111 passed;
    • @objectstack/plugin-hono-server: full suite, 34 files / 422 passed;
    • @objectstack/hono: full suite, 11 files / 215 passed.
  • Dogfood, once, on the final head: hook-after-condition-rollback.dogfood.test.ts ran against a booted stack and gave 5/5 with the flipped pins. @objectstack/dogfood typecheck exited 0.
  • Derived gate families: node scripts/pm/dispatch-gates.mjs --commands derived 96 commands at 910352e21, and all 96 exited 0 (exit codes captured before any pipe, after a full turbo run build). The --ran reconciliation reads "96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED".
  • pnpm lint (eslint . --no-inline-config, the whole repo) at 910352e21 exited 0.
  • The after-insert case in the rest e2e file was added after the ablation and was not ablated on its own. It reads the same arm, and the dogfood pin above is its booted-stack twin.
  • origin/main was not merged in: the 12 commits ahead of the merge base touch none of the files this change reads or edits. The one overlap by file, metadata-protocol/src/protocol.ts, changes the exposure-gate read and not the batch-row seam. The merge queue rebuilds on current main.

Acceptance notes

  • A before* unevaluable condition leaves no operator line. The validation-rule sibling gets a WARN from its producer (rule-validator.ts: "predicate failed to evaluate … — write rejected"). An after* hook's fault is covered by the engine's warn-level Insert operation failed line, seen in the dogfood run. A beforeInsert hook's fault raised no line in these runs: the hook wrapper logs nothing for the unevaluable reason, and the engine line did not appear. A producer-side line would belong in packages/objectql (hook-wrappers.ts), which this dispatch holds off. Observation only; not filed.
  • The runtime door still relays an uncompilable condition's 500 message verbatim. On the dispatcher /data door, @objectstack/hono's fallback arm answers the uncompilable reason's 500 with its message, which quotes the condition source. That is the standing fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667 posture ("whether an undeclared 5xx stays legible is a standing decision this arm does not reopen"), and this PR does not move it. The REST doors withhold the same message. Noted, not filed, because it is a ruled posture.

Generated by Claude Code

…ATION_ERROR at every data door

A HookConditionError whose condition compiled and then faulted on the record
it judged carries no status and no code by its own contract, and no boundary
classified it, so every door answered the unclassified 500 INTERNAL_ERROR and
@objectstack/rest logged it as an unhandled error. One recogniser in
@objectstack/types (hook-condition-refusal.ts) now answers 400 and the catalog
member 400 names; resolveThrownHttpError reads it as a declaration by shape
(the runtime and Hono doors, the batch rows), and structuredCodeAnswer as an
arm beside its validation-predicate sibling (the REST doors). The uncompilable
reason keeps its sanitised 500.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
…swers the same coded refusal

The dogfood pin flipped by the previous commit drives an afterInsert hook
whose condition reads previous on an insert, through the write door's unit.
The same shape is now pinned in process on the REST create door: 400
VALIDATION_ERROR naming the hook, the row rolled back, no Unhandled line.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/types, touching 6 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/error-handling.mdx (via structuredCodeAnswer (symbol, a top-level function))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 73d4cc7391bb5c2dc5d40e23af8cd505721242af → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bfc6f588c68ef14dc6906716cd190a282dff3ca1 — the merge of head 910352e2182a99ac3cf1bd4cf3b5f0c99bea4a3d into base 73d4cc7391bb5c2dc5d40e23af8cd505721242af, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bfc6f588c68ef14dc6906716cd190a282dff3ca1 && git checkout bfc6f588c68ef14dc6906716cd190a282dff3ca1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 73d4cc7391bb5c2dc5d40e23af8cd505721242af 910352e2182a99ac3cf1bd4cf3b5f0c99bea4a3d && git checkout -B drift-repro 73d4cc7391bb5c2dc5d40e23af8cd505721242af && git merge --no-ff 910352e2182a99ac3cf1bd4cf3b5f0c99bea4a3d

node scripts/docs-audit/affected-docs.mjs --json 73d4cc7391bb5c2dc5d40e23af8cd505721242af

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 73d4cc7391bb5c2dc5d40e23af8cd505721242af → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT

domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T01:31Z · head 910352e2182a99ac3cf1bd4cf3b5f0c99bea4a3d · claim 6279442235 → report 6281181277. No contract face: the write is refused before and after, and the code is a published StandardErrorCode member. So no review of record is owed.

Check Reading
Shape Draft, base main. First line Fixes #24662; Clause-②: no line-initial. Assignee os-justin = the card's. check-governed-merges.mjs --pr 24714: NOT governed, 680 changed lines, ≤ 3000.
Surface 11 files. The claim's file list widened inside @objectstack/types (thrown-http-error.ts, plus the new hook-condition-refusal.ts), with the measured reason in the body: the runtime twin reads resolveThrownHttpError, not the classification. Accepted. error-response.ts is unchanged, and the log verdict reads the declaration. The pin sweep reached one dogfood file (3 pins flipped), checklist records-forms.object-hook-lifecycle (revision 7) and one sentence in content/docs/automation/hooks.mdx. No packages/objectql/**, no packages/spec/**. No other open PR touches any changed file (read in this act).
Reach of the shared rule resolveThrownHttpError has 15 callers on origin/main, read in this act. All are answer-shaping boundaries: the data doors, the hono adapters, the batch rows (toRowApiError, clientFacingRowFailureText), the package persist-failure wrapper and the print route. For each, a declared 400 VALIDATION_ERROR for this throw means a refusal handed to the caller. No caller is a retry or durability path.
Code choice It follows the measured precedent, the validation predicate's unevaluable refusal (#4649). That answers 400 on the same door, from the same cel-fault.ts sentence. VALIDATION_ERROR = standardErrorCodeForHttpStatus(400), and no code is minted. The branch test is answered "no" with a reading: objectui cd4623d has 0 production reads of constraint.reason: 'unevaluable'.
Disclosure The arm relays the producer's message (hook name, the CEL fault, the missing key), never the condition source or a record value. The e2e pin asserts both are absent. uncompilable, whose message quotes the source, keeps its sanitised 500, pinned as a control on both transports.
Refusals The e2e pins assert status + error.code + the absent row + the absent [REST] Unhandled error line. There are two controls: a deliberate 403 hook unchanged, and uncompilable 500 plus exactly one log line, which is the spy's positive control. The ablation renamed the recogniser's anchor, rebuilt with a dist preflight, and the pins went red on each door. It was restored, with blob = HEAD.
Prose Read line by line against the diff. The hooks.mdx sentence ("answers 400 with the code VALIDATION_ERROR … never the condition's source"), checklist acceptance[8] verify/evidence and its revision note, and the changeset's four bullets each match a hunk or a pinned case. patch on @objectstack/types matches Clause-②: no, and the doors' behaviour ships through it.
Gates Check-runs on this head (38, latest per name), read at this act: 17 success, 5 skipped, 16 in_progress (Test Core 1–6/6, Build Core, Build Docs, Type Check ·, Lint ·). Landing waits on all of them.

Accepted deviations:

  • The surface widened inside @objectstack/types, as above.
  • Narrowed local runs: targeted runtime and metadata-protocol files. Test Core owns the full suites.
  • main was not merged: the one overlapping file of the 12 incoming commits is protocol.ts, in a different region.

Out-of-scope dispositions (one line each):

  • A before* hook's unevaluable condition now leaves no operator line, where its validation-predicate sibling WARNs from its producer (packages/objectql, held off): Acceptance notes. The refusal is handed to the caller with the hook named (AGENTS.md "Degradation log levels"), and a producer-side WARN for parity is an observation, not an a/b/c defect.
  • On the dispatcher /data door, an uncompilable condition's undeclared 500 relays its message, which quotes the condition source: Acceptance notes. It is the standing fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667 posture (undeclared 5xx kept legible), reachable only by a hook bound outside the metadata door, which refuses an uncompilable condition at authoring (422). It is not reopened here.

Landing: held while main-red #24700 is open (AGENTS.md: no lane PR arms the queue meanwhile; fix claim 6280921751). After that, it goes through the merge queue by this seat once every check on this head reads success (or an expected skip).


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 02:53
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 20, 2026 02:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 014863d Oct 20, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24662-hook-condition-coded-refusal branch October 20, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rest: a write refused by an unevaluable hook condition answers a generic 500 INTERNAL_ERROR instead of a coded refusal (#24619 row b)

2 participants