Skip to content

chore(license): set real SUBSCRIPTION_PUBLIC_KEY#284

Merged
github-actions[bot] merged 1 commit into
refactor/split-markdown-editorfrom
chore/set-subscription-public-key
Jun 9, 2026
Merged

chore(license): set real SUBSCRIPTION_PUBLIC_KEY#284
github-actions[bot] merged 1 commit into
refactor/split-markdown-editorfrom
chore/set-subscription-public-key

Conversation

@tomymaritano

Copy link
Copy Markdown
Collaborator

Summary

Replaces the all-zeros placeholder from #281 with a real Ed25519 public key. The matching private key was generated in this session and is held outside the repo — see notes below.

Public key (this PR)

```
d049019b2ff05ccfd3802e0619d5897e21431a6f946af724c13ed7ecca7ec01f
```

Public by design — the client needs it to verify. Safe to commit.

Private key (NOT in this PR)

Lives only on the licensing server. Tomy received it in the dev session that produced this PR and is responsible for storing it as the server's `LICENSE_SIGNING_PRIVATE_KEY` env var (Vercel env / Doppler / 1Password / wherever your secrets live).

This keypair was generated in a dev chat session and is considered "dev/staging-grade". For production, rotate before the first signed envelope ships: generate a new pair on a trusted machine, push a new desktop release with the new public key here, then switch the server. The rotation procedure is documented in the constant's comment.

Behavior change

State Before (placeholder) After (real key)
Cache without envelope Warning logged, accepted (lenient) Warning logged, accepted (lenient)
Cache with envelope signed by matching key Cannot occur Silently accepted
Cache with envelope signed by wrong key Refused → refetch Refused → refetch
Cache with tampered envelope Refused → refetch Refused → refetch

The lenient branch (no envelope) is unchanged — existing users are unaffected. The strict branch (envelope present) now actually works: real envelopes verify, fakes get rejected.

Test plan

  • `pnpm --filter @readied/desktop typecheck:main` — green
  • After merge: server team uses `signSubscriptionPayload(payload, LICENSE_SIGNING_PRIVATE_KEY)` from `@readied/licensing`. Round-trip test:
    ```bash
    cd packages/licensing
    node -e "
    import('./src/validator.js').then(async ({ signSubscriptionPayload, verifySubscriptionSignature }) => {
    const PRIV = process.env.LICENSE_SIGNING_PRIVATE_KEY;
    const env = await signSubscriptionPayload({
    payloadVersion: 1,
    subscription: { subscriptionId:'sub_test', customerId:'cus_test', email:'x@x.com', plan:'monthly', status:'active', currentPeriodStart: new Date().toISOString(), currentPeriodEnd: new Date(Date.now()+30*86400e3).toISOString(), cancelAtPeriodEnd:false },
    issuedAt: new Date().toISOString(),
    }, PRIV);
    const r = await verifySubscriptionSignature(env, { publicKey: 'd049019b2ff05ccfd3802e0619d5897e21431a6f946af724c13ed7ecca7ec01f' });
    console.log(r.valid ? '✅' : '❌ ' + r.error);
    });
    "
    ```

Stack context

