Repository navigation
Description
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 usedonError: 'abort'and norefusalscatalogue. - 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 meetVALIDATION_FAILED. A declared refusal such asASSET_NOT_IN_STOCKat 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)
- Teach that a hook which enforces a business rule declares a
refusalscatalogue and refuses by code: one code per rule, with a status and a message key. A bareonError: '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". - 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. - Proof:
pnpm checkfor 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
objectstack-fleet commentedon Nov 14, 2026
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)refusalsand refuses by code, rather than with a bareonErrorabort. This is AI-authoring guidance, measured on a gallery scenario run: p3. It pairs with hooks: the refusal catalogue (refusals) cannot translate a handler-authored hook's refusals, and holds one sentence per code — a TS hook has no way to refuse in the user's language #33214 (handler hooks cannot userefusalsyet), so land hooks: the refusal catalogue (refusals) cannot translate a handler-authored hook's refusals, and holds one sentence per code — a TS hook has no way to refuse in the user's language #33214 first or teach within today's limits.objectstack-fleet commentedon Nov 14, 2026
domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb
Claim: PM loop round 4
Session:
session_01FEkQ5pjSPT7ui2EUcunaVbAccount:
os-project-managerBranch:
claude/project-thread-g605coWorktree:
objectstack-issue-33257Domain:
domain:skillsSeat:
domain:skills#1File 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 --tieron the real diff decides),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-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 ownsskills/objectstack-data/SKILL.md, PR #33394 the data evals file, card #33044rules/security.md; this card stays out of all threeOrder:
/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.objectstack-fleet commentedon Nov 14, 2026
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
objectstack-fleet commentedon Nov 14, 2026
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.mdto the two example lines there, plus deleting its "Date range validation" line (a restatement of the remainingdate_rangeexample). 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:37Zobjectstack-fleet commentedon Nov 14, 2026
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