[WRONG BRANCH] release: v2.41.0-preview.20260903 - #3339
Conversation
…rkflow call (#3262) Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died at startup_failure: a workflow_call cannot grant its callee more than the calling job holds, and dev-version-bump.yml's job declares contents+pull- requests write. #3129 wired the call but never dispatched a release, so this is its first live run. The caller job now declares exactly the callee's two permissions; no other job in release.yml gains anything. Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(release): pass the bump job's permissions through the reusable-workflow call Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died at startup_failure: a workflow_call cannot grant its callee more than the calling job holds, and dev-version-bump.yml's job declares contents+pull- requests write. #3129 wired the call but never dispatched a release, so this is its first live run. The caller job now declares exactly the callee's two permissions; no other job in release.yml gains anything. * devlog(windows): 070 outcome — v2.40.0 released, Windows shards repaired, follow-ups * devlog(windows): 070 — correct the service-lifecycle trigger note * devlog(windows): 070 — record the full 4/4 Windows-green dispatch on the released dev tip * chore(devlog): wp5 release closeout marker --------- Co-authored-by: jun <jun@lidge.dev>
…toggle (#3269) * devlog(release): 080 — bot PR toggle provenance and decision, service-lifecycle trigger gap * chore(devlog): wp1 provenance closeout marker * devlog(release): 080 — toggle applied via REST, read-back true * ci(service-lifecycle): trigger on release.yml changes so the release gate cannot dead-end release.yml requires a service-lifecycle run for the release SHA when any gated path changed since the previous tag, but a release-branch commit that touched only release.yml (the v2.40.0 permissions carry, #3263/#3264) matched no lifecycle trigger path, so both dispatches needed a manual run. The path is now in both trigger lists and the gate regex; tests/ci-workflows keeps the three in sync. --------- Co-authored-by: jun <jun@lidge.dev>
…e_effort_table wp0) (#3272) Co-authored-by: jun <jun@lidge.dev>
…led bundle (#3273) * docs(devlog): Cursor bundle effort-table roadmap (260902_cursor_bundle_effort_table wp0) * feat(cursor): read Cursor Private Inference effort table from the installed bundle Predict the Reasoning ladder from the table compiled into the detected Cursor Private Inference install, with the static 3.18.25 mirror as fallback, and expose the provenance on the integration status route. * fix(cursor): reject partial effort tables and guard the bare gpt-5 regex; gate gemini on supports_reasoning * fix(cursor): bind the bare gpt-5 rule by shape, not minified identifier --------- Co-authored-by: jun <jun@lidge.dev>
…able 5.1 seeds (#3275) * refactor(cursor): canonical Claude-id normalizer replaces the three Fable 5.1 seeds Every Claude spelling Cursor has used (claude-fable-5-1, claude-fable-5.1, claude-5.1-fable, with -thinking/-fast/effort suffixes) resolves to one capability base; wire ids are composed back in the spelling the live GetUsableModels roster exposed, else the spelling the saved config used. * fix(cursor): publish live Claude spelling and Max-Mode evidence only after the cache accepts the capture --------- Co-authored-by: jun <jun@lidge.dev>
* feat(models): advertise max_output_tokens on /v1/models rows Carry an authoritative output ceiling (live capabilities, generated metadata, or a narrowing model-scoped config value) through the catalog and emit capabilities.max_output_tokens for native, routed and combo rows. The long-context threshold stays in pricing.overrides: Cursor derives its own top-level field from that and never reads a raw one. * fix(models): reject unsafe-integer output ceilings on /v1/models rows --------- Co-authored-by: jun <jun@lidge.dev>
…effort table (#3276) * docs(devlog): wp3 amendments c,d * feat(cursor): opt-in effort-variant rows for models outside Cursor's effort table cursorEffortRows (default off) publishes one <id>--<effort> row per supported effort for models Cursor Private Inference renders no Reasoning control for, and resolves the base model plus effort from that id on /v1/responses, /v1/chat/completions and /v1/messages. Off, /v1/models is byte-identical. * test(cursor): avoid a token-looking fixture credential * fix(cursor): effort rows reject --none, skip detection for plain ids, normalize before combo dispatch --------- Co-authored-by: jun <jun@lidge.dev>
…able-less rows (#3277) * feat(gui): Cursor tab shows effort-ladder provenance and a hint for table-less rows The Integrations > Cursor card names whether the Reasoning ladders came from the installed Private Inference bundle (and which version) or the static mirror, marks rows Cursor renders no control for, and points at cursorEffortRows / modelDefaultReasoningEfforts. en + ko strings; the other locales carry the en text because the catalogs are type-checked. * test(gui): cover Cursor tab provenance, table-less hint and effort-row counts * i18n(gui): translate the Cursor provenance strings; allowlist the em-dash marker * gui(cursor): honest static-fallback wording; separate single-window marker from the no-control marker --------- Co-authored-by: jun <jun@lidge.dev>
…e, bundle table, effort rows (#3278) * docs(cursor): identify the Private Inference build, gateway precedence, bundle table, effort rows The guide now tells the reader how to tell the local-agent build from regular Cursor (product.json nameLong), where the effort table lives and that opencodex reads it from the install, how the saved gateway and the CURSOR_LOCAL_AGENT_* environment rank, the real header format, the two ways a table-less model still gets an effort (cursorEffortRows and a provider default), and the /messages wire caveat for Claude effort. * docs(cursor): effort-row selector wins over an ocx-effort directive --------- Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…nd the sidecar (#3286) * docs(devlog): Gemini 3.8 Flash rollout roadmap (260903_gemini_38_rollout wp0) Diff-level roadmap for rolling Gemini 3.8 Flash across every surface that currently names 3.6/3.7. Two findings shape the plan, both first-hand rather than inherited from the 3.7 unit: - Google documents 3.7 Flash as "remains fully supported", and a live CCA fetchAvailableModels call returns 3.5, 3.6, 3.7 and 3.8 together. So this is an ADDITIVE rollout with a default move, not the hard replacement the 3.6 to 3.7 migration performed. - CCA publishes gemini-3.8-flash-{low,medium,high} and no -tiered row, so 3.8 is structurally 3.6-shaped: it belongs in ANTIGRAVITY_EFFORT_WIRE_MAP, not in the single-wire thinkingLevel map 3.7 uses. Three adversarial audit rounds (independent gpt-5.6-sol reviewer at high effort) took this from FAIL to PASS. The two most valuable findings were confirmed against the live backend: the Claude SDK identity paragraph 429s on 3.8 exactly as on 3.7, so that strip guard must widen before 3.8 becomes the default; and static vs discovered effort resolution returned different request bodies for the same input. Docs only. No runtime change in this commit. * feat(antigravity): expose Gemini 3.8 Flash with its suffix effort ladder Google shipped Gemini 3.8 Flash on 2026-09-02 and CCA already returns it ranked first in the Recommended sort, but discovery published it as three uncollapsed rows with no effort ladder at all. Additive, not a replacement. The 3.6 generation vanished from CCA the moment 3.7 shipped, which is why RETIRED_FLASH_TIERS exists. 3.8 did not do that: Google documents 3.7 Flash as remaining fully supported, and a live fetchAvailableModels call returns 3.8, 3.7 and 3.6 together. So 3.7 stays picker-visible and every retirement mapping is left where it is. 3.8 tiers ride SUFFIX wire ids, unlike the single -tiered id 3.7 uses, so it is registered through ANTIGRAVITY_EFFORT_WIRE_MAP. Two consequences, each proven against the backend rather than assumed: - The suffix is the sole tier carrier. CCA accepts a -low wire id paired with a HIGH thinking level and returns 200, so a contradictory pair would run at an unknowable tier. ANTIGRAVITY_SUFFIX_TIER_MODELS suppresses the redundant level and makes static resolution byte-identical to the discovery path, which never emitted one. That divergence also swallowed clamped efforts: max/xhigh/ultra resolved differently before and after discovery ran. - The Claude SDK identity paragraph 429s on 3.8 exactly as on 3.7. The strip guard was an equality check on one model id; with 3.8 becoming the default it would have returned RESOURCE_EXHAUSTED for every Claude-Agent-shaped request while looking like a quota problem. Membership is now canonicalized so raw suffix selectors from a partial ladder are covered too. GEMINI_FLASH_WIRE_ID is renamed GEMINI_RETIRED_FLASH_TARGET_WIRE_ID: it holds the 3.7 redirect target, and after 3.8 became current the old name pointed readers at the wrong model. Verification: bun run typecheck, plus focused tests/google-antigravity-wire.test.ts, gemini-37-flash-migration.test.ts, google-adapter.test.ts, provider-registry-parity.test.ts - 166 pass, 0 fail. * feat(models): price and publish Gemini 3.8 Flash metadata Adds the google/gemini-3.8-flash source record (regenerated, never hand- edited) and the Antigravity + direct Google price rows. The source record deliberately omits `cost`. Bundled generated metadata is consulted before the expected-price overlay and returns status "verified", so copying the adjacent 3.6 record - which does carry a cost block - would make the Antigravity row unreachable and report CCA spend as a verified price. Google publishes Developer API prices; it does not publish that Cloud Code Assist charges them. The overlay is verified-derived for exactly that reason: the number is proven, the claim that Antigravity bills it is inferred. GEMINI_38_FLASH is its own constant despite matching 3.7 today, so a later re-verification of one cannot silently move the other. Nothing is retired here. Reconciliation refreshes capability records from the registry, so an existing user picks up 3.8 on the next start, and a user who deliberately chose 3.7 keeps it - Google still serves that model. The new reconcile case asserts that preservation directly; the pre-existing 3.5 case only exercised the opposite branch, where a retired default gets healed. Verification: bun run typecheck, plus focused usage-cost, model-metadata-sync, oauth-provider-reconcile and gemini-37-flash-migration - 137 pass, 0 fail. * feat(models): carry Gemini 3.8 Flash to the remaining surfaces Direct Google, the free-provider directory, the Gemini web-search sidecar default, a preemptive Cursor seed, and the sidecar docs row. The direct Google ladder omits `minimal` even though its 3.5/3.6/3.7 neighbours list it: Google documents `minimal` as a validation error for this generation. defaultModel stays gemini-3.5-flash - adding a model elsewhere must not silently move an existing API-key user's default. The free-directory Gemini row gets a row-specific lastVerified rather than a bumped shared constant, which would have stamped a 2026-09-03 check onto every other provider row that nobody re-checked. The Cursor seed follows the documented glm-5.3 precedent: Cursor has not announced 3.8, and the static catalog is intersected with the live roster, so the entry stays invisible until Cursor lists it. Left alone deliberately: GEMINI_DIRECT_WIRE_RENAMES gains no 3.8 entry, because no source anywhere proves a gemini-3.8-flash-tiered id exists, and the providers.md rename example keeps naming 3.7 for the same reason. Verification: bun run typecheck, plus 15 focused test files - 753 pass, 1 fail. The failure is pre-existing and unrelated: 'Antigravity live model discovery uses the CCA agent list' leaks a credential across files and fails on the clean tree too, while passing when that file runs alone. * fix(antigravity): strip the rejected paragraph by routed generation, not selector The maintainer review found a hole three audit rounds missed, and a live probe confirmed it: a saved gemini-3.6-flash selection does not call 3.6. Rule 0 redirects it onto gemini-3.7-flash-tiered, which rejects the Claude Agent SDK identity paragraph with a 429 that reads as quota exhaustion. The guard keyed on the selector through canonicalAntigravityUsageModel, which covers the collapsed base and the raw suffix rows. It cannot cover retired ids, because those deliberately keep their OWN identity for usage accounting - that is the rule protecting historical spend from being relabelled. Two individually correct mechanisms combined into a gap, and every saved 3.6/3.5 config would have kept 429ing after this rollout. Judging the routed wire id closes it, and naming a wire spelling once now covers every selector that can reach that generation rather than requiring the set to enumerate selectors that redirect into it. The old test asserting a 3.6 selection KEEPS the paragraph was asserting the bug; it is replaced by one proving the strip, plus a real control on claude-sonnet-4-6 - a model with no recorded rejection, where the paragraph is literally true. Also folds the review's second point: the direct google 3.7 row no longer advertises `minimal`. Google documents it as a validation error for that generation, which is the same evidence 3.8 relies on, and the line was already being edited here. 3.5 and 3.6 keep theirs - their pages still list it. Verification: bun run typecheck, plus 12 focused test files - 681 pass, 0 fail. --------- Co-authored-by: jun <jun@lidge.dev>
…on (#3289) * docs(devlog): plan the bug-labelled PR and issue drawdown campaign Roadmap unit for the September bug drawdown on dev: 000_plan.md locks a ten-work-phase map from live gh evidence, and 010-090 carry per-item root cause, file:line fix maps, and RED-before-fix assertions sourced from six parallel read-only investigators. Four decade docs cover the open bug-labelled PRs as adoption phases with immutable base/head anchors. Five cover the bug-labelled issues, three of which terminate as NEEDS_HUMAN with the ruled-out causes recorded rather than a speculative patch. * fix(responses): stop the background write storm on responses-state.json A completed response mutates the continuation cache and calls schedulePersist, and the process-level timer already coalesces those triggers. Under concurrent completions, though, the revision kept changing during the async write, so the bounded-retry loop rewrote the whole snapshot up to four times per background tick. The existing test codified that as the contract. Ordinary background persistence now gets a single attempt. When the snapshot is unstable it schedules the existing delayed follow-up instead of rewriting immediately, so an unstable revision costs one extra debounce interval rather than three extra full atomic rewrites. Graceful shutdown keeps the bounded retry: that path drains requests first and has to land. Closes #3141 --------- Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): plan the bug-labelled PR and issue drawdown campaign Roadmap unit for the September bug drawdown on dev: 000_plan.md locks a ten-work-phase map from live gh evidence, and 010-090 carry per-item root cause, file:line fix maps, and RED-before-fix assertions sourced from six parallel read-only investigators. Four decade docs cover the open bug-labelled PRs as adoption phases with immutable base/head anchors. Five cover the bug-labelled issues, three of which terminate as NEEDS_HUMAN with the ruled-out causes recorded rather than a speculative patch. * fix(responses): stop the background write storm on responses-state.json A completed response mutates the continuation cache and calls schedulePersist, and the process-level timer already coalesces those triggers. Under concurrent completions, though, the revision kept changing during the async write, so the bounded-retry loop rewrote the whole snapshot up to four times per background tick. The existing test codified that as the contract. Ordinary background persistence now gets a single attempt. When the snapshot is unstable it schedules the existing delayed follow-up instead of rewriting immediately, so an unstable revision costs one extra debounce interval rather than three extra full atomic rewrites. Graceful shutdown keeps the bounded retry: that path drains requests first and has to land. Closes #3141 * fix(gui): stop the log panel from jittering as rows scroll in The virtualized log table measured rows dynamically from a 44px estimate while its multiline cells are roughly twice that, and the table itself was on automatic layout. Every changed mounted-row subset therefore recomputed intrinsic column widths, model wrapping changed row heights, and that fed another virtualizer measurement. Fixed layout with an explicit ten-column schema removes the feedback loop: column widths no longer depend on which rows happen to be mounted. The estimate moves to the observed mean, getItemKey keys measurements to the request rather than the index so a prepend does not shift them, and the wrapper disables scroll anchoring and reserves the scrollbar gutter. Closes #3152 --------- Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): Gemini 3.8 Flash rollout roadmap (260903_gemini_38_rollout wp0)
Diff-level roadmap for rolling Gemini 3.8 Flash across every surface that
currently names 3.6/3.7.
Two findings shape the plan, both first-hand rather than inherited from the
3.7 unit:
- Google documents 3.7 Flash as "remains fully supported", and a live CCA
fetchAvailableModels call returns 3.5, 3.6, 3.7 and 3.8 together. So this
is an ADDITIVE rollout with a default move, not the hard replacement the
3.6 to 3.7 migration performed.
- CCA publishes gemini-3.8-flash-{low,medium,high} and no -tiered row, so 3.8
is structurally 3.6-shaped: it belongs in ANTIGRAVITY_EFFORT_WIRE_MAP, not
in the single-wire thinkingLevel map 3.7 uses.
Three adversarial audit rounds (independent gpt-5.6-sol reviewer at high
effort) took this from FAIL to PASS. The two most valuable findings were
confirmed against the live backend: the Claude SDK identity paragraph 429s on
3.8 exactly as on 3.7, so that strip guard must widen before 3.8 becomes the
default; and static vs discovered effort resolution returned different request
bodies for the same input.
Docs only. No runtime change in this commit.
* feat(antigravity): expose Gemini 3.8 Flash with its suffix effort ladder
Google shipped Gemini 3.8 Flash on 2026-09-02 and CCA already returns it
ranked first in the Recommended sort, but discovery published it as three
uncollapsed rows with no effort ladder at all.
Additive, not a replacement. The 3.6 generation vanished from CCA the moment
3.7 shipped, which is why RETIRED_FLASH_TIERS exists. 3.8 did not do that:
Google documents 3.7 Flash as remaining fully supported, and a live
fetchAvailableModels call returns 3.8, 3.7 and 3.6 together. So 3.7 stays
picker-visible and every retirement mapping is left where it is.
3.8 tiers ride SUFFIX wire ids, unlike the single -tiered id 3.7 uses, so it
is registered through ANTIGRAVITY_EFFORT_WIRE_MAP. Two consequences, each
proven against the backend rather than assumed:
- The suffix is the sole tier carrier. CCA accepts a -low wire id paired with
a HIGH thinking level and returns 200, so a contradictory pair would run at
an unknowable tier. ANTIGRAVITY_SUFFIX_TIER_MODELS suppresses the redundant
level and makes static resolution byte-identical to the discovery path,
which never emitted one. That divergence also swallowed clamped efforts:
max/xhigh/ultra resolved differently before and after discovery ran.
- The Claude SDK identity paragraph 429s on 3.8 exactly as on 3.7. The strip
guard was an equality check on one model id; with 3.8 becoming the default
it would have returned RESOURCE_EXHAUSTED for every Claude-Agent-shaped
request while looking like a quota problem. Membership is now canonicalized
so raw suffix selectors from a partial ladder are covered too.
GEMINI_FLASH_WIRE_ID is renamed GEMINI_RETIRED_FLASH_TARGET_WIRE_ID: it holds
the 3.7 redirect target, and after 3.8 became current the old name pointed
readers at the wrong model.
Verification: bun run typecheck, plus focused
tests/google-antigravity-wire.test.ts, gemini-37-flash-migration.test.ts,
google-adapter.test.ts, provider-registry-parity.test.ts - 166 pass, 0 fail.
* feat(models): price and publish Gemini 3.8 Flash metadata
Adds the google/gemini-3.8-flash source record (regenerated, never hand-
edited) and the Antigravity + direct Google price rows.
The source record deliberately omits `cost`. Bundled generated metadata is
consulted before the expected-price overlay and returns status "verified", so
copying the adjacent 3.6 record - which does carry a cost block - would make
the Antigravity row unreachable and report CCA spend as a verified price.
Google publishes Developer API prices; it does not publish that Cloud Code
Assist charges them. The overlay is verified-derived for exactly that reason:
the number is proven, the claim that Antigravity bills it is inferred.
GEMINI_38_FLASH is its own constant despite matching 3.7 today, so a later
re-verification of one cannot silently move the other.
Nothing is retired here. Reconciliation refreshes capability records from the
registry, so an existing user picks up 3.8 on the next start, and a user who
deliberately chose 3.7 keeps it - Google still serves that model. The new
reconcile case asserts that preservation directly; the pre-existing 3.5 case
only exercised the opposite branch, where a retired default gets healed.
Verification: bun run typecheck, plus focused usage-cost, model-metadata-sync,
oauth-provider-reconcile and gemini-37-flash-migration - 137 pass, 0 fail.
* feat(models): carry Gemini 3.8 Flash to the remaining surfaces
Direct Google, the free-provider directory, the Gemini web-search sidecar
default, a preemptive Cursor seed, and the sidecar docs row.
The direct Google ladder omits `minimal` even though its 3.5/3.6/3.7
neighbours list it: Google documents `minimal` as a validation error for this
generation. defaultModel stays gemini-3.5-flash - adding a model elsewhere
must not silently move an existing API-key user's default.
The free-directory Gemini row gets a row-specific lastVerified rather than a
bumped shared constant, which would have stamped a 2026-09-03 check onto every
other provider row that nobody re-checked.
The Cursor seed follows the documented glm-5.3 precedent: Cursor has not
announced 3.8, and the static catalog is intersected with the live roster, so
the entry stays invisible until Cursor lists it.
Left alone deliberately: GEMINI_DIRECT_WIRE_RENAMES gains no 3.8 entry,
because no source anywhere proves a gemini-3.8-flash-tiered id exists, and the
providers.md rename example keeps naming 3.7 for the same reason.
Verification: bun run typecheck, plus 15 focused test files - 753 pass, 1 fail.
The failure is pre-existing and unrelated: 'Antigravity live model discovery
uses the CCA agent list' leaks a credential across files and fails on the
clean tree too, while passing when that file runs alone.
* fix(antigravity): strip the rejected paragraph by routed generation, not selector
The maintainer review found a hole three audit rounds missed, and a live probe
confirmed it: a saved gemini-3.6-flash selection does not call 3.6. Rule 0
redirects it onto gemini-3.7-flash-tiered, which rejects the Claude Agent SDK
identity paragraph with a 429 that reads as quota exhaustion.
The guard keyed on the selector through canonicalAntigravityUsageModel, which
covers the collapsed base and the raw suffix rows. It cannot cover retired ids,
because those deliberately keep their OWN identity for usage accounting - that
is the rule protecting historical spend from being relabelled. Two individually
correct mechanisms combined into a gap, and every saved 3.6/3.5 config would
have kept 429ing after this rollout.
Judging the routed wire id closes it, and naming a wire spelling once now covers
every selector that can reach that generation rather than requiring the set to
enumerate selectors that redirect into it. The old test asserting a 3.6
selection KEEPS the paragraph was asserting the bug; it is replaced by one
proving the strip, plus a real control on claude-sonnet-4-6 - a model with no
recorded rejection, where the paragraph is literally true.
Also folds the review's second point: the direct google 3.7 row no longer
advertises `minimal`. Google documents it as a validation error for that
generation, which is the same evidence 3.8 relies on, and the line was already
being edited here. 3.5 and 3.6 keep theirs - their pages still list it.
Verification: bun run typecheck, plus 12 focused test files - 681 pass, 0 fail.
* docs(devlog): Gemini 3.8 Flash rollout closeout
Records the merge (3d3c4fe), the 25/25 CI rollup on the exact head, and the
audit history - including the maintainer-review finding that three adversarial
plan rounds missed because it lived in the interaction between two individually
correct mechanisms.
---------
Co-authored-by: jun <jun@lidge.dev>
…ombo 503 (#3294) Request-rate 429s without Retry-After no longer inherit the 60s quota cooldown. Combo unavailable responses advertise the soonest remaining target cooldown.
* fix(usage): aggregate complete ledger incrementally * test(usage): bind overlay retry assertion to scan version --------- Co-authored-by: jun <jun@lidge.dev>
* fix(providers): save the dashboard provider editor atomically
The dashboard's JSON editor serialized the redacted config DTO and PUT it to
/api/config, which the server rejects on purpose, so Save always failed with
"Full config PUT is disabled."
Fanning the edit out to per-provider POST/PATCH/DELETE would have fixed the
error message and introduced a worse bug: each of those persists
independently, so a mid-sequence failure leaves half the edit on disk, and
every field absent from the public DTO is lost on the way back.
So the write stays server-side. PUT /api/providers takes { baseline, next }:
the GUI sends only what it can see, the server compares the baseline against
the current public projection, merges next into freshly read persisted
providers while keeping api keys, pools, headers and other private fields,
validates everything, and commits once. A stale baseline is a 409 rather than
a silent overwrite, and derived markers like hasApiKey are rejected instead of
being written back as data. The /api/config 405 is unchanged.
Closes #3280
* fix(providers): round-trip complete safe editor config
* fix(providers): keep runtime metadata out of batch edits
* fix(providers): restore the safeConfigDTO runtime-metadata contract
The previous commit reclassified modelMaxInputTokens as runtime so it could
not be editor write authority. That was right, but the same commit also
relaxed the existing safeConfigDTO assertion so the field could appear in the
public DTO.
That assertion was not incidental: dev already listed modelMaxInputTokens
among the values safeConfigDTO must never serialize. Weakening a security
contract to fit a new implementation is backwards, so the test is restored
verbatim and the implementation now satisfies it as written.
---------
Co-authored-by: jun <jun@lidge.dev>
# Conflicts: # src/combos/resolve.ts # tests/combos.test.ts
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Co-authored-by: jun <jun@lidge.dev>
Records the sixteen merges with their ancestor-proved shas, the three issues that ended NEEDS_HUMAN and why a diff would have been a guess, and the four things the process caught that a green build would not: a plan audit that failed four times over two factual errors in my own writeup, an allowlist that passed every test and rejected a real config in the browser, CI catching two contract breaks in my own change plus a subagent relaxing an existing security assertion to fit its implementation, and a contribution that silently reverted an earlier fix. Also lists the three macOS timing flakies that recurred across this campaign, so the next person does not re-investigate them. Co-authored-by: jun <jun@lidge.dev>
Meta shipped Muse Spark 1.3 on 2026-09-02. Command Code publishes meta/muse-spark-1.3 and meta/muse-spark-1.3-contributor alongside the 1.2 pair, and OpenCode Zen serves muse-spark-1.3-contributor (Go) and muse-spark-1.3-contributor-free (Zen) on the same wire as their 1.2 siblings. Additive, not a replacement: every 1.2 and 1.1 id stays exactly where it is. 1.3 inherits the 1.2 spec on every surface that already named 1.2, because it is the same-shaped successor on the same gateway. Four surfaces move together, and three of them would have failed silently had 1.3 only been added to the first: - Effort ladder (low/medium/high/xhigh/max) for both 1.3 ids, so a client that sends an effort gets it forwarded rather than stripped. - Command Code image models, so the catalog does not advertise a multimodal model as text-only. - OpenCode Go registry: Responses wire default, the 1M context window, and text+image. Zen Go publishes no modality or window metadata, so without these declarations 1.3 regresses to the 128k default and the Codex app blocks image attachments client-side — the exact pair of defects the 1.2 entries were added to fix. - The Muse web_search guard was an equality check on the 1.2 id. Selecting 1.3 would have let Codex's search_content_types field reach a gateway that 400s it. It is now a membership set. Verification: focused bun test on the four Muse/Command Code suites (55 pass) plus bun run typecheck. The repository-wide local suite was not run at the user's instruction; CI is the full gate. Co-authored-by: jun <jun@lidge.dev>
…and gate it (#3318) CREDITS.md records the landings whose original author is named in prose and nowhere a tool can read: 27 commits across 26 contributor pull requests, found by scanning dev commit trailers against maintainer closure comments on the 119 closed-unmerged external PRs since #2400. Rows are graded by what the maintainer actually wrote - carried work where code, design, or tests shipped, report and diagnosis where the fix exists because of the report and the branch's approach was explicitly not the vehicle. The trailers cannot be added retroactively: every affected commit but one sits inside a published tag, and dev/main/preview each block force-pushes. MAINTAINERS.md already says authorship in git history is not rewritten, so the repair goes forward into a file, into the release notes, and into a gate. missing_coauthor_credit fails a pull request whose own text says it reimplements, supersedes, carries, or rebases another author's pull request until a Co-authored-by trailer names that author. Fail-open on an unresolved lookup, capped at five per run, matched on git identity rather than login, and cleared by attribution-approved. Verification: node --test .github/scripts/*.test.cjs 541 pass 0 fail; bun test tests/ci-workflows.test.ts 135 pass; bun run test:changed 140 pass; privacy:scan and typecheck clean.
Records the merge 7a529a2, the six release bodies credited afterwards, and the four defects caught by a check rather than by reading. Docs only.
* docs(devlog): Muse Spark direct-provider roadmap and plan-credential verdict wp0 of 260903_muse_spark_plan_oauth. Docs only; no runtime change. Two questions, two different answers. Can opencodex reach Muse Spark on Meta's own endpoint? Yes, and 010 is a diff-level plan for it: an OpenAI-compatible Responses provider at https://api.meta.ai/v1 built entirely from published spec. No API key was issued; the only contact with the endpoint was an unauthenticated GET that returned 401 invalid_api_key, which proves the host routes /v1 and answers in OpenAI error shape. Can a Muse Code subscription drive it? No, and that answer changed twice. The first draft said no third-party OAuth existed, sourced from a docs search returning 'No matching results' and Authentication's flat 'every request needs an API key'. Installing the CLI disproved it in one command: muse login opens a browser device-approval flow at auth.meta.com/oauth/device that Meta does not document. Finding it did not make it usable. The plan then became: complete a login, extract the credential, fire it at api.meta.ai, ship if it returns 200. The A-gate reviewer named the flaw - that experiment tests whether enforcement is absent, not whether use is permitted, and Meta answered the second question in writing before anyone asked ('This credential is for use with Muse Code only'). An unenforced restriction is still a restriction. So wp2 closes NOOP: no credential extracted, no login completed, no API test, and none exists on this machine. The user-visible half ships in wp1's provider note, which states outright that a Muse Code subscription does not apply here and every call is metered. Four adversarial audit rounds against one independent gpt-5.6-sol reviewer (FAIL, FAIL, FAIL, GO-WITH-FIXES) found what reading alone did not. Two were invisible from the vendor docs and only existed in this repository: - Provider id 'meta' would have hijacked 'meta/muse-spark-1.3', a live Command Code selector merged last week. router.ts:676 resolves the prefix against configured providers, so a working model reference would have silently changed destination. Now 'meta-model', with a routing regression required. - The advertised 'minimal' effort would have been rewritten to 'low' by reasoning-effort.ts:171. The registry array looked correct the whole time, which is why the tests now assert the built Responses body. Also folded: liveModels false (no authenticated roster was ever observed), preserveCustomDestination, decided price overlays with full Cost4, the order-sensitive parity roster, and the stack collapsed to a single PR. * feat(providers): add the direct Meta Model API provider Muse Spark reached opencodex through resellers only (command-code, opencode-go). This adds Meta's own endpoint as meta-model, an openai-responses key provider on https://api.meta.ai/v1. Built entirely from published spec. No API key was issued: the only contact with the endpoint was an unauthenticated GET /v1/models returning 401 invalid_api_key, which proves the host routes /v1 and answers in OpenAI error shape. Every value traces to a vendor page recorded in the plan's claim ledger, and anything Meta does not publish is absent rather than guessed -- notably max output tokens, whose only apparent value (131072) lives inside a third-party config sample while the protocol docs call the real limit model-dependent. Four decisions are not obvious from the vendor docs, and three of them come from this repository rather than from Meta. The id is meta-model, not meta. router.ts resolves a <provider>/<model> prefix against configured providers first, so a provider named meta would have captured meta/muse-spark-1.3 -- a live Command Code selector merged last week -- and silently redirected a working model reference to a different vendor and a different bill. It would also have derived META_API_KEY, which is the Muse Code CLI's variable, not this API's MODEL_API_KEY. The effort ladder carries an identity wire map. Meta publishes minimal/low/medium/high/xhigh and excludes none for Muse Spark (HTTP 400); max and ultra are absent from its list entirely. But reasoning-effort.ts rewrites minimal to low unless a model-scoped map says otherwise, so the picker would have advertised an effort the wire never sent. The registry array looked correct throughout, which is why the test asserts the built Responses body instead. liveModels is false. No authenticated /v1/models payload was ever observed, and Meta serves image and voice families on this same base URL, so discovery would publish rows this Responses-agent provider cannot drive. preserveCustomDestination is set, so a user who already owns a custom provider under this id keeps their destination and their key. The note states plainly that a Muse Code subscription does NOT apply here. Meta scopes that credential to its own CLI and meters every other key per token, so a user assuming their plan covers this would be billed pay-as-you-go. The Contributor tier's ~92% discount is disclosed as what it is: Meta trains on those prompts. Also: two price overlays (meta-model has no jawcode alias, so without them the cost column renders nothing), the order-sensitive parity roster, and the docs-site provider tables. Plan, claim ledger, and the four-round audit trail: devlog/_plan/260903_muse_spark_plan_oauth/. * fix(providers): name the env var opencodex actually reads for Meta CodeRabbit review findings on #3321. Two were user-facing defects. The docs told a user to set MODEL_API_KEY, which is Meta's own name for the variable. opencodex derives the env var from the provider id, so it reads META_MODEL_API_KEY and would never have seen the key. Following the docs verbatim produced a provider that silently failed to authenticate. Both the provider note and the docs-site section now give Meta's name and the one this proxy reads. The Contributor tier was described as '~92% cheaper', which is true only of input. The published prices are about 92% off input, 95% off output, and 99% off cached input, and a single tier-wide figure understates the saving while sounding like a precise claim. All three are stated now. Three documentation findings, all accepted: - 002 asserted that the credential muse login stores IS the subscription credential. No login completed, so that was never measured. It is now stated as inferred from Meta's description of CLI onboarding, with the reason the conclusion holds either way: muse login is the Muse Code CLI's own sign-in, so whatever it yields has no stronger claim to third-party use. - The verification gate differed across three plan documents. 030 now holds one canonical gate and the others reference it, including the docs-site frozen-lockfile install and build that docs-site/AGENTS.md requires whenever that directory is touched. Verified: 417 pages built. - A test-count sentence said six where the block has seven. * fix(docs): narrow the reseller claim for Muse Spark CodeRabbit second-pass finding on #3321, and it is right: opencode-go serves only muse-spark-1.3-contributor, not the standard tier. Saying 'the same models are also reachable' would send a user to a preset that does not carry the model they were reading about. Both the provider note and the docs-site section now name which reseller carries which tier: command-code has both, opencode-go has Contributor only. Also corrects 'seven assertions' to 'seven tests' in the plan doc; the block has seven test cases and more than seven expect calls. --------- Co-authored-by: jun <jun@lidge.dev>
…3330) #3222 seeded a display label for every Cursor row, so the Codex picker lost the cursor/ prefix: cursor/kimi-k3 rendered as "Kimi K3", indistinguishable from the same model under another provider. Publish only the labels that carry Cursor's own product name (Cursor Grok 4.5/4.6); every other row keeps the routed cursor/<id> slug like its siblings from other providers. Operator modelDisplayNames still win; enrich remains per-model fill-only. Co-authored-by: jun <jun@lidge.dev>
…#3337) * docs(devlog): plan the meta-muse OAuth provider and its ToS warning wp4 plan plus the research it rests on. Five audit rounds; the reviewer failed it four times. * feat(oauth): import the Muse Code CLI credential behind a ToS warning Adds `meta-muse`, an OAuth provider that reuses the API key the Muse Code CLI already holds, for operators who signed that CLI in and would rather not provision a second key. This ships because the repository owner authorized it for his own account. An earlier phase closed the same idea as a NOOP, and that reasoning stands: proving a credential works is not the same as being allowed to use it, so an agent must not spend a user's ToS risk on its own initiative. A user spending his own deliberately is a different act, and the repository already models it - anthropic and google-antigravity sit in the same HIGH_RISK map. Two measurements shaped the design. The credential file at ~/.config/muse/auth.json holds no secret; it is a pointer to a macOS Keychain item. That item carries both an access_token and an api_key, and only the api_key authenticates: the OAuth access token returns 401 invalid_api_key on /v1/models while the sibling key returns 200. So this is a static-key credential with nothing to refresh, the shape command-code already uses. Import-only, and macOS-only. `muse login` has no non-interactive mode, so a spawned child could outlive cancellation, and polling for the pointer file is satisfied instantly by the one already on disk - which would reimport the OLD account on a force-login. When no credential is present the provider says what to run instead of running it. The warning reaches both surfaces, which took two fixes: - The GUI map alone was not enough. Reauthentication called loginOAuth directly, so a user who had already logged in could refresh a high-risk credential without ever seeing the modal. onReauth now routes through the warning-aware path, carrying accountId so acknowledgement continues the same operation rather than a plain login against the active account. - login-cli.ts never reads the registry note, so `ocx login meta-muse` had no warning at all. loginMetaMuse emits it through ctrl.onProgress before it touches the pointer or the Keychain. The disclosures say what is actually known. Meta scopes this credential to its own CLI and how these calls settle is not observable from the API, so the note says treat every call as billable rather than asserting pay-as-you-go as fact. It also states that the key is copied into OpenCodex's auth store, because it is - runLogin persists it like every other OAuth credential. Also: two price overlays (overlays resolve by exact provider id, so a provider whose warning is 'treat every call as billable' would otherwise report no cost), a privacy-scan detector for the measured LLM|<digits>|<tail> key shape exercised through a new exported scanText seam, and a GUI test that asserts login, add-account and reauth each reach login zero times before acknowledgement and once after. supportsPerAccountQuota stays false, with a test. That predicate gates fetchAccountQuota, whose fallback sends any non-Kiro/non-Antigravity bearer to Anthropic's usage endpoint - flipping it without a dedicated branch would ship a Meta key to Anthropic. Quota is deferred to wp5: Meta does report subscription windows, but only as an SSE event on streaming turns, which needs a passive cache rather than a probe. Plan and five-round audit trail: devlog/_plan/260903_muse_spark_plan_oauth/. * fix(test): scale the CL-07 producer isolation budget under load tests/lab-fabric-task.test.ts failed intermittently on the macOS CI lane and in local full-suite runs, with four or five failures in the same describe block. It read as a flake. It is not one. The tests shorten the fabric producer's isolation budget from the product's 30s/5s to 2000ms/750ms so a hung producer fails in about a second instead of stalling the suite. That budget starts counting when the parent spawns a Bun CHILD process. Spawning one while the rest of the suite saturates the CPU can take longer than 750ms by itself, so the child is killed for inactivity before it runs a line - and the assertion then sees whatever the harness makes of a killed producer: inactivity_timeout where it expected sandbox_violation, or blocked where it expected pass. That is deterministic under contention, not random. Eight parallel runs of the file reproduced five failures each, at a near-identical ~760ms, while a single run passes 49/49. It only looked flaky because it needs a busy machine, which is also why the same four tests passed on one CI run and failed on another. The fix mirrors the watchdogMs helper that already exists for the adjacent problem. watchdogMs bounds how long a TEST may run; isolationBudgetMs scales a PRODUCT budget a test deliberately shortened, with a floor that only applies under load (CI, or a full-suite lane) and leaves a lone local run untouched. The total budget is now a multiple of the inactivity budget rather than a fixed 2000ms. fabricActivityPatchExecutor sleeps 40% of the inactivity budget three times to prove activity resets the deadline, so it needs ~1.2x inactivity to finish; pinning the total while inactivity scaled up starved exactly the test that exercises the scaling. scripts/test.ts marks its spawned lanes with OCX_TEST_FULL_SUITE=1, since a lane running many files in parallel is the same contention as CI. * fix(test): reauth reaches login through the ToS gate, not around it tests/provider-workspace-auth.test.ts pinned the exact call `loginOAuth(provider, true, accountId)` in the onReauth handler. That assertion was correct when it was written: its point is that re-authentication actually reaches login rather than dead-ending. It now conflicts with the fix in the previous commit. Reauth was calling loginOAuth directly, which meant a user who had already logged in could refresh a high-risk credential without ever seeing the Terms-of-Service warning - the map gated the first login and nothing after it. The assertion is updated rather than the code reverted, because the seam it guards is unchanged: requestLoginOAuth forwards the same (provider, addAccount, accountId) triple, and the continuation now carries accountId so acknowledging the warning resumes the same operation instead of a plain login against the active account. Both halves are asserted. Independently, CI confirms the CL-07 isolation-budget fix worked: all 49 tests in that file passed on the macOS lane, including the four that had been failing, with the activity test taking 6.07s under the scaled budget where the old 750ms budget killed its producer mid-spawn. * fix(oauth): redact scanned secrets and bound the Keychain read Both findings from CodeRabbit, and both were right. The privacy scanner printed finding.value to stderr on failure. For a home path or an email that is the context a reviewer needs. For a bearer token or an API key it means the scan that exists to keep a secret out of a readable artifact copies it into CI logs, which are more widely readable than the diff it was blocking. Credential-shaped kinds now report location and kind only. defaultReadKeychain waited on `security find-generic-password` with no deadline. That call can raise an interactive Keychain approval prompt, and on a headless or locked machine nobody answers it - so the login would hang before the 10s validation timeout was even created. It now races a 5s deadline combined with the caller's abort signal, and kills the child in a finally block so a prompt still on screen cannot outlive the race. Two tests: a blocked read fails with a bounded message rather than hanging, and the caller's signal actually reaches the reader. --------- Co-authored-by: jun <jun@lidge.dev>
* docs(devlog): Muse release-train roadmap and audit round 1 Plans the v2.41.0 release train: a risk-classed regression review of the 36 commits on dev ahead of main, a Meta provider mark, and the preview/main publish path. The manual release path exists because the user forbade the local full suite for this unit, and scripts/release.ts runs it in preflight. Audit round 1 established that a hand-rolled path is strictly weaker than the helper unless it also proves the version unused before bumping, uses push-event CI on the exact 40-char SHA, and re-reads the live remote head before dispatch; all three are now in 030/040. 005 records the audit synthesis, including the one finding rebutted: the Terms-of-Service acknowledgement for high-risk OAuth providers is enforced in the browser rather than at /api/oauth/login. That is real, but it predates this work and applies identically to anthropic and google-antigravity, so it is recorded as a follow-up in 050 rather than folded into a release cycle. * feat(gui): give both Meta providers their brand mark meta-model and meta-muse landed today with no icon alias and no display name, so the dashboard drew them as an initial tile with a raw config id underneath. Every other first-class provider carries both. meta.svg is the aria-label="Meta symbol" inline SVG that dev.meta.ai renders in its own navigation header, read through a signed-in session. Meta publishes no square vector at the conventional paths -- favicon.svg, icon.svg and logo.svg all 404 on that host, and the declared icon is a 32x32 .ico -- so the rendered header mark is the first-party vector, taken from the developer console these two providers actually belong to. Path data and gradient stops are verbatim. React's generated gradient ids (_r_d_, _r_e_, _r_f_) are renamed to meta-mark-a/-b/-c because a generated id collides when several marks are inlined into one document, which is the same reason minimax.svg renamed its unnamed-gradient id; the presentational height/width/role/aria-label give way to the viewBox, and xmlns is added so the file stands alone. One brand, two credentials: meta-model is Meta's own pay-as-you-go Model API, meta-muse imports the Muse Code CLI credential. Sharing one asset is the same shape as the three Alibaba plan ids. The mark is not masked -- three linear gradients in Meta brand blue, and masking flattens a gradient to one ink. The generic wiring check only fires when an asset named after the provider id is already committed, and neither id is "meta", so a dropped alias row would restore the fallback tile silently. Both are pinned explicitly instead. * docs(devlog): screenshot of the Meta provider mark The provider rail before and after, on both tile surfaces. The fallback tile and the new mark are the same 19px box, which is the only way to see that a gradient mark actually reads at that size on #303030 and #f4f4f4 both. * docs(devlog): per-commit regression review of the 36-commit release delta Every commit dev carries ahead of main, risk-classed and evidenced. Four commits get R3 treatment: the Muse credential import, the reusable-workflow permission grant, the pull_request_target attribution gate, and the provider editor's field-admission policy. All four are clean on a line-level read. 1387 focused assertions across 38 test files, zero failures, plus typecheck and privacy:scan at the dev head. The full suite was not run, at the maintainer's instruction for this unit. One accepted residual, not a blocker: the ToS acknowledgement for high-risk OAuth providers is enforced in the browser rather than at /api/oauth/login. It predates this delta and applies identically to anthropic and google-antigravity, so publishing 2.41.0 changes nobody's exposure. * docs(devlog): fold the CodeRabbit review into the release plan Six findings, all correct. The ledger now carries one row per SHA. The grouped Cursor and combos rows hid exactly what a per-commit ledger exists to show: 2ab9d94 touches four request paths while its neighbours touch one file each, and that is not visible from a row reading "clean, covered by the batch". R1 and R0 become tables for the same reason. The credential format example moves out of its table cell. Two pipe characters inside a five-column row are delimiters to a Markdown parser, so the verdict column was not where it claimed to be. The version preflights become blocking commands rather than commands that retrieve data. The channel-forward check has no automated equivalent: release.yml compares only for exact-version duplication, so nothing in CI stops a dist-tag being moved backwards. The ancestry proof fetches main first. git ls-remote reads the remote without updating origin/main, so the merge-base could have evaluated stale history. The artifact check covers meta-model, meta-muse and meta.svg rather than one of the three, and the ToS verdict rule is now stated as a rule instead of leaving a later reader to infer why one bypass was accepted. --------- Co-authored-by: jun <jun@lidge.dev>
Promotes the reviewed dev head (81a1fc1) onto preview and moves the channel to 2.41.0-preview.20260903. The delta since v2.40.0 is 37 commits, reviewed per-commit in devlog/_plan/260903_muse_release_train/011_review_ledger.md: four R3 credential/workflow-permission changes read line by line, no blockers, one accepted pre-existing residual recorded in 050_followups.md. Preflight before this bump: 2.41.0-preview.20260903 unpublished on npm, no v2.41.0* tag, no GitHub release, and it moves preview forward from 2.40.0-preview.20260902.
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
Its title has been prefixed with |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Team Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (240)
📝 WalkthroughWalkthroughThis PR bundles several independent efforts: a PR carry-attribution hygiene gate with a Co-authored-by check, a Cursor Private Inference effort-table/effort-row feature, an additive Gemini 3.8 Flash rollout, new Meta Model API/Meta Muse Code providers, a streaming usage-ledger rewrite, combo failover cooldown refinements, an atomic provider-config batch editor, and numerous smaller runtime fixes (transient retry budgets, catalog timeouts, dashboard URLs, write_stdin bridging, max_output_tokens propagation, log-table layout). ChangesPR Carry-Attribution Hygiene Gate
Estimated code review effort: 4 (Complex) | ~75 minutes Cursor Effort Table and Effort Rows
Estimated code review effort: 4 (Complex) | ~90 minutes Gemini 3.8 Flash Rollout
Estimated code review effort: 3 (Moderate) | ~30 minutes Meta Model API and Meta Muse Code Providers
Estimated code review effort: 4 (Complex) | ~60 minutes Usage Ledger Streaming Rewrite
Estimated code review effort: 5 (Critical) | ~120 minutes Combo Failover and Cooldown Refinement
Estimated code review effort: 3 (Moderate) | ~30 minutes Atomic Provider Config Batch Editor
Estimated code review effort: 4 (Complex) | ~60 minutes Independent Runtime Fixes and Test Infrastructure
Estimated code review effort: 5 (Critical) | ~150 minutes ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
리뷰 · 우선순위 74 / 80이 PR은 기여자 기능 PR이 아니다. 지금 싣는 화물은 봇이 제목에 지금 GitHub merge 상태는 프리플라이트는 리뷰 시점에도 그대로다. npm types.ts/config.ts 분할을 이유로 닫을 대상이 아니다. 중복 승격도 아니다. 미리보기 채널이 목적이다. 라벨은 바꾸지 않는다. ToS 고위험 OAuth 동의는 브라우저에만 있는 잔여(
메인테이너의 판단이 필요한 지점
너의 추천 이 댓글은 grok-bot이 작성했습니다 |
Summary
devhead (81a1fc1cc) ontopreviewand moves the channel to2.41.0-preview.20260903.v2.40.0is 37 commits, reviewed per-commit indevlog/_plan/260903_muse_release_train/011_review_ledger.md. Four of them are R3 credential or workflow-permission changes and were read line by line: the Muse Code credential import (feat(oauth): import the Muse Code CLI credential behind a ToS warning #3337), the reusable-workflow permission grant (fix(release): pass the bump job's permissions through the reusable-workflow call #3262), thepull_request_targetattribution gate (docs+ci: record contributor work landed without a co-author trailer, and gate it #3318), and the provider-editor field-admission policy (fix(providers): save the dashboard provider editor atomically #3296). All clean.050_followups.md: the Terms-of-Service acknowledgement for high-risk OAuth providers is enforced client-side rather than at/api/oauth/login. It predates this delta and applies identically toanthropicandgoogle-antigravity, so this publish changes nobody's exposure.Verification
Release preflight run before the bump, each check blocking:
2.41.0-preview.20260903unpublished on npm (404).v2.41.0*tag on the remote.v2.41.0.previewforward: current dist-tags arelatest=2.40.0,preview=2.40.0-preview.20260902.Evidence for the delta itself, all at the dev head: 1387 focused assertions across 38 test files with zero failures,
bun run typecheckexit 0,bun run privacy:scanpassed. PR #3338 carried the same tree through full CI green on all 24 checks.The local full suite was not run at the maintainer's explicit instruction for this unit, which is also why
scripts/release.tsis not the vehicle here — its preflight runs that suite. The documented manual path in030_wp3_preview_release.mdis used instead, and exact-SHA push-event CI onpreviewis the gate before dispatch.Checklist
Screenshot
This promotion carries the Meta provider mark from #3338. The provider rail before and after, on both tile surfaces:
Summary by CodeRabbit
New Features
Bug Fixes