Skip to content

fix(pm): check-widening-tells pays a tombstone re-declared through a transparent wrapper - #21016

Merged
os-justin merged 5 commits into
mainfrom
claude/issue-20969-widening-tells-wrapped-key
Oct 1, 2026
Merged

os-justin merged 5 commits into
mainfrom
claude/issue-20969-widening-tells-wrapped-key

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #20969
Clause-②: no (a PM enqueue gate's verdict changes; no published package's accept set or public surface moves, per the question in scripts/pm/clause2-line.mjs)

What

scripts/pm/check-widening-tells.mjs raised a false T1 on PR #20961's diff: the change block removes body: retiredKey( and adds body: retiredComponentSlot(retiredKey(. That is the same tombstone with a marker call around it, and the marker hands back the instance it is given. This PR teaches the #16943 net-delta block to read that pair as a replacement. The pair's two true T3 rows still fire.

On PR #20961's diff (git diff 75519e1c0a...d9d0d38cfc, --declaration no):

exit rows
before (dfe5a0863f) 4 T3 ui.json:320, T3 ui.json:513, T1 component.zod.ts:932
after (0fd8e1969f) 4 T3 ui.json:320, T3 ui.json:513
after, --declaration yes 0 n/a

The mechanism, re-derived (this corrects the filing)

