Skip to content

fix(cli): os validate / build / lint --json report the conversions a refusing defineStack applied, beside the refusal - #20926

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-20583-refusal-conversions
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-20583-refusal-conversions

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20583
Clause-②: no

What this lands: location 2's CLI half

A defineStack (or composeStacks) call that converts an ADR-0087 D2 spelling and then refuses now reports both on os validate --json, os build --json and os lint --json: the refusal's error and code, and the conversions the producer applied before it refused, in conversions.

The spec half landed in PR #20651 (#20618): a refusing producer stamps the notices it applied on the ADR-0112 refusal it throws, and stackConversionsOf(error) reads them. This PR is the fold the seat answer 5886671384 kept on this card: one line in each of the three catch-alls.

  • validate.ts, compile.ts (os build inherits it) and lint.ts: inside the catch-all's --json branch, conversionNotices.push(...stackConversionsOf(error)) runs before emitJson. The payload still reads the one shared sink (conversions: conversionNotices), which the nightly source-scan pins require of every exit.
  • Folded, never recomputed. There is no second conversion pass over the authored source and no reading of the producer's stderr line. Both were declined as consumer-side reconstructions (5886671384).
  • Exactly once, by construction. None of the three commands calls a stack producer itself (git grep over packages/cli/src: every defineStack( / composeStacks( hit is prose or scaffold text; the door dependencies packages/lint/src and packages/objectql/src hit only comments). So only the config module's load can throw a stamped refusal, and a throwing load comes before both other fillers of the list (step 1b's loaded.stackConversions fold and step 2's own pass). stackConversionsOf answers [] for every other throw: a plain Error, this CLI's own refusals (refuseUnbuiltStack, ConfigRefusalError) and non-refusal throws.
  • loadConfig passes the refusal through untouched, so packages/cli/src/utils/config.ts is not edited. Measured at 165c1d49e3 by calling loadConfig from source on the fixture below. The caught error is a StackCapabilityUnknownError (code STACK_CAPABILITY_UNKNOWN, instanceof Error) that carries the own symbol objectstack.stack.conversions, and stackConversionsOf(error) answers the one page-header-subtitle-alias notice. The canonical control answers [].
  • The text face does not change. The fold sits inside the --json branch, and the producer's own stderr line is unchanged.
  • The comments that said "a throw at load reports []" now name the one exception. validate.ts's hoist note counts three fillers.
  • test/validate-build-gate-parity.test.ts: stackConversionsOf is now a bare call site in both compile.ts and validate.ts, so the closed call-site ledger needed a row for it. It gets its own NOT_A_GATE reason ("carries the conversion record a stack producer stamped on the refusal it threw ... refuses nothing"), not the nearest bucket.

os lint still does not take the one-authoring-shape rule. An unbuilt default export is neither built nor refused by a producer, so nothing moves for it (#20367's OQ3 stays unopened).

Measurements: the three doors through bin/run-dev.js (CLI from source)

The fixture is a strict defineStack whose page:header authors description (the live conversion page-header-subtitle-alias) and which then declares requires: ['no-such-capability']. The control is the same config with subtitle.

run before (165c1d49e3) after (fold applied)
os validate --json, convert then refuse exit 1, STACK_CAPABILITY_UNKNOWN, conversions: [], 1 producer stderr line exit 1, STACK_CAPABILITY_UNKNOWN, conversions = the one notice, 1 stderr line
os build --json, the same config exit 1, STACK_CAPABILITY_UNKNOWN, conversions: [], 1 stderr line exit 1, STACK_CAPABILITY_UNKNOWN, the one notice, 1 stderr line
os lint --json, the same config exit 1, STACK_CAPABILITY_UNKNOWN, conversions: [], 1 stderr line exit 1, STACK_CAPABILITY_UNKNOWN, the one notice, 1 stderr line
all three, the canonical control exit 1, STACK_CAPABILITY_UNKNOWN, conversions: [], 0 stderr lines unchanged

The notice is page-header-subtitle-alias at pages[0].regions[0].components[0].properties.subtitle, description to subtitle, retiresIn 18. The "after" column was also read at the final head: at dd6fa55374 all six rows answer exactly as in the table.

Tests

  • The pins, in packages/cli/test/stack-conversion-record-door.test.ts (the per-PR integration tier; *.e2e.* files run nightly only, which is why the fix(cli): os lint --json reports the ADR-0087 conversions defineStack applied #20617 lint rows live here too). A new block drives triage's convert-then-refuse fixture through each door. Each row asserts exit 1, code STACK_CAPABILITY_UNKNOWN, the catch-all's error string plus the door's own verdict key (valid: false / success: false), and conversions equal to exactly the one notice. The control (canonical then refuse) asserts the same envelope with conversions: []. That is 6 new rows, and the file now holds 21.
  • At the final head dd6fa55374, through scripts/pm/os-verify-lock.sh:
    • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: Test Files 238 passed (238) / Tests 3382 passed (3382).
    • vitest run --project integration --maxWorkers=2 test/stack-conversion-record-door.test.ts: Test Files 1 passed (1) / Tests 21 passed (21). That is the 15 existing rows plus the 6 new ones.
    • pnpm --filter @objectstack/cli typecheck: exit 0, check:test-typecheck: OK. The door file is in the test-layer program (tsc -p tsconfig.test.json --listFilesOnly, 1 hit).
  • The nightly pins that read these catch-alls were run too, because the diff edits exactly what they assert. At dd6fa55374, OS_TEST_TIERS=nightly vitest run over validate-json-failure-conversions.e2e.test.ts, build-json-failure-conversions.e2e.test.ts and lint-conversion-notices.e2e.test.ts gave Test Files 3 passed (3) / Tests 35 passed (35). That covers every exit still reading the one sink, the load-throw rows (a plain Error) still answering [], and the normalize call still below loadConfig.
  • The first unit run, at 649236e024, failed one test: validate-build-gate-parity.test.ts found the new call site unclassified. dd6fa55374 adds the ledger row, and the run above is green.

Ablation (reverse verification)

Run from the committed state a34e868fa4. The fold line was deleted in all three doors at once: three nested WRAP legs of node scripts/ablation-replace.mjs, plus a shell trap restoring the three absolute paths with git checkout HEAD --. The CLI loads these files from src/ through tsx, so no dist/ sits on the measured path and no rebuild was needed.

  • Landed on disk: the anchor conversionNotices.push(...stackConversionsOf(error)); went from 1 to 0 in each file. The blobs moved: validate.ts 11b7ea3e79e2 to f744edd59909, compile.ts 1e716a6e53fa to 3f653b1a82ba, lint.ts 4837e2246a13 to 5df926e26a3e.
  • Direction observed: red, and exactly where expected. The result was Tests 3 failed | 18 passed (21). The three red rows are the convert-then-refuse rows on validate, build and lint, each failing on conversions: expected [] to deeply equal [ { …(5) } ]. Their exit and code assertions held. The three controls and all 15 earlier rows stayed green.
  • Restore proven: each file's blob equals HEAD again (11b7ea3e79e2, 1e716a6e53fa, 4837e2246a13), the anchor count is 1 in each, and git diff HEAD over the three paths is 0 bytes.

Gates

At the final head dd6fa55374, in this worktree:

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 63 commands. I ran each one and captured its exit code before any pipe.
    • 61 exited 0 on the first run.
    • check:dual-build-cjs-loads and check:i18n-coverage first answered PREREQUISITE NOT MET (exit 3: no dist/ for packages outside the CLI closure). After pnpm turbo run build --filter=!@objectstack/docs, both exited 0. check:i18n-coverage printed OK (13 config(s), 621 baselined untranslated string(s), none new).
  • --ran reconciliation: Run reconciliation — 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero (all 63 carry an exit code).
  • pnpm lint (eslint . --no-inline-config, the whole repo): exit 0 at dd6fa55374.
  • Remote CI: not waited on.

Acceptance notes


Generated by Claude Code

… stack producer applied

A defineStack / composeStacks that converts an ADR-0087 D2 spelling and
then refuses stamps the notices it applied on its ADR-0112 refusal
(stackConversionsOf(error)). os validate, os build and os lint now fold
that record into the --json `conversions` of their catch-all exit,
beside the refusal. Folded, never recomputed; `[]` for any other throw.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
A strict defineStack that converts page:header `description` and then
refuses `requires: ['no-such-capability']` answers exit 1,
STACK_CAPABILITY_UNKNOWN and exactly the one page-header-subtitle-alias
notice on each door; the canonical-then-refuse control answers `[]`.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
…e ledger

The catch-all fold is a call site both doors now make; it reads the
record a producer stamped on its refusal and judges nothing.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json d78a0bda07c4bed2e3b71e051ac5db2632b82741.

⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 anchor(s) matched too much of the corpus to be a work list: os lint (command, 30 pages), os validate (command, 53 pages)
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 25 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 d78a0bda07c4bed2e3b71e051ac5db2632b82741 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8647e2d4b860e859f958f604110b88ed4e3047cc — the merge of head dd6fa5537450a9b13d982abead4b9e543a39725b into base d78a0bda07c4bed2e3b71e051ac5db2632b82741, 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 8647e2d4b860e859f958f604110b88ed4e3047cc && git checkout 8647e2d4b860e859f958f604110b88ed4e3047cc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d78a0bda07c4bed2e3b71e051ac5db2632b82741 dd6fa5537450a9b13d982abead4b9e543a39725b && git checkout -B drift-repro d78a0bda07c4bed2e3b71e051ac5db2632b82741 && git merge --no-ff dd6fa5537450a9b13d982abead4b9e543a39725b

node scripts/docs-audit/affected-docs.mjs --json d78a0bda07c4bed2e3b71e051ac5db2632b82741

⚠️ 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 d78a0bda07c4bed2e3b71e051ac5db2632b82741 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dd6fa5537450a9b13d982abead4b9e543a39725b
Local-runs: none

Inputs read: card #20583 (body and all ten comments, governed by 5884692257, 5886671384, 5916544096, 5916638647 and the newest os-dev-report 5918429389); #20618 and its PR #20651 (packages/spec/src/stack-provenance.ts, stack.zod.ts at this head); PR #20617; PR #20926's body, file list and net diff cb4c31dd52..dd6fa55374 (6 files, +148 / −14); the check-runs on this head, collapsed latest-per-name. The dispatch order and the dispatching seat's own conclusions were not inputs.

① Derived judgments

Accept set: unchanged. Public surface: no export added or removed; no --json key added or removed; no text-face byte changes. What changes is the CONTENT of the existing conversions array on the refusal path of os validate --json, os build --json and os lint --json, from [] to the ADR-0087 conversions the refusing producer applied. Each judged below.

(a) The fold reads the producer's own record, unwrapped, exactly once — RIGHT.

  • stackConversionsOf(error) (packages/spec/src/stack-provenance.ts:259) answers the frozen ConversionNotice[] that a value carries as its OWN property under Symbol.for('objectstack.stack.conversions'), and only when the value is a marked stack or an instanceof Error; otherwise the frozen empty record. The only writers are markStackProvenance and markRefusalConversions, both internal to spec (stack.zod.ts:3800 re-exports hasStackProvenance and stackConversionsOf only; index.ts does export * from './stack.zod' and never names stack-provenance). markRefusalConversions is reached solely through withRefusalConversions in defineStack and composeStacks, and only for instanceof StackRefusalError. So the three catch-alls read what the producer recorded at its throw. No second normalizeStackInput pass was added anywhere, and nothing reads stderr: the two channels the seat answer 5886671384 declined are absent from the diff.
  • loadConfig (packages/cli/src/utils/config.ts:589-669) rethrows the stamped refusal untouched: resolveConfigPath then a bare bundleRequire({ filepath, external }) with no try, so the config module's throw leaves as the same object. The only errors config.ts constructs itself are ConfigRefusalError (no code) and a plain Error for a missing default export; neither is stamped. Leaving config.ts unedited was the correct reading of the claim's conditional line.
  • Double report: in all three doors loadConfig is the first statement of the try; step 1b (conversionNotices.push(...loaded.stackConversions), validate.ts:243, compile.ts:277, lint.ts:971) and step 2's pass come after it. A load that throws never reaches 1b or 2, so the catch-all is the list's only filler on that run. A throw after 1b can carry a record only if a producer runs after load, and none does: at this head git grep over packages/cli/src (non-test) finds defineStack( / composeStacks( only in prose, scaffold templates (init.ts, create.ts) and comments; packages/lint/src and packages/objectql/src hit tests, strings and comments only. The CLI's own refusals (StackProvenanceMissingError in stack-provenance-refusal.ts:42, ConfigRefusalError) are plain Error subclasses, never stamped. os build inherits compile's catch. A composed stack: composeStacks stamps its refusal with composedConversions(stacks), a Set over identity-frozen notices (one application once); and when an input defineStack refuses inside the array literal, that refusal is thrown before composeStacks runs and carries its own record only (first stamp wins, markRefusalConversions returns an already-stamped error unchanged). One record, folded once, on every path.
  • A non-refusal or unstamped throw yields []: a plain Error, an esbuild BuildFailure, the CLI's refusals, the bare zod error from composeStacks' options parse (not a StackRefusalError), a forged plain object carrying the key without the mark (isStampedRefusal requires instanceof Error), and an heir of a stamped refusal (own-property test). The canonical-then-refuse controls pin the empty answer on all three doors.

(b) Text face unchanged, fold inside the --json branch — RIGHT, and consistent with the direction. Triage's direction (5884692257) reads 「carries the conversions it applied into the refusal's --json」; the card body names the loss as 「loses the conversions from --json」; the claim's surface says the same. The text face of a refusing run is byte-identical before and after this diff: the producer's own warn-once stderr line still carries the notice there, as it did. On the success path the door additionally prints the folded record in its warning block at step 2; on the refusal path it prints the refusal alone. That asymmetry pre-dates this PR and the direction did not ask to close it; --json is the complete channel. It is a boundary to note, not a defect (see ③).

(c) Payload shape — RIGHT. The emitted object literals are untouched by the diff: validate valid, error, code and httpStatus via errorCodeFields, warnings, conversions, duration; compile success, error, the same carriers, warnings, conversions; lint error, the same carriers, conversions. Only the conversionNotices binding's content changes, and only when the caught error is a stamped refusal. code is read by errorCodeFields off the same error object (the stamp is a non-enumerable symbol property and touches neither code nor httpStatus); the exits stay this.exit(1) / process.exit(1). The new pins assert the envelope per door (expectCatchAll: exit 1, code STACK_CAPABILITY_UNKNOWN, error a string, valid: false / success: false) and expectExactly asserts conversions by toEqual against exactly the one notice, or [] for the control — exactly-once, not containment.

(d) The validate-build-gate-parity.test.ts row — RIGHT, reason text true. The closed roster's scan (callSitesOf) matches a bare identifier followed by ( and skips property accesses, so step 1b's loaded.stackConversions never reached it and the new bare call stackConversionsOf( in both compile.ts and validate.ts does; the unit run at 649236e024 went red on exactly that name. NOT_A_GATE under its own reason key is the right ledger: the call raises no finding and refuses nothing, the refusal was thrown and the conversions applied by the producer before it runs, and the exit is 1 whether it answers the record or []. It is not a gate (not SHARED_NON_REGISTRY_GATES), not presentation, and both commands (and lint.ts) call it, so parityGapBetween has nothing to report and no ledger entry is stale sees it live. Each clause of the reason text checks against the code.

(e) Changeset @objectstack/cli patch — RIGHT; claims nothing undelivered. The three doors, the refusal beside the conversions, exactly once, [] for a refusal whose source needed no conversion and for any other load failure, no key added, exit / error / code unchanged, text face unchanged: each is in the diff and pinned (the six new door rows; the nightly source-scan pins hold the load-throw [] rows and every exit reading the one sink). stackConversionsOf(error) in @objectstack/spec is a landed fact (#20651, api-surface root.json:201). One wording imprecision, not a false claim: 「a defineStack or composeStacks call converts a deprecated spelling」 — composeStacks converts nothing; its refusal carries what its inputs' defineStack calls converted (spec composedConversions). The behaviour claimed for it holds by the same fold line and the spec's own pin; it is not pinned at a door. Acceptance note, below.

② Semver level

Clause-②: no with patch is right. Under AGENTS.md's changeset rule the declaration answers whether the card widens the accept set or expands the public surface (yes takes at least minor; (narrowing) is BREAKING; no arm is written here, which the closed pair allows). This diff accepts and refuses exactly what main does (the refusal still throws, exit 1, same code), adds and removes no --json key, adds no export, and moves no text-face byte; an existing field on a failure path stops answering [] for conversions that were applied. That is a bug fix on a released package (card labelled bug), the same shape PR #20617 landed under no + patch with a PASS at-tier record, and PR #20579 before it. A consumer that asserted conversions: [] on a refusal was asserting the defect, not a contract. Check Changeset is success on this head.

③ Boundary flags

Each dev deviations entry, answered:

  1. Base 165c1d49e3 rather than the dispatch's cf684c98eb, and one merge of main. Verified: cf684c98eb is an ancestor of 165c1d49e3, which is dd7f3c886a's parent; the merge 649236e024 has parents 64bf48ddf1 and cb4c31dd52; the branch delta before the merge (165c1d49e3..64bf48ddf1) and the merge result against main (cb4c31dd52..649236e024) are byte-identical (5 files, +139 / −14); between 165c1d49e3 and cb4c31dd52 main touched none of the PR's six files; the merge-base with main is cb4c31dd52. Accepted.
  2. The parity-ledger edit outside the claim's named files. Forced by the closed roster, which reddened at 649236e024 on the one new bare call; the row is truthful (①d) and is a pin under packages/cli/test, inside the claim's 「pins in packages/cli」. Accepted; no escalation.
  3. Three nightly-tier *.e2e.* files run locally. Read-only runs of existing source-scan pins that assert on exactly these catch-alls (every exit reads the one sink; a load-throw plain Error answers []); nothing edited; extra evidence beyond the per-PR tiers. The verdicts this record cites are the head's check-runs, not those runs. Accepted.
  4. The fold sits inside the --json branch only. Answered at ①(b): consistent with the direction as written. Acceptance note: on a refusal the text face carries the notice only through the producer's warn-once stderr line, so a repeated notice across a composed config is printed once there; --json is the complete channel.
  5. The attribution trailer. The harness reminder named a model in Co-Authored-By; AGENTS.md binds the model-free pair and its pre-push hook refuses a model identifier, and AGENTS.md wins. Verified on the head: all four non-merge commits end with Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB and the model-free Co-authored-by: Claude line with the noreply address; the merge commit carries no trailer (a merge, as fix(cli): os lint --json reports the ADR-0087 conversions defineStack applied #20617's landing record accepted); no model identifier appears in the diff, the commit messages, the changeset or the PR body; the PR body's footer is the session-URL form.

Anything in the diff that should have been an out_of_scope_findings or open_questions entry: no. Checked and set aside: lint.ts's --eval --generator load failure (:1385) emits no conversions key — pre-existing, fenced to its own card in its comment, not this door; a composition refusal reaching a door has no door pin (spec pins the stamp; the fold is type-agnostic); the changeset's composeStacks wording (①e); the refusal text face (item 4). None is a reproducible defect, a contract violation or an authoring trap; all three belong in Acceptance notes. The card's carry-over (content/docs/deployment/cli.mdx 「Warnings checked」) stays where the card left it.

Check-runs on this head, read from the API and collapsed latest-per-name, converged at 19:56:36Z: 34 names, 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failed or cancelled. The seven required contexts are all success: Lint & Repo Gates, TypeScript Type Check, Test Core (six shards and the rollup), Dogfood Regression Gate (three shards and the rollup), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also success: Check Changeset, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Check PR Size, Dogfood Verify CLI, the four Type Check legs, Flag docs affected by code changes, Check Documentation Links, Auto Label, filter. The six new door rows run in the per-PR population (test/stack-conversion-record-door.test.ts carries no nightly-tier name and spawns the CLI, so it is the queue population's integration project under Test Core), which is green on this head.

Implemented-by: claude/issue-20583-refusal-conversions
Reviewed-by: session_01VvcEokUG1tvVxkceYfR5XB

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 20:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 87847a2 Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20583-refusal-conversions branch September 30, 2026 20:19
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/m tests tooling

Projects

None yet

2 participants