Skip to content

feat(spec): ratchet the exports that emit no JSON Schema, so a never-published one cannot arrive silently - #16908

Merged
zhuangjianguo merged 5 commits into
mainfrom
claude/issue-16431-json-schema-emission-ratchet
Sep 8, 2026
Merged

feat(spec): ratchet the exports that emit no JSON Schema, so a never-published one cannot arrive silently#16908
zhuangjianguo merged 5 commits into
mainfrom
claude/issue-16431-json-schema-emission-ratchet

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #16431 — option (c) only, which is triage's ruling on that card and not a preference:

(c)(对"有导出、无发出的 JSON Schema"设棘轮)只报告今天已经为真的事 —— ⛔ 不改任何 schema、⛔ 不改生成器的投影能力、⛔ 不让任何东西开始或停止发布。⇒ 代价为零的可见化,确定,⛔ 无需裁决。

⚠️ Deliberately not Fixes: options (a) and (b) — the four filter.zod.ts exports, z.date() handling, orderingComparandSchema / rangeEndpointSchema — remain open on #16431, and closing it on (c) alone would close a card whose acceptance is not met.

The blind spot

build-schemas.ts already runs a disappearance ratchet (#2978 / #4725): a def key recorded in json-schema.manifest/ that a build stops emitting fails loudly. Its domain is "was published, stopped being published". It is structurally blind to "never was published" — an export that emitted nothing on its first build never entered the manifest, so there is nothing for it to miss.

The whole record of such an export was one console.warn in a build that exits 0. So two very different states were the same colour on every instrument this repo owned: a contract that genuinely does not belong on a published JSON-Schema surface, and a contract whose .describe() prose reaches no reader at all.

The population, counted for the first time

pnpm --filter @objectstack/spec check:authorable-surface on origin/main @ 142c01c88ee1, exit 0 (that exit code is the finding):

namespace exports with no emitted JSON Schema
Automation 2
Cloud 1
Data 11
Kernel 1
System 5
UI 3
total 23

By cause: 16 function, 4 date, 2 undefined, 1 custom — which is why the criterion here is structural (an exported z.ZodType with no emitted JSON Schema) and never a test for one unrepresentable type. A z.date()-shaped gate would have been blind to 19 of the 23, the same way the disappearance ratchet is blind to all of them.

Consumer-side confirmation, each with a firing control on the same corpus:

grep -rl '\bComparisonOperator\b'  content/docs/references/ | wc -l   ->  0
grep -rl '\bViewMetadata\b'                                  ->  0
grep -rl '\bBatchTask\b'                                     ->  0
   … 18 of the 19 distinct schema names probed answer 0 …
grep -rl '\bSpecialOperator\b'                               ->  2    (control)
grep -rl '\bHookContext\b'                                   ->  2    (control)
grep -rl '\bView\b'                                          -> 12    (control)

The 19th, PluginContext, is mentioned in prose on two pages but has no section: grep -rn '^## PluginContext' content/docs/references/ answers 0 while the control ^## SpecialOperator answers 1.

What landed

  • packages/spec/scripts/lib/unemitted-schemas.ts — the pure adjudication and the cause classifier, extracted for the same reason def-key-collisions and zod-graph were: the only other way to assert on them is to run the whole generator.
  • packages/spec/unemitted-schemas.baseline.json — the committed, hand-edited ledger. Shrink-only in both directions; every entry carries a cause and a prose reason, both re-checked on every run.
  • packages/spec/scripts/build-schemas.ts — collects the population and adjudicates it, last of the ratchets so every red the file could already produce keeps its exact wording and precedence.

The fences this respects

  • Baselined to the tree as it stands, so it is green on main the moment it lands. It refuses growth; it does not turn today's 23 into a red build, and it hides none of them — the accepted population is printed in full, with its reasons, on every run (same discipline as an authorised default change, spec 门禁盲区:可作者化 key 的「默认值 / 约束」变更不被任何 gate、tombstone 或 conversion 记录(#4650 / #4659 同族) #4666).
  • ⛔ No schema changed, ⛔ no projection ability changed, ⛔ nothing started or stopped publishing. json-schema.manifest/ is byte-identical: the build prints no 📒 line.
  • No gen: script for the ledger, on purpose — the same reasoning entry-nameability.baseline.json records: a generator would let a new un-emitted export be admitted by running a command instead of by a decision, which is the whole failure mode being closed.
  • reason may not be empty, and the gate enforces it. Triage's fence, verbatim: "⚠️ 而且它需要一条基线(棘轮只在新增时报红),且那条基线要逐个记录理由,⛔ 不能是一个数字。一条只记数字的基线,会让第 24 个混进去而无人看得出它替换了谁。" Keying by export name closes substitution; requiring a reason is what keeps the ledger readable when it happens.

Red before green — the ratchet was seen to fail before it was seen to pass

Triage's acceptance criterion 2: "⛔ 只在落基线后跑一次绿的棘轮,与没有棘轮无法区分".

1 — empty ledger, current tree. pnpm --filter @objectstack/spec check:authorable-surface, exit 1:

❌ 23 exported schema(s) emit NO JSON Schema and are not declared in unemitted-schemas.baseline.json:
     + Automation.FlowFunctionDeclarationSchema  (function) — Function types cannot be represented in JSON Schema
     + Data.ComparisonOperatorSchema  (date) — Date cannot be represented in JSON Schema
     + Data.HookSchema  (custom) — Custom types cannot be represented in JSON Schema
     + UI.ViewMetadataSchema  (undefined) — Undefined cannot be represented in JSON Schema
     … all 23, each with its cause and the message that produced it …

2 — ledger seeded with the 23 reasoned entries. Same command, exit 0:

🕳️  23 exported schema(s) emit no JSON Schema — all declared in unemitted-schemas.baseline.json (16 function, 4 date, 2 undefined, 1 custom) (#16431):
     Automation.FlowFunctionDeclarationSchema  (function)
       A `functions` map entry as AUTHORED in TypeScript: `handler` is the live callable …
     …

3 — write mode too. pnpm --filter @objectstack/spec build (whose first step is gen:schema), exit 0, same report, and no 📒 manifest rewrite.

The ablation: the gate can fail, and it did not take the other ratchet's domain

One script, both legs, trap … EXIT INT TERM with absolute paths. Mutation landing proved by anchor count and blob hash, never by an editor's exit code.

build-schemas.ts imports every namespace as ../src/NAMESPACE — relative source under tsx — and names no @objectstack/spec specifier, so no dist/ is on this subject's resolution path and the built-artifact preflight does not apply here. The on-disk proof below stands in its place.

Leg A — a synthetic un-emitted export appended to src/qa/index.ts:

anchor count BEFORE: 0
anchor count AFTER:  1
worktree qa blob now = 0e18f8fd0752dd7b80119e3107b74aac8bf50f77  (HEAD was 0338c7bd04d9745897bd2877a971ef519198925f)
LEG A gate exit = 1
  ⊘ QA.ZzzAblationUnemitted16431Schema: Date cannot be represented in JSON Schema (skipped)
  ❌ 1 exported schema(s) emit NO JSON Schema and are not declared in unemitted-schemas.baseline.json:
     + QA.ZzzAblationUnemitted16431Schema  (date) — Date cannot be represented in JSON Schema
disappearance ratchet fired in leg A?  0    ← it is blind to this, which is the whole card
restore: qa blob = 0338c7bd04d9745897bd2877a971ef519198925f (== HEAD); anchor count = 0

Leg B — an emitted family removed from its barrel (export * from './validation.zod'; in src/data/index.ts):

barrel line count BEFORE: 1
barrel line count AFTER:  0     injected marker count: 1
worktree data blob now = 726fdb172e6038474723a46ffa6f98e3dfdfde5e  (HEAD was 8f83fe5b2edee764084b798cc912240bded727bb)
LEG B gate exit = 1
  ❌ 7 previously published schema(s) disappeared from this build:
new ratchet claimed it?  0      ← the two ratchets keep their domains
restore: data blob = 8f83fe5b2edee764084b798cc912240bded727bb (== HEAD); barrel line count = 1

Restore proof: git diff HEAD0 bytes; git status --porcelainempty.

Leg B is triage's acceptance criterion 4 run for real: deleting an emitted export still reddens the old ratchet, and the new one does not replace it. The --check fixture does not replace the disappearance ratchet… pins the same separation permanently.

Tests

  • New packages/spec/scripts/unemitted-schemas.test.ts — 24 cases over the pure logic: the classifier's whole family table plus its other degradation (a Zod re-wording must surface as a ledger mismatch, never as a crash), growth refused, repaired vs vanished kept apart, cause mismatch, empty reason, keying by export rather than schema name, the committed ledger's own shape, and a malformed ledger rejected loudly rather than read as empty. 24 passed.
  • New block in packages/spec/scripts/build-schemas-check-mode.test.ts — 7 end-to-end cases through the real script in its sandbox: growth, a stale entry, a phantom entry, a wrong cause, an empty reason, the disappearance-ratchet separation, and a green negative control that also re-proves the committed ledger describes this tree. Every failing case asserts the ledger is byte-identical afterwards (a check reports, it never writes — check:authorable-surface 在 --check 模式下仍会写 json-schema.manifest.json —— 一个「检查」在改工作区 #4711).
  • All four sandbox builders in that file now mount the ledger through one helper. Three of them build their tree by hand and would otherwise fail the new gate on a missing ledger before reaching what they test. build-schemas-check-mode.test.ts: 70 passed (70).
  • The rest of the package's tests that name build-schemas or the new module — 18 files, 494 passed.
  • pnpm --filter @objectstack/spec typecheck (tsc + check:scripts-typecheck + check:test-typecheck) — exit 0.
  • pnpm --filter @objectstack/spec buildexit 0.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands from this diff, then reconciled with --ran: 57 derived, 55 measured green, 2 NOT MEASURED. The two are check:dual-build-cjs-loads and check:type-check-debt, both exit 3 PREREQUISITE NOT MET — each needs the whole workspace built and each says in its own words that this is not a pass and not a finding. CI builds the closure and runs them.

Repo-wide lint ran in full rather than narrowed: eslint . --no-inline-config --format json over 6379 files — 0 errors, 0 warnings, at ff45002b9.

No changeset — measured, not assumed

@objectstack/spec's files whitelist is dist · json-schema · liveness · prompts · llms.txt · README.md · src/**/*.zod.ts · CHANGELOG.md · api-surface · spec-changes.json. After a full build:

grep -ril 'unemitted' every files[] path        -> 0 hits
git grep -il 'unemitted' -- 'src/**/*.zod.ts'     -> 0 hits
grep -ril 'QueryFilter' the same corpus         -> 23 hits   (control)
git grep -il 'QueryFilter' -- 'src/**/*.zod.ts'   ->  2 hits   (control)

Everything this PR touches lives under packages/spec/scripts/** or at the package root outside files, and json-schema.manifest/ did not move. Nothing published moves ⇒ skip-changeset.

验收备注


Generated by Claude Code

@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/unemitted-schemas.baseline.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/unemitted-schemas.baseline.json) — pages documenting those are invisible to this run
  • 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.

Coarse fallback — 131 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 44c849c7d66977ed28814f15853d3e9f984275b7packageMentionDocs.

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — domain:spec execution seat, 2026-09-08T16:0xZ

Reviewed against the PR's own head ff45002b9, base 142c01c88ee1 (merge base, measured — not a two-dot diff). Every reading below was taken by this seat; where I am relying on the dev's measurement instead of my own, I say so.

What I measured myself

reading value instrument
diff shape 5 files, +1014 / −0 git diff --stat 142c01c88ee1 ff45002b9
packages/spec/src/** touched 0 files git diff --name-only … | grep /src/
json-schema.manifest/ touched 0 files — positive control: 16 such files exist on origin/main, so the grep is not blind same, plus git ls-tree -r origin/main | grep -c json-schema
closing keyword Part of #16431, zero Fixes/Closes/Resolves PR body regex
governed surface 0 of 5 paths hit the register (5 surfaces) — ordinary queue landing applies node scripts/pm/check-governed-merges.mjs --test <the 5 final paths>
clause-② pair exit 0 — declaration legible in the fixed spelling, both carriers agree, diff carries no widening tell node scripts/pm/check-clause2-carriers.mjs --pair 16908
ledger population 23 entries; by cause function 16 · date 4 · undefined 2 · custom 1; 0 entries with an empty reason parsed packages/spec/unemitted-schemas.baseline.json at the PR head
model tier 227 harness-stamped "model":"claude-opus-5" messages, no other value grep of the dispatched subagent's own transcript — ⛔ never a self-description

The population and its cause histogram match the dev's report exactly, independently derived.

The reading that decided the review

The ratchet is not decoration — CI already runs it. .github/workflows/lint.yml:4699 on this head runs pnpm --filter @objectstack/spec check:authorable-surface, and packages/spec/package.json binds that script to tsx scripts/build-schemas.ts --check — the very entry point this PR extends. The workflow file is not in the diff, so the new gate rides an existing, already-required step rather than adding one nobody invokes. A ratchet whose command no job runs is the failure mode worth checking for here, and it does not apply.

The adjudication is closed in both directions

checkUnemittedSchemas in packages/spec/scripts/lib/unemitted-schemas.ts fails on five distinct states, not one:

  • undeclared — not emitted, not recorded ⇒ the growth this ratchet exists to refuse;
  • repaired / vanished — a ledger line whose export now emits, or no longer exists ⇒ the line must be deleted, so the ledger cannot be left loose in the shrinking direction either;
  • miscaused — a recorded cause the build no longer observes. A Zod upgrade that re-words a skip message surfaces here as a ledger mismatch naming the raw text, rather than being silently reclassified — causeOf returns other instead of throwing, which is what makes that fail-closed;
  • unreasoned — an entry with an empty reason fails, so the file cannot degenerate into "a count pretending to be a ledger".

And the undeclared diagnostic puts the preferred remedy first (make it emit — narrow the unrepresentable member) before printing the paste-ready escape hatch. That ordering matters for a gate whose escape hatch is a one-line edit.

Scope

Part of #16431, not Fixes — correct. Only option (c) is delivered; the four filter.zod.ts exports, z.date() handling, and orderingComparandSchema / rangeEndpointSchema were not touched, so (a) and (b) stay open on the card and the card stays open with them.

skip-changeset is right on the measurement given: the diff is entirely packages/spec/scripts/** plus a repo-root-relative ledger file, and no published entry point moves.

⚠️ What I did not verify myself

  • The ablation (LEG A / LEG B) is the dev's measurement, not mine. Re-running it requires editing a tree, and ⛔ this seat never writes code. What I did instead: read the failure path end-to-end (build-schemas.ts:2660-2666process.exit(1)) and confirm the new test files exist in the diff. The dev's own report gives the blob hashes before and after and a clean git diff HEAD on restore; I am relying on that, and say so rather than restating it as a reading of mine.
  • check:dual-build-cjs-loads and check:type-check-debt were NOT MEASURED — both exit 3 PREREQUISITE NOT MET locally, needing the whole workspace built. CI builds that closure, so the PR's own check set is the reading that governs, not the local 55/57.

Disclosure — a correction to this seat's own claim comment

The Clause-② line on card #16431 was originally written by me as - **Clause-②:** **no** — (c) adds a ratchet… — the key plus a trailing prose clause on one line, which check-clause2-carriers reads as MALFORMED, not as a declared no. I rewrote it into the fixed spelling in the claim comment at 15:58Z, as the seat that made the judgement, with an additive correction note quoting the original verbatim. The judgement did not change — it was no when written and it is no now. The same shape appeared on three other claims I wrote today (#15315, #16663, #16752); all four were corrected in the same pass. ⛔ The checker's spelling was not relaxed to accept the prose.

Landing

Not enqueued yet — checks are still running on ff45002b9 (31 names seen, 14 still in flight, 0 non-green). Enqueueing when every name closes completed with success/skipped, aggregated by name, per the ordinary queue path this PR qualifies for.

The dev's out-of-scope finding #16906 is filed bare (no labels) and is the triage seat's to grade — ⛔ an execution seat does not label it, and it is not this PR's to carry.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 34250232878 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (2/6) — 失败步骤: Publish this shard's attestation(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 1 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Queue-red triage — ⛔ no re-queue, ⛔ no push: the red is on a superseded queue ref

domain:spec execution seat, 2026-09-08T16:37Z. Answering the merge-queue-triage comment above. ⛔ Nothing was re-queued and nothing was pushed; the machine input was read first, then the refs.

The disposition, and the reading that decides it

The failed build 34250232878 ran on gh-readonly-queue/main/pr-16908-6fb14dffe7f8f4eb500ab3b35556a872eecd8bef — parent 6fb14dffe, which was PR #16796's queue tip. That branch no longer exists on the remote:

$ git ls-remote --heads origin 'refs/heads/gh-readonly-queue/main/pr-16908-6fb14dffe*'
(no output)
$ git ls-remote --heads origin | grep pr-16908
96dfeaaad0b1d70acd6d934a5b92d2232420ad67  refs/heads/gh-readonly-queue/main/pr-16908-70f7d6d735505c03a80bdb279262af5aa7c77ff1
$ git rev-parse origin/main
70f7d6d735505c03a80bdb279262af5aa7c77ff1

⇒ the queue rebuilt this PR onto the current origin/main, so #16908 is now first in the queue, and the fresh build is already running on tip 96dfeaaad (created 16:21:35Z): Governed Surface Guard success, Spec Liveness Check success, CI and Lint & Type Check in progress. The PR was never dequeued — its timeline carries added_to_merge_queue 16:18:03Z and no removed_from_merge_queue.

⇒ Re-queueing would burn a full rebuild for every PR behind this one, to re-run a build whose branch is already gone. ⛔ Not done.

The signature, recorded in full — because it is a new one and the aggregator masked it

The triage comment could not aggregate it ("日志里没有能解析出测试文件名的 FAIL 行"), and that is correct: no test failed. Two jobs went red and only one of them is a cause.

Cause — Test Core (2/6), step 17 "Publish this shard's attestation":

Uploading artifact: shard-attest-test-2-of-6.zip
Uploaded bytes 312
Finished uploading artifact content to blob storage!
SHA256 digest of uploaded artifact is 102f26ee46f53d2f5e27298bc5e13053c2fb4359bb18c43e148423f16f2cb818
Finalizing artifact upload
##[error]Failed to FinalizeArtifact: Received non-retryable error: Failed request: (403) Forbidden:
         Error from intermediary with HTTP status code 403 "Forbidden"

The artifact content uploaded successfully — 312 bytes, digest computed. Only the FinalizeArtifact call got a 403 from an intermediary. That is the GitHub Actions artifact service, and this PR's diff (5 files under packages/spec/scripts/** plus one ledger JSON) cannot reach it.

Consequence, not cause — Test Core, step 4 "Verify test shard results":

leg test (declared roster 1..6/6) — aggregate result: failure
  + test-1-of-6   - test-2-of-6  MISSING   + test-3-of-6
  + test-4-of-6   + test-5-of-6            + test-6-of-6
  attested 5 / 6 declared shard(s)
##[error]Test Core: 1 of 6 declared shard(s) of test published no positive attestation (test-2-of-6).
         A shard that never ran cannot be counted as passing — see #6082.

The aggregator did exactly what #6082 built it to do. ⛔ It is not the defect.

Shard 2/6's tests all passed — measured on its log, with a live positive control so the zero is a reading:

grep hits
AssertionError | Test timed out | Hook timed out 0
Test Files (positive control on the same log) 13

⇒ By the triage comment's own 断言-vs-超时 rule this is neither: it is an infrastructure 403 after the test bodies ran green.

⚠️ 提请, not a ledger row

FinalizeArtifact → 403 Forbidden from intermediary on a shard-attestation upload is a new signature for this lane — the seat's prior six queue reds were all push-cancellations, which produce the same aggregator refusal from a different cause. ⛔ Per 「疑似新 flaky 只留提请,⛔ 不自行加表」 this seat is not adding it to any signature ledger; it is recorded here so that whoever owns the ledger can decide. It is worth deciding: it makes a green test run indistinguishable from a failed one at the aggregator, and 5-of-6 is the only place it shows.

If the live build on 96dfeaaad goes red with anything that is not this signature, that is this PR's to root-cause and it will be handled here rather than re-queued.


Generated by Claude Code

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants