diff --git a/Cargo.lock b/Cargo.lock index 2a5255685..3bfd97b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 9da942761..770afaa8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/RELEASE_NOTES/v0.8.4.md b/RELEASE_NOTES/v0.8.4.md new file mode 100644 index 000000000..900b3ddea --- /dev/null +++ b/RELEASE_NOTES/v0.8.4.md @@ -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:///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 ."} +``` + +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:///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. diff --git a/packages/bun.lock b/packages/bun.lock index e1c7b00a5..c9405f77a 100644 --- a/packages/bun.lock +++ b/packages/bun.lock @@ -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:*", }, diff --git a/packages/op-chrome-extension/manifest.json b/packages/op-chrome-extension/manifest.json index 3eb0ecc35..c9e975afc 100644 --- a/packages/op-chrome-extension/manifest.json +++ b/packages/op-chrome-extension/manifest.json @@ -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": { diff --git a/packages/op-web-sdk-react/package.json b/packages/op-web-sdk-react/package.json index d81a82377..af578e185 100644 --- a/packages/op-web-sdk-react/package.json +++ b/packages/op-web-sdk-react/package.json @@ -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", diff --git a/packages/op-web-sdk-react/src/index.ts b/packages/op-web-sdk-react/src/index.ts index a705e1ebf..393922eaf 100644 --- a/packages/op-web-sdk-react/src/index.ts +++ b/packages/op-web-sdk-react/src/index.ts @@ -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'; diff --git a/packages/op-web-sdk-vue/package.json b/packages/op-web-sdk-vue/package.json index a091b5a2f..dd1f5a9dc 100644 --- a/packages/op-web-sdk-vue/package.json +++ b/packages/op-web-sdk-vue/package.json @@ -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", diff --git a/packages/op-web-sdk-vue/src/index.ts b/packages/op-web-sdk-vue/src/index.ts index 54611cc4b..f0d7a261f 100644 --- a/packages/op-web-sdk-vue/src/index.ts +++ b/packages/op-web-sdk-vue/src/index.ts @@ -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'; diff --git a/packages/op-web-sdk/package.json b/packages/op-web-sdk/package.json index 8fd17a51b..125ac5d96 100644 --- a/packages/op-web-sdk/package.json +++ b/packages/op-web-sdk/package.json @@ -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", diff --git a/packages/op-web-sdk/src/index.ts b/packages/op-web-sdk/src/index.ts index 653b7d5e5..99203c67d 100644 --- a/packages/op-web-sdk/src/index.ts +++ b/packages/op-web-sdk/src/index.ts @@ -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'; diff --git a/packages/package.json b/packages/package.json index 548f64020..aa14b38c0 100644 --- a/packages/package.json +++ b/packages/package.json @@ -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": {