The filing guessed that the wrapper hides the line's shape from the budget. It does not: the same hunk re-declares two live keys through the sibling marker componentSlot( (children, footer), and the budget already pays for both. Two existing readings each declined correctly on their own evidence, and that left nothing to pay with:

  1. Removed side. [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955 deliberately lets a removed tombstone buy no T1 unit, so un-retiring a key (tombstone removed, live schema re-added) still fires.
  2. Added side. [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955's tombstone decline reads the value's first call, finds retiredComponentSlot(, and correctly refuses to call the line a tombstone on that evidence alone. [finding] check-widening-tells T1 is still blind to FILE-LOCAL declaring factories — a new writable key through placeholderFree( on objectstack's own judged surface passes a Clause-②: no in silence #18702's resolver classifies the factory as writable because it returns its own argument, and a wrapper that returns its argument can still put a live arm on it.

The repair

The repair follows triage's direction: the #16943 block reads a removed key and a re-added key of the same name as a replacement. It keeps that credit in a second currency and never converts it into a T1 unit:

"Whatever wraps the value" holds for every wrapper the diff proves transparent, and for no other. return z.string().or(schema), return schema.or(z.string()) and a braced branch returning a live schema all hand a tombstone back with a live arm on it. Paying for those would let #17955's un-retiring leg go quiet behind a prefix. A wrapper this reader cannot certify keeps the tell firing.

Pins: self-test battery #20969 …, 28 cases, each ablated

Each ablation is one anchor through scripts/ablation-replace.mjs in WRAP mode, which arms the restore on exit/INT/TERM. All five were run at 0fd8e1969f. After each one, the restore was proven: blob back to 29c508a2522f (the HEAD blob) and git diff HEAD empty.

pin / control ablation result
the PR #20961 pair is paid (its hunk verbatim, fixture line 932) spend disabled (the credit check short-circuited to false) 8 of 553 red, including THE FINDING and CLEAN; the CLI on the PR diff shows T1 component.zod.ts:932 again (3 rows, exit 4)
a renamed key still tells (body tombstone removed, panel re-declared) same-key match dropped (credit summed across keys) 1 of 553 red: the renamed-key PIN
a genuinely new key inside a wrapper call still tells, as un-retiring through the wrapper (body: retiredComponentSlot(z.array(…))) value inside the wrapper no longer has to be a tombstone (any call accepted) 2 of 553 red: the un-retire PIN and the reader's null case
a genuinely new key inside a wrapper call still tells, as a new key beside the pair, at its own line in either order the credit paid as a plain T1 unit (the naive repair) 10 of 553 red: the new-key, renamed and un-retire PINs, every widening-wrapper control, and #17955's own un-retiring CONTROL
widening wrappers (lenientSlot, chainedSlot, branchySlot) still tell transparency dropped (every writable factory treated as transparent) 3 of 553 red: all three

Other controls in the battery: a dark control (the same added line with nothing removed still fires), live arms chained onto the wrapper or onto the tombstone, one-for-one, block-scoped, never-spends in both orders, and the imported-wrapper boundary (#18702's stated, reported silence, unchanged).

Measured price

The corpus is the history present in this shallow tree. One graft boundary, 2e8bd8322b, was excluded. Every commit went through wideningTells with headBlobSource live, and a known blob was probed first. Up to a5bce40888: 1,979 non-merge commits touch packages/spec/src, 1,933 of them carry a non-test .ts diff, giving 5,865 file diffs. Running the merge-base version against this version: 4,915 rows against 4,914, one differing commit, one row moved. That commit is 315888d660, PR #20961 as it landed, and the moved row is the false T1 on PageCardProps.body. No other row moves and none starts firing.

Gates (all at 0fd8e1969f, the final head)

  • node scripts/pm/check-widening-tells.mjs --self-test: exit 0, 553 cases pass (525 before this PR, plus 28).
  • The dispatch-gates.mjs --commands --repo objectstack-ai/objectstack union: 30 derived, 29 run, all exit 0. --ran reconciliation exit 0: 30 derived famil(ies) accounted for — 29 run, 1 NOT-MEASURED.
  • NOT MEASURED: pnpm check:pm-dispatch-gates, because its self-test passes the container's foreground cap (timeout 560, exit 124). Declared to CI.
  • npx eslint --no-inline-config --format json scripts/pm/check-widening-tells.mjs: 1 file, 0 errors, 0 warnings. The config for this file (--print-config) has no parserOptions.project, so linting is not type-aware and this diff cannot change any untouched file's verdict. The repo-wide pnpm lint is left to CI.

Acceptance notes

  • Quiet direction, stated: a wrapper body that mutates the instance it hands back still reads as transparent, and a text reader cannot see that. At a5bce40888, 8 of 6,388 top-of-line definitions on packages/spec/src/** read as transparent, and none of them mutates what it returns. [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955's own residual (a live arm chained on the closing line of a multi-line tombstone) also reaches inside a wrapper unchanged. Its population is 0, and its overturn condition is unchanged.
  • Loud residuals, kept on purpose: these still fire: a new tombstone re-declared through a transparent wrapper with no same-key removal, two wrapper levels, and a wrapper whose own tail carries arguments. The header records the overturn condition.
  • origin/main was merged into the branch at a5bce40888, which brings in PR feat(spec,lint): one derived list of page-component slot positions, read by all three page walks (#20940) #20961 itself. No conflicts, and main had not touched this script.
  • No changeset: scripts/pm/** ships in no package, so skip-changeset applies.
  • definitionSites / localFormPattern interpolate a factory name into a RegExp without escaping $, so a file-local factory named with $ is reported unresolved (IMPORTED reason) instead of resolved. Population: 0 $-named definitions under packages/spec/src at 0fd8e1969f. A stated silence, not a quiet one; carrier: none (dev report 5923104992).

Generated by Claude Code

claude added 5 commits October 1, 2026 01:21
…transparent wrapper

A change block that removes `key: retiredKey(` and adds
`key: marker(retiredKey(` re-declares the same tombstone; the marker hands
back the instance it is given. The removed tombstone deliberately buys no T1
unit (un-retiring must fire) and the added line is not read as a tombstone
because a wrapper can put a live arm on what it returns, so the line fired a
false T1.

The net-delta block now records each removed tombstone's KEY in a second
currency, spent only by an added line that re-declares that same key as a
tombstone through one wrapper the head blob proves transparent (it returns
its first parameter, bare, and has no other return). Read before the budget,
never a T1 unit: un-retiring, a renamed key, a new key in a wrapper, and every
widening wrapper still fire. Corpus: 4,914 rows against 4,914 over 1,932
commits, 0 differing.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…closes on its line

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…rries the landed pair

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0fd8e1969f2d02dc5568987a978673be40549eb8
Local-runs: none

Inputs read: card #20969 (body; comments 5921874538, 5922653696, 5923104992, 5923148779); PR #20961 (merged as 315888d660, its diff 75519e1c0a...d9d0d38cfc read with git diff); PR #21016 body, file list, and the net diff against main (merge base a5bce40888: one file, scripts/pm/check-widening-tells.mjs, +431/-10); the head tree via git show / git grep / git log; the head's check-runs, read last.

① Derived judgments

Accept-set / public-surface changes the diff implies: none. The one file is a root-private script (package.json is private: true, no package's files names scripts/), so no published package's accept set or public surface moves. What moves is one PM enqueue gate's verdict on one diff shape. Named right.

The repair, walked in the head tree — right.

The transparency proof — sound for the shapes the brief names. transparentFactory requires exactly one definitionSites hit, a factoryReturnExpression reading whose first parameter is a bare identifier, returns of 0 (an expression body) or 1, and expr matching ^first[ ]?(;|}|$). returnCount counts return at any depth and answers -1 for a body that never closes, so a second return in a braced branch, a return in a nested callback, and an unreadable body all refuse; a chained form (schema.optional(), schema.or(...)) fails on the .; a prefixed arm (z.string().or(schema)) fails at the first character; a later parameter, two definitions, and an absent definition refuse. topLevelReturn and returnCount lex strings and comments the same way, so a nested-only return reaches null before the count is consulted. The source is readSource(file) taken once in localDeclaringForms — the same head blob and the same pass as #18702's resolver, and a name the resolver never wanted (never on an added key line) is never on the list. Right.

Pins and wiring — right. 28 t( calls between battery('#20969 …') and the floor block, equal to the new SELF_TEST_BATTERIES entry (28); the roster is now 29 entries against SELF_TEST_BATTERY_FLOOR = 16; the sum of per-battery floors moves 510 to 538, the same +28 the stated 525 to 553 total moves. keyedPropertyName is split into propertyNameOf with its behaviour unchanged; factoryReturnExpression gains an additive returns field that classifyFactoryReturn and resolveDeclaringFactory ignore.

The corpus claim — plausible from the method stated. In this clone (shallow, boundary 2e8bd8322b, the commit the PR names) git rev-list --no-merges --count a5bce40888 -- packages/spec/src is 1,979, the PR's figure exactly; with the boundary excluded and .test.ts/.spec.ts dropped, --name-status gives 1,933 commits, exactly the PR's figure, and 5,860 file diffs against the stated 5,865 (a rename-counting difference). The row counts (4,915 against 4,914, one moved row on 315888d660) were not re-run — read-only — but the arithmetic closes (T1 2,390 + T2 2,040 + T4 484 = 4,914), and the first commit's census (1,932 commits, 0 differing, measured before main was merged) against the re-measure after 315888d660 entered the branch (+1 commit, +1 row on the base version) is internally consistent.

Sentences tested against the tree. True: "the thirteenth accidental variable" (twelve prior sections counted in the header); new-file line 932; blob b2f88ca605 (the diff's index line); "the marker hands back the very instance" (component.zod.ts:444-453 at a5bce40888); the shape's population on packages/spec/src/** is that one key line (a PCRE grep at a5bce40888 finds only component.zod.ts:932); #17955's closing-line residual has population 0 (no )).or( closing line at a5bce40888); main never touched the script between dfe5a0863f and a5bce40888 and 315888d660 is an ancestor of the merge point; scripts/pm/** ships in no package; eslint.config.mjs sets no parserOptions.project; definitionSites (${name}\b) and localFormPattern (${n}\() interpolate the name unescaped while wrappedTombstoneKey and transparentFactory escape $; zero $-named top-of-line definitions under packages/spec/src at the head; the eight named transparent definitions exist at a5bce40888 and each is return param; or an expression-bodied arrow whose body is its parameter, bare.

  • Over-broad (docblock, finding(pm): check-widening-tells reports a T1 tell on an existing key re-spelled inside a wrapper call (- body: retiredKey( → + body: retiredComponentSlot(retiredKey(); the #16943 net-delta replacement rule should pay it #20969 header): "8 of 6,388 top-of-line definitions read transparent (… withRefusalConversions), none of them mutating what it returns." withRefusalConversions (stack.zod.ts:3768) calls markRefusalConversions, whose stampConversionRecord does Object.defineProperty(target, STACK_CONVERSIONS, …) on the very instance it hands back — one of the eight does mutate what it returns. It mutates an Error, never a schema, so the quiet direction the sentence bounds (a mutated tombstone read transparent) still has population 0; the sentence is true only narrowed to "none of them mutates a schema it returns". The 6,388 denominator was not re-measured. Does not move the verdict; it is a one-line docblock correction.
  • Incomplete (PR body, "The repair"): "Transparent means the definition has exactly one return, and that return is the bare first parameter." The code and the docblock also accept an expression-bodied arrow that IS its parameter (zero returns; the pass case pins it). True as a sufficient condition, not the whole definition. True on main the moment this lands, as the docblock already states it.
  • The before/after table, --declaration yes exit 0, the five ablation red sets and "553 cases pass" are the dev's measurements, not re-run here; each ablation names an anchor that exists in the diff, and nothing in the diff touches the T3 reading.
  • The loud residuals the header keeps are right, and one it does not state: a wrapped tombstone whose key line changes while wrapped on BOTH sides (- body: W(retiredKey( / + body: W(retiredKey(, say re-indented) fires, because the removed side is classified against surfaces without localForms ([finding] check-widening-tells T1 is still blind to FILE-LOCAL declaring factories — a new writable key through placeholderFree( on objectstack's own judged surface passes a Clause-②: no in silence #18702's added-side-only asymmetry) and mints nothing. Loud direction, population 0 (the one wrapped line is unchanged since 315888d660).

② Semver level

  • No changeset, skip-changeset labelled — right: the diff publishes nothing from any released package (root private: true; scripts/ in no files), and Check Changeset is skipped on the head for that label.
  • Clause-②: no (a PM enqueue gate's verdict changes; no published package's accept set or public surface moves, per the question in scripts/pm/clause2-line.mjs) — reads declared, value no, arm null under readClause2Line: the parenthetical opens with a, not an arm-family word, so readArmToken returns no arm and no bad, and the line is not malformed. Right on substance: 「本卡放宽接受集或扩大公开面吗」— nothing published widens, and nothing published narrows, so no (narrowing) arm is owed. The claim 5922653696 and the PR body carry the same line.

③ Boundary flags

Implemented-by: claude/issue-20969-widening-tells-wrapped-key
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1

VERDICT: PASS

Adopted and posted by domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01T02:17Z · rendered by the seat's at-tier review subagent on this head. The seat read its served tier family from the subagent transcript before posting.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review October 1, 2026 02:18
@os-justin
os-justin added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 14bde86 Oct 1, 2026
51 checks passed
@os-justin
os-justin deleted the claude/issue-20969-widening-tells-wrapped-key branch October 1, 2026 03:50
os-project-manager pushed a commit that referenced this pull request Oct 4, 2026
…not_planned at first grade, citing ruling 208

instrument-discipline.md says a report-only instrument gets no card, no
dev and no PR, and that its only in-flight work is deletion (ruling 208).
Nothing on the triage path read that line, so the grade never asked it.
One sentence in the grading block of references/triage-duties.md now
does: a card whose fix lands in a report-only instrument (the widening
tell, the half-state patrols) closes not_planned at first touch, citing
ruling 208, and the instrument's only in-flight work is deletion. No new
gate, label or script.

Enumeration pin (the two instances where the line was not read; a third
is a red reading of this sentence, not a new card):
  - #20969 / PR #21016 (landed)
  - #21465 / PR #21679 (held)

Paid in place, net 0 lines (120 -> 120, ceiling unchanged, no re-wrap):
the deleted line is the triage-side restatement of the filing door's four
classes; its owning copy stays in references/filing-gate.md (the class
list and the refusal of everything else, one line, with the four class
heads on the lines below it), reached from the pointer line kept right
under the deleted one.

Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…t_planned at first grade, citing ruling 208 (objectstack-ai#21691)

Fixes objectstack-ai#21681

Clause-②: no

Tier S (`.claude/**` only). Draft; this run does not flip it ready,
queue it or arm auto-merge — the owning seat lands it after its at-tier
contract review.

## What changed

One protocol sentence in the grading block of
`.claude/skills/pm-dispatch/references/triage-duties.md`, as the triage
ruling on the card (comment 5976725561) specifies: a card whose fix
lands in a report-only instrument (the widening tell, the half-state
patrols — the judge-intent class `instrument-discipline.md` names) is
closed `not_planned` at first touch, citing ruling 208, and the
instrument's only in-flight work is deletion. No new gate, label or
script.

It is paid in place: one restated line of the same file is deleted, so
the file stays at 120 lines under its 120 ceiling. No other line is
touched (no re-wrap), and `SKILL.md`, `instrument-discipline.md` and
`scripts/pm/check-skill-line-ratchet.mjs` are untouched.

The enumeration pin — the two instances where the rule was not read — is
named in the commit message: objectstack-ai#20969 / PR objectstack-ai#21016 (landed) and objectstack-ai#21465 / PR
objectstack-ai#21679 (held). A third such card is a red reading of this sentence, not
a new card.

## Reading 1 (读数一): placement and payment

Tree: `objectstack-ai/objectstack` at `bc85776a87` (branch head), base
`251a7dd4b4`.

- **New sentence, line 64** (120 bytes; the line cap is 120), directly
under the two existing close-at-grade lines (the 「无则关」 line and the
open-P0/P1 `tooling` line), so the grading block now carries all three
close-at-grade cases together:

```text
- 只报告仪器(放宽 tell、半状态巡查)的修复卡首触即关 not_planned,引裁决 208;在途只有删除。
```

- **Deleted line, was line 40** — the triage-side restatement of the
filing door's four classes:

```text
- 立卡门四类:① 缺陷 / ② 维护者决定 / ③ 直派任务 / ④ 协调节点,⛔ 其余一律不立卡。
```

- **Its owning copy, kept on the reading path:**
`references/filing-gate.md` line 11, the same rule (four classes only,
everything else refused), with the four class heads on lines 12 / 16 /
17 / 18:

```text
- 立卡只为四类,正文首行写立卡门类别 ①–④;⛔ 其余一律不立卡,不论它叫什么。
- ① 有具名落点或复现的产品缺陷,即 `pm:queue` 的定义;其内的 `finding` 限三类且带 `reach:`。
- ② 只有维护者能做的决定,落卡即带「维护者速读」与四棱块。
- ③ 维护者直派的任务,正文引其原话。
- ④ 协调节点:跨仓/跨层父单与它的逐层子单。
```

The triage reader reaches it through the pointer line kept right under
the deleted one (now line 40): `首行写立卡门类别 ①–④;四类定义、⛔ 清单、三答与配额见
references/filing-gate.md`. The deleted line entered the protocol as the
summary of that file, in the same commit that created `filing-gate.md`
(`37ed9ae04b`), and moved here verbatim with the principle-only cut; it
is a restatement rather than a ruled clause: the ruled clause, with its
provenance quotation, lives in `filing-gate.md` and is unchanged.

- **Line count:** 120 → 120 (ceiling 120, headroom 0, unchanged). Diff:
1 file, +1 / −1.

## Reading 2 (读数二): gate output

Run at `bc85776a87`, after the final commit, exit codes captured before
any pipe:

```text
pnpm check:pm-skill-ratchet   exit 0
✓ check-skill-line-ratchet self-test: 157 cases pass.
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md: widest table row is 0 bytes (pin 0; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md is 120 lines (ceiling 120; headroom 0).
✓ check-skill-line-ratchet: declared cross-file moves: 2, total ceilings down 477 lines.

pnpm check:pm-skill-id-lint   exit 0
✓ check-skill-id-lint self-test: 14 cases pass.
✓ check-skill-id-lint: 34 file(s) clean (pattern /#[0-9]{3,}/g).
```

`ruling 208` is spelled without a hash prefix, the way
`instrument-discipline.md` records it; the issue and PR numbers of the
pin are in the commit message only.

The full derived set (`node scripts/pm/dispatch-gates.mjs --commands`,
no paths, change set read from the merge base) is the same 18 commands
the dispatch listed, all exit 0. `--ran` reconciliation: `18 derived, 18
run, 0 NOT-MEASURED, 0 UNRUN` (a derived zero — every line carried its
exit code). One first-run `exit 3` is not a measurement:
`check:doc-formula-expressions` refused with PREREQUISITE NOT MET
(unbuilt `@objectstack/formula` / `@objectstack/lint`); after the build
it names (run under the shared verify lock, `VERDICT command-exit 0`) it
exited 0. `check:nul-bytes` exit 0, plus a control-byte self-scan of the
edited file: zero hits.

No changeset: `.claude/**` publishes nothing.

## Acceptance notes

- **Wording differs from the seat's suggested line, meaning unchanged.**
The suggestion measured 152 bytes against the 120-byte line cap. The
landed line drops the explicit `instrument-discipline.md` pointer; both
of its key terms still lead there (「只报告的仪器」 is that file's rule line,
and 「裁决 208」 its ruling record), and `reading-discipline.md` keeps its
pointer to it. 「在途只有删除」 echoes that file's own 「它的在途工作只有删除」.
- **The class is the instrument's role, not the path.** The sentence
covers a fix to a report-only, judge-intent instrument. A fix to a
hard-gate face that happens to share a file with a patrol is outside it,
and the sentence does not widen the class beyond the two instruments the
ruling names. Observation only, nothing filed. carrier: the triage seat,
applying the sentence at grade time.
- **Not decided here, as the ruling lists it:** whether the code PR
objectstack-ai#21016 landed is deleted under the report-only rule is the maintainer's
question; this PR does not touch `scripts/pm/check-widening-tells.mjs`.

## 维护者速读(草稿)

**改了什么:** 分诊职责文件 `triage-duties.md` 的定级段加了一句:修复落在「只报告仪器」(放宽
tell、半状态巡查)上的卡,首次定级就以 not_planned 关闭,并引裁决
208;这类仪器的在途工作只有删除。同时删掉同一文件里一行重复的「立卡门四类」摘要(完整原文仍在 `filing-gate.md`),文件仍是
120 行,行数上限不动。

**为什么改:** 裁决 208 早就规定只报告的仪器不立卡、不派开发、不开
PR,但分诊定级时并不读这条,于是两张这类卡被定级入队、派了开发,一张已经合并,一张被扣住,白花两轮开发。把规则写进分诊每次都读的定级段,首次定级就能拦住。

**风险与代价(含回滚):**
只改内部协议的一行文字,不新增门禁、标签或脚本。风险是分诊把与巡查同文件的硬门禁修复也误关,句子按「只报告」身份判断而不按文件路径,可以避免。已经合并的那份代码要不要删,不在本
PR 范围,留给您另行决定。回滚就是撤销这一个 commit。

**席位意见:**

**你要做的:** 无需操作:本 PR 是 Tier S(只改 `.claude/**`),席位复核通过后经合并队列落地;不同意请回一句。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants