chore(release): bump to 0.8.4

v0.8.3's published binaries could never start the public relay; this
release carries that fix, the build guard that keeps a malformed injected
hub from shipping again, the control-plane failure classification, and the
update-check fallback.

The prebuilt op-auth libraries are version-pinned through their signed
provenance, so a 0.8.4 build requires 0.8.4 artifacts; the 0.8.3 archives
are refused rather than silently linked.
This commit is contained in:
Kayshen-X 2026-08-11 01:01:58 +08:00
parent 59be2b27f3
commit 4fb4cc0071
12 changed files with 209 additions and 53 deletions

82
Cargo.lock generated
View file

@ -3636,7 +3636,7 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "op-acp"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"futures-util",
"op-ai",
@ -3651,11 +3651,11 @@ dependencies = [
[[package]]
name = "op-ai"
version = "0.8.3"
version = "0.8.4"
[[package]]
name = "op-ai-skills"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"include_dir",
"jian-ops-schema",
@ -3665,7 +3665,7 @@ dependencies = [
[[package]]
name = "op-auth-bridge"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"ed25519-dalek",
@ -3678,7 +3678,7 @@ dependencies = [
[[package]]
name = "op-chrome-extension-core"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-ops-schema",
"op-html",
@ -3689,7 +3689,7 @@ dependencies = [
[[package]]
name = "op-cli"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"jian-ops-schema",
@ -3707,7 +3707,7 @@ dependencies = [
[[package]]
name = "op-codegen"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"jian-ops-schema",
@ -3721,7 +3721,7 @@ dependencies = [
[[package]]
name = "op-collab"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"blake3",
"jian-ops-schema",
@ -3735,7 +3735,7 @@ dependencies = [
[[package]]
name = "op-collab-host"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"ed25519-dalek",
@ -3766,7 +3766,7 @@ dependencies = [
[[package]]
name = "op-collab-policy-file"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"blake3",
@ -3782,7 +3782,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-client"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"futures-util",
"op-collab-relay-protocol",
@ -3795,7 +3795,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-control-plane"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"ed25519-dalek",
"getrandom 0.3.4",
@ -3810,7 +3810,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-locator-hsm"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"cryptoki",
@ -3828,7 +3828,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-locator-server"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"axum",
"base64",
@ -3857,7 +3857,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-protocol"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"blake3",
@ -3871,7 +3871,7 @@ dependencies = [
[[package]]
name = "op-collab-relay-server"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"blake3",
@ -3897,7 +3897,7 @@ dependencies = [
[[package]]
name = "op-collab-smoke"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"anyhow",
"base64",
@ -3910,7 +3910,7 @@ dependencies = [
[[package]]
name = "op-collab-transport"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"getrandom 0.3.4",
@ -3933,7 +3933,7 @@ dependencies = [
[[package]]
name = "op-config-store"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"serde",
"serde_json",
@ -3941,7 +3941,7 @@ dependencies = [
[[package]]
name = "op-design-lint"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-ops-schema",
"op-util",
@ -3951,7 +3951,7 @@ dependencies = [
[[package]]
name = "op-editor-core"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"glam",
"jian-core",
@ -3967,7 +3967,7 @@ dependencies = [
[[package]]
name = "op-editor-host-core"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-ops-schema",
"op-ai",
@ -3984,7 +3984,7 @@ dependencies = [
[[package]]
name = "op-editor-ui"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"accesskit",
"base64",
@ -4005,7 +4005,7 @@ dependencies = [
[[package]]
name = "op-figma"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"flate2",
@ -4020,7 +4020,7 @@ dependencies = [
[[package]]
name = "op-git"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"git2",
"op-config-store",
@ -4031,7 +4031,7 @@ dependencies = [
[[package]]
name = "op-host-desktop"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"accesskit",
"accesskit_macos",
@ -4104,7 +4104,7 @@ dependencies = [
[[package]]
name = "op-host-native"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"accesskit",
"casement",
@ -4137,7 +4137,7 @@ dependencies = [
[[package]]
name = "op-host-services"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"agent",
"anthropic-agent-sdk",
@ -4182,7 +4182,7 @@ dependencies = [
[[package]]
name = "op-host-web"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"accesskit",
"base64",
@ -4210,7 +4210,7 @@ dependencies = [
[[package]]
name = "op-host-web-server"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"op-host-services",
"serde_json",
@ -4218,7 +4218,7 @@ dependencies = [
[[package]]
name = "op-html"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"base64",
"encoding_rs",
@ -4233,14 +4233,14 @@ dependencies = [
[[package]]
name = "op-i18n"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"op-html",
]
[[package]]
name = "op-mcp"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-ops-schema",
"jian-scene",
@ -4260,7 +4260,7 @@ dependencies = [
[[package]]
name = "op-opmerge"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-ops-schema",
"serde_json",
@ -4269,7 +4269,7 @@ dependencies = [
[[package]]
name = "op-orchestrator"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"futures",
"jian-ops-schema",
@ -4289,7 +4289,7 @@ dependencies = [
[[package]]
name = "op-pen-loader"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"jian-core",
"jian-ops-schema",
@ -4305,14 +4305,14 @@ dependencies = [
[[package]]
name = "op-process-io"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"tokio",
]
[[package]]
name = "op-rpc-transport"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"serde",
"serde_json",
@ -4320,7 +4320,7 @@ dependencies = [
[[package]]
name = "op-smoke"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"agent",
"futures",
@ -4340,11 +4340,11 @@ dependencies = [
[[package]]
name = "op-util"
version = "0.8.3"
version = "0.8.4"
[[package]]
name = "op-web-sdk"
version = "0.8.3"
version = "0.8.4"
dependencies = [
"console_error_panic_hook",
"jian-ops-schema",

View file

@ -24,7 +24,7 @@ exclude = [
default-members = ["crates/op-host-desktop", "crates/op-cli", "crates/op-host-web-server"]
[workspace.package]
version = "0.8.3"
version = "0.8.4"
edition = "2021"
# 1.87 floor: op-mcp's `script` feature (enabled unconditionally by
# op-host-services + op-orchestrator) depends on rquickjs 0.12, whose

156
RELEASE_NOTES/v0.8.4.md Normal file
View file

@ -0,0 +1,156 @@
# OpenPencil v0.8.4 — The Public Relay Actually Works
`v0.8.4` is a corrective release. `v0.8.3` shipped collaboration end to end, but
in the published builds the **public relay could never start**: every attempt —
China region or Global, owner or guest — ended with "the public relay is
temporarily unavailable." The service was healthy the whole time. The binaries
were not.
The cause was a single space character. This release fixes it, and then fixes
the three separate reasons that one character was able to reach users and stay
invisible for so long: a build that could not detect it, an error that lied
about it, and a diagnostic path that erased it.
A second, unrelated failure goes with it — the in-app update check reporting
"cannot reach the release server, check your network connection" on networks
that were working fine.
## Highlights
- **Public relay works in published builds.** The hub endpoints baked into the
release binaries were malformed; corrected at the source.
- **A malformed hub endpoint now fails the build.** It can no longer ship.
- **Relay errors tell the truth.** A refused sign-in, a rate-limited hub, an
unreachable network and a broken build are four different messages now, not
one.
- **Update checks survive shared IP addresses.** The probe falls back to the
releases Atom feed when GitHub's anonymous API quota is exhausted.
## The Relay Fix
### What happened
The production hub URLs are deliberately absent from the open-source tree. The
release pipeline injects them at compile time from repository secrets, and
`relay_bootstrap_select.rs` reads them with `option_env!`.
Both secrets had been saved with a **leading space**:
```
" https://op.zseven.cn/api/v1/collaboration/bootstrap"
" https://op.zseven.tech/api/v1/collaboration/bootstrap"
```
`option_env!` bakes the value in exactly as stored, space and all. The runtime
endpoint policy is deliberately strict — it rejects any value where
`value.trim() != value` — so the injected endpoint parsed as *no usable hub*,
and every relay attempt failed before a single packet left the machine.
Because both secrets carried the space, switching regions changed nothing. And
because the bad character is invisible in every log, every dashboard, and every
`strings` dump of the binary, nothing pointed at it.
### Why it shipped
A guard for exactly this already existed —
`injected_bootstrap_urls_pass_the_endpoint_policy_or_stay_absent` — and it
passed, vacuously. Repository secrets are not exposed to the CI **test** job, so
the assertion took its "no hubs injected, nothing to validate" branch on every
run. The **release** job is the one that receives the secrets, and it ran no
such assertion.
The fix moves the check where neither job can miss it: a build script on
`op-collab-host` validates the injected endpoints at compile time and fails the
build with an actionable message.
```
error: failed to run custom build command for `op-collab-host`
OPENPENCIL_BUILD_COLLAB_BOOTSTRAP_URL_CN is not a usable collaboration hub URL
(the value has leading or trailing whitespace). Expected exactly
`https://<host>/api/v1/collaboration/bootstrap` with no surrounding
whitespace; check the repository secret for a stray space or trailing newline.
```
An absent variable stays valid — open-source and fork builds legitimately carry
no production hub, and continue to build.
## Honest Failure Reporting
The single space was hard to find because the code actively hid it. Three
changes fix that, and they outlive this particular bug.
- **A broken build no longer poses as an outage.** An injected endpoint that
fails the policy now reports as *not configured* rather than *temporarily
unavailable*. The old copy invited the user to wait for a condition that could
never resolve on its own.
- **Control-plane failures are classified.** Every call to the relay locator —
route publication, pairing-code publication, pairing-code claim — used to end
in `map_err(|_| RelayUnavailable)`. An expired collaboration ticket, a
rate-limited hub, an unreachable network and a malformed response all reached
the user as the same sentence. A refused ticket (HTTP 401/403) is now a
sign-in problem, a rate-limited hub (HTTP 429) is now a retry-later problem,
and only genuine transport and protocol faults remain "relay unavailable".
This also gives `collab.error.rateLimited`, translated in all 15 locales since
`v0.8.3`, its first real producer.
- **Stage diagnostics, without credentials.** Control-plane failures write one
line naming the stage and the failure class. Only the failure enum — which
carries no payload — and a `&'static str` variant tag are ever formatted; the
error's own `Display` is deliberately never used, so a future payload field
cannot leak into a support log. A test enforces that every tag stays a bare
variant name.
## Update Check
The in-app update check reported "cannot reach the release server, check your
network connection" for users whose network was fine. GitHub's anonymous API
allows 60 requests per hour **per source IP**, so anyone behind a shared egress
— corporate NAT, a VPN, most China-region proxies — can find the quota already
spent by strangers:
```
403 {"message":"API rate limit exceeded for <shared-egress-ip>."}
```
The probe now falls back to the releases **Atom feed**, which is served by
`github.com` rather than `api.github.com` and is not on that quota. The JSON API
stays the first choice for its richer response; the feed only has to cover the
case where the API refuses to answer at all. Drafts never appear in the feed,
which is the same rule the API path already applied.
## Fixed
- Public relay unusable in all published `v0.8.3` builds, in both service
regions, for owners and guests alike.
- Update check failing with a network error on shared egress IP addresses.
- A misconfigured relay build reporting as a temporary service outage.
- Expired collaboration tickets, rate-limited hubs and network faults sharing
one indistinguishable relay error.
## Upgrade Notes and Compatibility
- **Upgrade to restore the public relay.** No server-side change is needed and
no action is required from you beyond installing this release — the `v0.8.3`
hubs, regions and pairing codes were correct throughout. Local-network
collaboration was never affected.
- **Pairing codes and sessions are unchanged.** The 10-character region-tagged
pairing code, the wire protocol, and the bootstrap document format are
identical to `v0.8.3`; a `v0.8.4` peer and a `v0.8.3` peer interoperate on any
path a `v0.8.3` peer could already reach.
- **Sign-in prompts may replace relay errors.** With ticket rejection now
classified, a session whose sign-in has lapsed reports that directly instead
of blaming the relay. This surfaces a pre-existing condition; it is not a new
restriction.
- **Building from source with production hubs.** If you inject
`OPENPENCIL_BUILD_COLLAB_BOOTSTRAP_URL_CN` / `_GLOBAL`, they must now be exact
`https://<host>/api/v1/collaboration/bootstrap`, lowercase host, no
surrounding whitespace — or the build fails. Builds that inject neither are
unaffected.
- **Prebuilt `op-auth` libraries are version-pinned.** Their signed provenance
records the product version, so the `v0.8.4` artifacts are required for a
`v0.8.4` build; a `v0.8.3` artifact is refused rather than silently linked.
## Acknowledgements
Thanks to everyone who reported the relay failure and stayed with the
investigation through the server, the gateway, the trust chain and the build
pipeline before it came down to one byte in a repository secret.

View file

@ -28,7 +28,7 @@
},
"op-web-sdk": {
"name": "@zseven-w/op-web-sdk",
"version": "0.8.3",
"version": "0.8.4",
"devDependencies": {
"jsdom": "^25.0.0",
"tsup": "^8.0.0",
@ -38,7 +38,7 @@
},
"op-web-sdk-react": {
"name": "@zseven-w/op-web-sdk-react",
"version": "0.8.3",
"version": "0.8.4",
"dependencies": {
"@zseven-w/op-web-sdk": "workspace:*",
},
@ -60,7 +60,7 @@
},
"op-web-sdk-vue": {
"name": "@zseven-w/op-web-sdk-vue",
"version": "0.8.3",
"version": "0.8.4",
"dependencies": {
"@zseven-w/op-web-sdk": "workspace:*",
},

View file

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"description": "__MSG_extDescription__",
"version": "0.8.3",
"version": "0.8.4",
"default_locale": "en",
"minimum_chrome_version": "103",
"action": {

View file

@ -1,6 +1,6 @@
{
"name": "@zseven-w/op-web-sdk-react",
"version": "0.8.3",
"version": "0.8.4",
"description": "React adapter for the OpenPencil read-only web viewer SDK",
"license": "MIT",
"type": "module",

View file

@ -1,4 +1,4 @@
export const VERSION = '0.8.3';
export const VERSION = '0.8.4';
export { DesignProvider, useViewer } from './use-viewer.js';
export { useDocument, useViewport, useActivePage } from './hooks.js';
export { DesignView, type DesignViewProps } from './design-view.js';

View file

@ -1,6 +1,6 @@
{
"name": "@zseven-w/op-web-sdk-vue",
"version": "0.8.3",
"version": "0.8.4",
"description": "Vue 3 adapter for the OpenPencil read-only web viewer SDK",
"license": "MIT",
"type": "module",

View file

@ -1,4 +1,4 @@
export const VERSION = '0.8.3';
export const VERSION = '0.8.4';
export { viewerKey } from './injection.js';
export { provideViewer, provideViewerRef, useViewer } from './use-viewer.js';
export { useDocument, useViewport, useActivePage } from './composables.js';

View file

@ -1,6 +1,6 @@
{
"name": "@zseven-w/op-web-sdk",
"version": "0.8.3",
"version": "0.8.4",
"description": "Read-only OpenPencil .op viewer SDK for the web (wasm-backed)",
"license": "MIT",
"type": "module",

View file

@ -1,5 +1,5 @@
// Public entry for the OpenPencil read-only web SDK core.
export const VERSION = '0.8.3';
export const VERSION = '0.8.4';
export { createViewer, OpViewer } from './viewer.js';
export type { Viewport, CreateViewerOptions, PenDocument, PenPage } from './types.js';
export type { ViewerEvent } from './events.js';

View file

@ -1,6 +1,6 @@
{
"name": "@zseven-w/openpencil-packages",
"version": "0.8.3",
"version": "0.8.4",
"private": true,
"description": "Web SDK workspace for OpenPencil (the Rust product lives in ../crates). Hosts the wasm-backed op-web-sdk viewer family plus the JS build glue that feeds the Rust web target.",
"author": {