PR-Set-Key at the tip of the stack. Sits on top of editor split (#283) → note repo split (#282) → ... down to PR-B (#265). 20 PRs deep.

🤖 Generated with Claude Code

Replaces the all-zeros placeholder from #281 with the actual Ed25519
public key. The matching private key lives only on the licensing
server — never the repo.

Behavior change:
- Before: any real signed envelope failed verification (placeholder
  doesn't match any private key), so the lenient "no envelope" branch
  was the only working path.
- After: envelopes signed by the matching private key verify and pass
  through; forged or tampered envelopes are refused and the cache is
  re-fetched on next read.

The lenient branch (no envelope on disk → warning log + accept) is
unchanged. Existing users without a cached envelope are unaffected.

Rotation procedure now lives in the constant's doc comment — generate
keypair on a trusted machine, ship desktop release with new public
key, then flip the server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
readide Error Error Jun 9, 2026 1:49am

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7c5d834d-d91f-47d6-89b4-51db5b1662b2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/set-subscription-public-key

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot merged commit d773749 into refactor/split-markdown-editor Jun 9, 2026
5 of 6 checks passed
@github-actions github-actions Bot added the size/S label Jun 9, 2026
tomymaritano added a commit that referenced this pull request Jun 9, 2026
Adds the server-side counterpart of the public key set in the previous
commit. The api worker needs LICENSE_SIGNING_PRIVATE_KEY in its
Cloudflare secrets to sign SignedSubscriptionEnvelope payloads via
@readied/licensing#signSubscriptionPayload.

What this commit adds:
- packages/api/.dev.vars: placeholder line + generation snippet so
  contributors know what the env var is, where the matching public
  key lives, and how to mint a fresh pair. The .dev.vars file is
  tracked (it's a template); real values go in .dev.vars.local
  (gitignored).
- packages/api/wrangler.toml: adds LICENSE_SIGNING_PRIVATE_KEY to the
  "Required secrets" comment block alongside the existing TURSO/JWT/
  RESEND/STRIPE entries.

What this commit does NOT do:
- Actually set the secret in Cloudflare. That requires `wrangler login`
  in an authenticated session, which has to happen on a maintainer's
  machine. Run:
    cd packages/api
    echo "<hex>" | npx wrangler secret put LICENSE_SIGNING_PRIVATE_KEY --env production
    echo "<hex>" | npx wrangler secret put LICENSE_SIGNING_PRIVATE_KEY --env staging
  See PR #284 description for the matching public key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Jun 9, 2026
## Why this PR

The audit stack (#266 through #284) was structured as 19 stacked PRs,
each one's base pointing at its predecessor in the chain. Each merged
into **its own parent branch**, not into \`develop\`. Net result:
\`develop\` only contains #265, and the other 19 PRs are stranded on
their branch tips.

This PR ships the head of the stack
(\`chore/set-subscription-public-key\`) into \`develop\` so the work
actually lands. It's a single PR with **21 commits** — the linear chain
of the stack — preserving each individual PR's conventional commit
message so semantic-release can categorise them for the next release.

## What's in this PR (in merge order)

| # | Commit | Title |
|---|---|---|
| 1 | \`3412e4e\` | fix(typecheck): unblock pnpm -r typecheck after TS
6.x bump (already merged as squash in develop — no-op overlap) |
| 2 | \`03da9cb\` | fix(editor): stop runtime crashes in MarkdownEditor
|
| 3 | \`07a76d6\` | chore(tooling): replace husky with lefthook, add
knip, tighten lint-staged |
| 4 | \`e27f1ba\` | refactor(stores): use named selectors instead of
destructuring full state |
| 5 | \`c519fc3\` | chore(test): add coverage baseline + smoke tests for
@readied/commands |
| 6 | \`5b1cc55\` | chore(mcp-server): migrate to registerTool API +
FTS5 for read_note |
| 7 | \`0eb49a5\` | fix(backup): integrity-check restored db and roll
back on failure |
| 8 | \`402e280\` | refactor(ipc): add typed IPC registry, migrate
aiKeyHandlers as proof |
| 9 | \`dd4823d\` | refactor(ipc): migrate light handlers to
defineIpcHandler |
| 10 | \`5607100\` | refactor(ipc): migrate heavy data handlers to
defineIpcHandler |
| 11 | \`c51c4d3\` | fix(aiKeyStorage): stop deleting encrypted keys on
transient decrypt errors |
| 12 | \`d5f33ef\` | feat(licensing): add Ed25519 subscription envelope
sign + verify |
| 13 | \`5ba96d4\` | feat(e2e): scaffold Playwright Electron suite + CI
job |
| 14 | \`2bd1396\` | refactor(main): extract FileLicenseStorage and
window state to services |
| 15 | \`fd9b809\` | chore(knip): delete verified-unused files (phase 1)
|
| 16 | \`a3d7c1b\` | chore(knip): remove unused dependencies (phase 2) |
| 17 | \`0ec48b3\` | feat(license): wire Ed25519 signed-envelope
verification at the storage layer |
| 18 | \`cca7e04\` | refactor(storage-sqlite): extract noteMapping
helpers from SQLiteNoteRepository |
| 19 | \`1a0df95\` | refactor(editor): extract theme + highlight from
MarkdownEditor |
| 20 | \`9034c71\` | chore(license): set real SUBSCRIPTION_PUBLIC_KEY |
| 21 | \`390503c\` | docs(api): document LICENSE_SIGNING_PRIVATE_KEY
secret requirement |

Each is already individually reviewed and merged on GitHub (#266#284).
They appear here as their original commits because the stack used
rebase-based stacking, not merge commits.

## Pre-merge verification (local, this branch)

- ✅ \`pnpm -r typecheck\` — green across 18 workspace projects
- ✅ \`pnpm test\` — 17/17 packages
- ✅ \`pnpm build\` — 6/6 packages

## After this PR merges

Per the release flow in \`CLAUDE.md\`:

1. Open \`develop → main\` PR
2. Click \"Run workflow\" on Release action — \`semantic-release\`
analyses these conventional commits and bumps the version
3. Tag push triggers Build workflow — mac/win/linux in parallel
4. All builds green → release un-drafts → electron-updater serves the
update

## Notable behaviour changes for users

- **Editor**: no more blank-window crash on notes with tables (#266) —
root cause was \`Decoration.replace\` over multi-line ranges from a
ViewPlugin instead of a StateField
- **AI keys**: no more silent deletion of keys when keychain is
temporarily locked after sleep/wake (#275)
- **Backups**: corrupt restored DB is now refused and rolled back to a
safety copy (#271)
- **Subscriptions**: client now verifies Ed25519 server signatures
(#281, #284); server-side signing rollout still needed for full effect
- **Tooling**: lefthook replaces husky, knip available for dead-code
audits (#267)
- **Infrastructure**: full IPC surface now validated with Zod at the
boundary (#272 + #273 + #274), Playwright scaffold in place (#277)

## Notable for reviewers

- The placeholder \`SUBSCRIPTION_PUBLIC_KEY\` in #281 was replaced with
a real key in #284. The matching private key is set in Cloudflare as
\`LICENSE_SIGNING_PRIVATE_KEY\` (prod + staging). This keypair was
generated in a Claude session and is dev/staging-grade — rotate before
serving real paid customers.
- 50% of audit findings were Knip false positives or already-fixed
(documented per PR). Stack reflects real debt, not the audit verbatim.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added end-to-end testing for the desktop application with
comprehensive smoke and notes testing.
* Implemented IPC handler validation using Zod schemas for improved type
safety.
* Added subscription envelope signing and verification using Ed25519
cryptography.

* **Bug Fixes**
  * Improved encryption error handling and recovery logic.

* **Tests**
* Established centralized test coverage configuration across all
packages.
* Expanded test suites for markdown commands and licensing
functionality.

* **Chores**
  * Transitioned from Husky to Lefthook for Git hooks management.
  * Refactored internal IPC architecture and service modules.
  * Updated build and deployment configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Jun 9, 2026
## Summary

Promotes `develop` to `main` for **v0.15.0**. Originally opened
2026-04-24; now refreshed with the 19-PR tech-debt audit shipped via
#285 plus the accumulated dependabot bumps reconciled.

`semantic-release` will pick the version bump. Expected: **minor
(v0.14.x → v0.15.0)** because of multiple \`feat:\` commits.

## What ships (audit highlights, from #285)

### Runtime fixes (user-facing)
- **Editor no longer crashes on table-containing notes** (#266) —
\`Decoration.replace\` over multi-line ranges moved from a ViewPlugin to
a StateField, plus an EditorView.exceptionSink so any future plugin
error no longer tears down the EditorView.
- **AI keys survive sleep/wake** (#275) — \`aiKeyStorage\` stopped
silently deleting the encrypted store when the keychain was temporarily
locked after macOS sleep.
- **Backup restore is now safe** (#271) — restored DBs go through
\`PRAGMA integrity_check\` before being swapped in; corrupt backups roll
back to the safety copy.
- **MCP server runs without electron-builder rebuilds** (#264#270) —
migrated from native \`better-sqlite3\` to built-in \`node:sqlite\`
(Node 22.5+), updated to the new \`registerTool\` MCP SDK API.

### Security
- **Typed IPC boundary** (#272 + #273 + #274) — 130+ IPC channels now
validated with Zod tuples at the main↔renderer boundary. Garbage in
fails fast with \`IpcValidationError\` instead of corrupting downstream
code.
- **Ed25519 license verification scaffolding** (#276 + #281 + #284) —
\`signSubscriptionPayload\` / \`verifySubscriptionSignature\` helpers
ship in \`@readied/licensing\`, wired into
\`FileLicenseStorage.readSubscriptionData\` with lenient fallthrough
during migration. Real public key embedded (\`d04901…\`). Server-side
\`LICENSE_SIGNING_PRIVATE_KEY\` already set in Cloudflare staging +
production.

### Developer experience
- **Husky → Lefthook** (#267) plus lint-staged that now runs ESLint, not
just Prettier.
- **\`knip\` added** (#267) + 12 unused files deleted (#279) + 6 unused
deps dropped (#280).
- **Playwright Electron E2E scaffold** (#277) with smoke + notes-IPC
specs and a Linux+xvfb CI job (\`continue-on-error: true\` while it
stabilises).
- **Vitest coverage baseline** (#269) — 12 packages share a coverage
config; smoke tests added for \`@readied/commands\`.

### Refactor (no behavior change)
- **Zustand selectors migration** (#268) — 3 components stopped
destructuring entire stores.
- **God-file extractions**:
- \`main/index.ts\` 1065 → 950 lines (#278) — \`FileLicenseStorage\`,
\`windowState\` extracted to services
- \`SQLiteNoteRepository.ts\` 1121 → 1038 lines (#282) — pure helpers
extracted to \`noteMapping.ts\`
- \`MarkdownEditor.tsx\` 737 → 612 lines (#283) — theme +
markdownHighlighting extracted to \`editorTheme.ts\`

## Deploys triggered

| Workflow | Trigger | What happens |
|---|---|---|
| \`deploy-api.yml\` | Auto on \`push\` to main affecting
\`packages/api/**\` | Tests + deploys \`@readied/api\` to Cloudflare
Workers (\`readied-api-production\`). This stack only touched
\`wrangler.toml\` + \`.dev.vars\` docs, no production code change. |
| \`release.yml\` | Manual \`workflow_dispatch\` post-merge |
\`semantic-release\` analyses conventional commits, bumps version,
creates GitHub Release draft + tag |
| \`build.yml\` | Auto on tag push from release.yml | mac / windows /
linux parallel builds, artefacts attached to the GitHub Release |

## Pre-merge verification (local, this branch)

- ✅ \`pnpm -r typecheck\` — green across 18 workspace projects
- ✅ \`pnpm test\` — 17/17 packages
- ✅ Merge resolved: take develop versions for 19 conflicted
package.jsons (develop has equal or newer deps than main's dependabot
bumps)

## Post-merge action items (operator)

1. **Deploy API to staging first** (smoke test):
   \`\`\`
   gh workflow run deploy-api.yml -f environment=staging
   \`\`\`
2. Confirm staging API responds correctly (subscription endpoint with
new \`LICENSE_SIGNING_PRIVATE_KEY\` secret already set in CF).
3. Merge this PR → auto-deploys API to production.
4. Trigger Release workflow: GitHub → Actions → Release → "Run workflow"
→ main.
5. Watch Build workflow for mac/win/linux completion.
6. Confirm the release un-drafts itself.

## Known risks / follow-ups

- **Pre-existing Vercel preview failure for \`apps/web\`** — marketing
site, scheduled to be extracted to its own repo (P3 in the roadmap).
- **\`SUBSCRIPTION_PUBLIC_KEY\` is dev-grade** — generated in a Claude
session. Before the licensing server emits envelopes for real paid
users, rotate the keypair from a trusted machine and ship a follow-up
release.
- **Branch protection should require CodeRabbit completion before
automerge** — added to the roadmap as a process item; this very PR was
BLOCKED correctly because of that policy gap being closed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
tomymaritano added a commit that referenced this pull request Jun 9, 2026
## Why this PR

PR #245 squash-merged 19 individual PRs (#266-#284) into a single commit
on main: \`release: audit + Ed25519 signed envelopes + lefthook
(v0.15.0) (#245)\`. That message **isn't a conventional commit type that
semantic-release recognises**, so the analyser saw 11 commits since the
last tag and concluded *"no release"*. The actual feat:/fix:/refactor:
messages from the 19 underlying PRs were lost in the squash.

Concretely, run
[27184456847](https://github.com/tomymaritano/readide/actions/runs/27184456847)
finished cleanly but logged:

\`\`\`
[semantic-release] [@semantic-release/commit-analyzer] › ℹ Analysis of
11 commits complete: no release
[semantic-release] › ℹ There are no relevant changes, so no new version
is released.
\`\`\`

## What this PR does

### 1. Provides the release signal

This commit's title is \`feat(release): cut v0.15.0 audit release\` — a
recognised conventional type. semantic-release will analyse it as a
**minor bump** (v0.14.x → v0.15.0).

### 2. Drops the broken \`@semantic-release/exec\` step

\`release.config.js\` referenced \`node scripts/bump-version.js
\${nextRelease.version}\` in a prepareCmd, but that script was deleted
in the knip cleanup (#279) and the config wasn't updated. Any release
triggered now would fail at the prepare step with \`ENOENT\`.

The remaining \`@semantic-release/git\` plugin already commits root
\`package.json\` + \`apps/desktop/package.json\` + \`CHANGELOG.md\` via
its \`assets\` list — that's everything the desktop release needs
bumped. Workspace packages stay at \`workspace:*\` and their numeric
versions aren't user-visible.

## Side effect

\`@semantic-release/exec\` is still listed in \`package.json\` devDeps
but unused after this PR. Not removing it here to keep this PR surgical;
can be dropped in the next knip pass.

## After this merges

1. Manually re-trigger Release workflow on main
2. semantic-release picks up this commit + the existing release notes
generator → cuts **v0.15.0**
3. Creates GitHub Release draft + tag
4. Tag push fires Build workflow → mac/win/linux artefacts
5. Builds complete → release un-drafts → electron-updater serves it

## Related

- #287 (deploy-api workflow fix)
- #288 (release workflow install fix)
- #279 (knip cleanup that deleted bump-version.js without updating
release.config.js)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant