Skip to content

skills (data-hooks): a hook that enforces a business rule declares refusals and refuses by code, not a bare onError abort (HOOK_ABORTED) #33257

Open

Description

@objectstack-fleet

epic:#3600#1 · hotlong · session_01Q4EjjPXwX1YfHyKJUh4agu

Filing-gate category: ① AI-authoring guidance gap, measured. reach: the gallery debut refresh of 2026-11-14 (card asset_lifecycle, scratch, --packages published 18.4.0, runner at objectstack main 295ce127, model claude-opus-5-5; record 2026-11-14-scenario-asset_lifecycle.json, kept in the seat's work dir). The built app refused a forbidden write correctly, but with the platform's generic code, so the card's validation operation failed on both the build half and the iterate half.

What happened

Operation check_out_asset_in_repair as asset_admin declares refused VALIDATION_FAILED, guardrail write_validation: "Checking out AST-0041 while it is in repair is refused … with the form's validation error, never written."

  • The AI enforced "only an in-stock asset can be checked out" with a sandboxed hook, src/hooks/checkout_requires_stock.hook.ts. This is legitimate, because a validation predicate cannot read the asset's status from the checkout. It used onError: 'abort' and no refusals catalogue.
  • The runner's probe write was refused HOOK_ABORTED (400). That is a platform catalogue code, so under ruling A on Scenario runner and card defects from the QA scenario round: a reserved field name in two cards, and scorer/observer rules that fail correct agent answers #31999 item 9 (refusalMeets, scenario-operation-grade.ts) it does not meet VALIDATION_FAILED. A declared refusal such as ASSET_NOT_IN_STOCK at 409 would have met it, and it would also have given the user and the agent a meaningful code and a localized message.
  • The agent itself declined correctly in both halves.

Ask (skills/objectstack-data/references/data-hooks.md, and the hook rules that point to it)

  1. Teach that a hook which enforces a business rule declares a refusals catalogue and refuses by code: one code per rule, with a status and a message key. A bare onError: 'abort' is for unexpected failures, not for rules the user can act on. Show this case: a cross-object state guard, "only an in-stock asset can be checked out".
  2. If an authoring rule or lint can say it mechanically (a hook whose body refuses on a business condition carries refusals), propose it in the PR. Otherwise the prose rule is enough.
  3. Proof: pnpm check for the skills package, plus one scenario re-run on a card with a cross-object guard. This is a live run, so it needs the maintainer's word; until then, cite the recorded build.

Reader: the domain:skills lane seat, from pm:queue. Dedupe: hook refusals skills HOOK_ABORTED; the only near hit is the closed #27597, the code-less 400 itself. Priority p3: coverage, and the AI may or may not choose a hook on the next run.

Activity

  1. added
    pm:queueReady for the PM dispatch loop
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    on Nov 14, 2026
  2. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: audit of a pre-labeled card · p3 confirmed · 2026-11-14T15:48Z

    ⛔ Not a claim.

    Area: none (domain:skills)

  3. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

    Claim: PM loop round 4
    Session: session_01FEkQ5pjSPT7ui2EUcunaVb
    Account: os-project-manager
    Branch: claude/project-thread-g605co
    Worktree: objectstack-issue-33257
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: skills/objectstack-data/references/data-hooks.md, a pointer line in a hook rule that points to it only if now incomplete, one changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: opus (default; dispatch-gates --tier on the real diff decides), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds (re-read on b5f2f516; #33214 still open, so teach within today's limits per triage 6880063659)
    Serial constraints cleared: PR #33292 (same file) merged as b5f2f516; PR #33458 owns skills/objectstack-data/SKILL.md, PR #33394 the data evals file, card #33044 rules/security.md; this card stays out of all three
    Order: /mnt/project-files/pm-orders/skills/order-33257.md (project file). Ruling-ref: Leehom 「开放 p3卡认领」 (2026-11-14T21:21:06Z). Claimed 2026-11-14T22:04Z.

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

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5njvc1ya6PypTHRBe5
    os-dev-report
    {
    "issue": 33257,
    "status": "done",
    "branch": "claude/project-thread-g605co",
    "pr": "#33488",
    "session": "session_01FEkQ5njvc1ya6PypTHRBe5",
    "premise_still_valid": true,
    "summary": "data-hooks.md section Declared refusals now opens with the rule: a hook that enforces a business rule declares a refusals catalogue, one SCREAMING_SNAKE code per rule, and its body throws the code; without a catalogue a throw is answered HOOK_ABORTED, fine only for an unplanned failure. Its example is the card's cross-object guard (checkout_requires_stock, ASSET_NOT_IN_STOCK at 409, os:check), and it says only a js body can declare refusals on main today (a handler hook, inline TS included, is refused; nothing #33214 would add is taught) and how the refusal reaches the wire (status's code plus declaredCode). Best Practices and the Error Handling intro point to it. rules/validation.md, the hook rule that points to data-hooks.md, had an os:check tax-ID hook throwing new Error free text: its pointer sentence and example now declare and throw TAX_ID_BLOCKED (seat approved the widened surface before the edit). Tokens paid in-file in both files; no ceiling moved.",
    "tests": "Skill text only; no committed test. Scratch probe (not committed, deleted, git status clean): a vitest file in packages/runtime/src/sandbox on the overlap-hook-template harness (real ObjectQL, SQLite driver, QuickJSScriptRunner, bindHooksToEngine, read off mapDataError), hooks passed through HookSchema.parse, after building runtime^... under the verify lock (VERDICT command-exit 0): Test Files 1 passed, Tests 3 passed, exit 0. Readings: in_repair asset, missing asset_id and unknown id each 409 code RESOURCE_CONFLICT declaredCode ASSET_NOT_IN_STOCK userMessage 'Only an in-stock asset can be checked out.', one checkout stored; new tax hook 400 VALIDATION_ERROR declaredCode TAX_ID_BLOCKED; old tax hook 400 code HOOK_ABORTED (the card's symptom); HookSchema.safeParse of refusals with a string handler and with an inline function handler: both success false, one issue at refusals. check:skill-examples exit 0 (346 prose examples type-check) covers both os:check blocks.",
    "gates": "Local on head 47af59e (base origin/main b5f2f51): dispatch-gates --commands derived 45; 44 exit 0, 1 exit 3 NOT MEASURED (check:dual-build-cjs-loads, reads every package dist, no package touched); exit captured before any pipe, after turbo build of lint..., client-react... and runtime^... under the lock (VERDICT command-exit 0, 320s). dispatch-gates --ran: 45 derived, 44 run, 1 NOT-MEASURED. Extra: spec check:skill-examples exit 0. check-skills-token-ratchet exit 0 (data-hooks.md 10009/10011, validation.md 2760/2761). check-skill-line-ratchet exit 0. check-governed-merges --branch: exit 3 GOVERNED tier H, skills/** x2, +47/-29. dispatch-gates --tier: MANDATORY contract-review tier. Record: pm-orders/skills/33257-gates-ran.txt. CI check-runs: not read at report time; the seat reads them.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "0 — no MCP GitHub calls",
    "api_writes": "2 — relay pr_create (PR #33488, read-back identical 11974 bytes, no labels, no assignees); this os-dev-report comment on #33257 via post-stamped",
    "files_changed": [
    "skills/objectstack-data/references/data-hooks.md (+32 / -23)",
    "skills/objectstack-data/rules/validation.md (+5 / -6)",
    ".changeset/33257-skills-hook-refuses-by-declared-code.md (+10, new)"
    ],
    "line_budget": "data-hooks.md 884 to 893 lines, 9962 to 10009 tokens (ceiling 10011); validation.md 331 to 330 lines, 2754 to 2760 tokens (ceiling 2761); all SKILL.md 4603 to 4603 lines; bundle 244659 to 244712 tokens. Paid by deleting same-file restatements: the protocol-17 history sentence and a shortened follow-on paragraph in data-hooks.md, the Date range validation pattern (restates the date_range cross_field example) in validation.md. No ceiling moved.",
    "deviations": [
    "Surface widened by one file, skills/objectstack-data/rules/validation.md (pointer sentence plus two example lines), beyond the order expected surface; asked the seat first and the seat approved it in its thread before the edit.",
    "dispatch-gates --tier reads MANDATORY contract-review tier for skills/** with no one-line exit; this card thread ran on the claim default tier. The seat in-seat contract review is the owed record.",
    "Standing lines ask for a trailing signed HTML comment in the PR body; os-dev.md bans HTML comments other than the harness attribution line. os-dev.md wins: the signature is a prose line under Fixes #33257.",
    "Order mechanism assumption refined: HOOK_ABORTED does not arise from onError abort as such. onError abort (the default) only makes the throw abort; the code comes from resolveThrownHttpError answering a 4xx sandbox business throw that carries no code (UNDECLARED_HOOK_REFUSAL_CODE). With a catalogue declared, a no-code throw is a 500 fault instead."
    ],
    "open_questions": [],
    "security_findings": "none",
    "out_of_scope_findings": [
    "carrier: maintainer · ask item 2 proposal, not built (order). A lint or authoring rule: a hook js body that throws a literal free-text Error on a business condition and declares no refusals. Mechanically decidable for literal throws only (the same reading hookRefusalCodesThrownIn does), blind to computed throws. Four axes: business need, one measured gallery build (asset_lifecycle), no producer count beyond it; long-term, it pushes toward the declared catalogue, which is the contract; AI-proofing, a warning at os validate would catch the measured mistake where the prose may be skipped; startup focus, a new gate is default no unless the maintainer names it, and no such words are on the card. Recommend: do not add now; the prose rule is enough, revisit if the next scenario refresh repeats it. Dedupe words: hook free-text throw lint, refusals missing lint, HOOK_ABORTED authoring rule",
    "carrier: none · noted, not filed: other skills files may still show a free-text hook throw; not swept, because objectstack-data/SKILL.md (PR #33458), the data evals file (PR #33394) and rules/security.md (card #33044) are held by serial constraints."
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

    Surface widening (seat record): during development the thread asked to widen this claim's file surface from a pointer line in skills/objectstack-data/rules/validation.md to the two example lines there, plus deleting its "Date range validation" line (a restatement of the remaining date_range example). The seat approved it in the card thread before the PR opened. The approval lives in the project thread, so it is recorded here so that the card holds it. PR #33488's diff stays inside that widened surface. 2026-11-14T22:37Z

  7. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

    ACCEPT → PR #33488 comment 6885632631 · contract review PASS 6885627321 · held for the maintainer (Tier H; reviewers requested) · 2026-11-14T23:06Z

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

Metadata

Metadata

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:skillsDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions