v0.2.0:0 plumbing prep — draft version file + cutover doc
Adds startos/versions/v0.2.0.ts as a draft milestone version entry, ready to swap in as `current` when we're ready to cut. NOT yet wired into the version graph at versions/index.ts — flipping that switch is a release decision (one-line change there, then make x86 + publish), and the draft sits parked so we can iterate on the release-notes content without committing to the cut. Format note: the SDK's VersionInfo.of() expects releaseNotes as a LocaleString (Record<string, string>), not the string[] form v0.1.0.ts uses. The new file uses the modern shape; v0.1.0.ts keeps its existing form to avoid churn on the alpha line. CUTTING_V0.2.0.md walks the operator (or future me) through the 4-step cutover: edit versions/index.ts to swap in v0_2_0, npm run check, make x86, publish. Plus rollback notes if anything goes sideways post-cut. Why park rather than cut now: 1. The user said "prepare for the version 0.2 plumbing" — that's "prepare" not "do". The cutover is intentional in the user's workflow, not bundled into a routine push. 2. Cutover changes how the StartOS marketplace renders the upgrade dialog to existing :N installs; best to QA the release-notes content first. 3. SDK migration-API behavior on the upstream version bump is worth verifying on a test install before flipping for everyone. The v0.2.0 release notes themselves are written conservatively — they describe what's already shipped and stable in the alpha line through :47, not aspirational v0.3 features.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# Cutting v0.2.0:0
|
||||
|
||||
The v0.2.0 milestone version is drafted at `startos/versions/v0.2.0.ts`
|
||||
but **not yet wired in as the current version**. This file documents
|
||||
exactly what to do when you're ready to flip the switch.
|
||||
|
||||
## Pre-flight (do these once, before the cut)
|
||||
|
||||
1. **Read through `startos/versions/v0.2.0.ts`** — especially the
|
||||
release notes. The notes ship to every operator who installs or
|
||||
upgrades; treat them as the public-facing changelog. Edit freely.
|
||||
2. **Sanity-check the SPA** at `/admin/` on a running `:46` daemon.
|
||||
The v0.2 cut is the one where the SPA is "officially" the primary
|
||||
interface; if anything's still rough, fix it on the alpha line first.
|
||||
3. **Confirm no schema changes are pending.** v0.2.0:0 is a label
|
||||
change, not a data migration — `licensing-service/migrations/`
|
||||
should still end at `0009`. (When v0.3 ships its first schema
|
||||
change, that's a `0010_*.sql` file and the migration regression
|
||||
tests in `tests/migrations.rs` will run against it automatically.)
|
||||
|
||||
## The cut itself (≈5 minutes)
|
||||
|
||||
### Step 1 — Wire v0.2.0 in as the current version
|
||||
|
||||
Edit `startos/versions/index.ts`:
|
||||
|
||||
```ts
|
||||
import { v0_1_0 } from './v0.1.0'
|
||||
import { v0_2_0 } from './v0.2.0' // ← add
|
||||
|
||||
export const versions = VersionGraph.of({
|
||||
current: v0_2_0, // ← change from v0_1_0
|
||||
other: [v0_1_0], // ← add so 0.1.0:N can upgrade
|
||||
})
|
||||
```
|
||||
|
||||
### Step 2 — Type-check + build
|
||||
|
||||
```bash
|
||||
cd licensing-service-startos
|
||||
npm run check # tsc --noEmit; should pass
|
||||
make x86 # produces keysat_x86_64.s9pk for v0.2.0:0
|
||||
```
|
||||
|
||||
If the SDK's `VersionInfo.of` signature wants a migration callback
|
||||
for the upgrade from v0.1.0 → v0.2.0, the `tsc` step will tell you.
|
||||
The current draft has no migration callback because there's no on-
|
||||
disk transformation needed — but if `start-sdk` enforces one, add an
|
||||
empty one:
|
||||
|
||||
```ts
|
||||
export const v0_2_0 = VersionInfo.of({
|
||||
version: '0.2.0:0',
|
||||
releaseNotes: [...],
|
||||
migrations: {
|
||||
up: async () => { /* no-op */ },
|
||||
down: async () => { /* no-op */ },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Step 3 — Publish
|
||||
|
||||
```bash
|
||||
~/.keysat/publish.sh
|
||||
```
|
||||
|
||||
The publish script's gate (current version differs from
|
||||
`~/.keysat/last_published_version`) will fire because `0.2.0:0` is a
|
||||
new version string. The script handles upload + registry-add as
|
||||
usual.
|
||||
|
||||
### Step 4 — Verify the upgrade dialog
|
||||
|
||||
Refresh the StartOS marketplace on a test instance running
|
||||
v0.1.0:46 (or any v0.1.0:N). It should now show v0.2.0:0 as
|
||||
available with the release notes from `v0.2.0.ts` rendered. Click
|
||||
"Update" and confirm the daemon comes up cleanly post-upgrade.
|
||||
|
||||
If the test instance gets stuck (StartOS won't compute the upgrade
|
||||
graph, daemon panics post-upgrade, anything weird): the v0.2.0:0
|
||||
.s9pk is still in the registry but you can pull it via
|
||||
`start-cli registry package remove keysat 0.2.0:0` and roll back to
|
||||
the alpha line by reverting `versions/index.ts`.
|
||||
|
||||
## Rollback
|
||||
|
||||
If it goes sideways:
|
||||
|
||||
```bash
|
||||
# Revert versions/index.ts to use v0_1_0 as current
|
||||
git checkout HEAD~1 -- startos/versions/index.ts
|
||||
|
||||
# Bump to a fresh alpha-iteration revision (so the registry has
|
||||
# something newer than the busted 0.2.0:0)
|
||||
# Edit startos/versions/v0.1.0.ts → version: '0.1.0:47'
|
||||
# with release notes explaining the rollback.
|
||||
|
||||
# Build + publish
|
||||
make x86
|
||||
~/.keysat/publish.sh
|
||||
```
|
||||
|
||||
The bad v0.2.0:0 stays in the registry but operators on
|
||||
v0.1.0:46 won't see it as the latest if a newer v0.1.0:47 is
|
||||
present (StartOS picks the highest-version compatible release).
|
||||
|
||||
## Why v0.2.0:0 (not v0.2)
|
||||
|
||||
The version string is ExVer (`<upstream>:<downstream>`). `0.2.0` is
|
||||
the upstream milestone; `:0` is the wrapper revision. The next
|
||||
routine wrapper change on the v0.2 line is `0.2.0:1`. v0.2's first
|
||||
schema change is a new SQL migration file — the upstream version
|
||||
doesn't move for that.
|
||||
|
||||
The upstream version `0.3.0` opens when we ship a substantial
|
||||
feature set (Zaprite, recurring subscriptions, tier upgrades, etc.)
|
||||
that warrants the marketing distinction.
|
||||
@@ -0,0 +1,64 @@
|
||||
// Draft of the v0.2.0 milestone version entry.
|
||||
//
|
||||
// NOT YET WIRED INTO `versions/index.ts` — this file sits ready to
|
||||
// use when we cut v0.2.0:0 from the alpha-iteration line. To
|
||||
// activate:
|
||||
// 1. In `versions/index.ts`:
|
||||
// import { v0_2_0 } from './v0.2.0'
|
||||
// export const versions = VersionGraph.of({
|
||||
// current: v0_2_0,
|
||||
// other: [v0_1_0], // ← so installs on 0.1.0:N can upgrade
|
||||
// })
|
||||
// 2. Build the .s9pk (`make x86`).
|
||||
// 3. Publish via `~/.keysat/publish.sh` (the version-changed gate
|
||||
// will fire because `0.2.0:0` differs from the recorded
|
||||
// `0.1.0:N`).
|
||||
//
|
||||
// Why this draft exists separately:
|
||||
// - The cut is an irreversible release decision for already-installed
|
||||
// operators (downgrade paths exist in StartOS but they're sticky).
|
||||
// - Wiring it in changes how StartOS computes the upgrade dialog
|
||||
// shown to operators on registry refresh — best to QA the
|
||||
// release-notes content in this file before flipping the switch.
|
||||
// - Lets us write the v0.2.0 release notes carefully and then ship
|
||||
// them all at once, rather than amending mid-build.
|
||||
//
|
||||
// Version-string format reminder: ExVer is `<upstream>:<downstream>`.
|
||||
// The `<upstream>` bump from 0.1.0 → 0.2.0 marks the milestone; the
|
||||
// `:0` resets the downstream revision counter for the new line. The
|
||||
// next routine wrapper update on the v0.2 line will be `0.2.0:1`,
|
||||
// then `:2`, etc.
|
||||
|
||||
import { VersionInfo } from '@start9labs/start-sdk'
|
||||
|
||||
const RELEASE_NOTES = [
|
||||
'Keysat v0.2.0 — first non-alpha milestone. Operator-visible: web admin SPA replaces the StartOS Actions tab for day-to-day work, buyer self-service recovery, opt-in community analytics, and the wire format now agrees byte-for-byte across five language SDKs (Rust, TypeScript, Python, Go, plus the daemon itself).',
|
||||
'',
|
||||
'**The web admin SPA is the headline.** Daily operator work — creating products, configuring policies and discount codes, searching licenses, suspending/revoking, inspecting machines, registering webhook endpoints, browsing the audit log — happens in the embedded dashboard at /admin/. The StartOS Actions tab is intentionally trimmed to setup-time operations only (Connect/Disconnect BTCPay, Set operator name, Set web UI password, Activate Keysat license, Show credentials). No more "wall of buttons" for everyday tasks.',
|
||||
'',
|
||||
'**Buyer self-service recovery.** A buyer who lost their license key can re-derive it themselves from (invoice_id, buyer_email) at /recover on the daemon\'s public URL. No support ticket, no operator involvement. Per-IP rate limited (10 req/min), generic-404 on mismatch (does not leak which side of the pair was wrong), audit-logged with the email\'s SHA-256 hash so the log doesn\'t store PII.',
|
||||
'',
|
||||
'**Webhook delivery DLQ.** The outbound-webhook delivery worker has always retried failed deliveries with exponential backoff up to 10 attempts; failed deliveries past that were silent dead-letters. v0.2 surfaces them: `GET /v1/admin/webhook-deliveries?status=failed` lists them, `POST /v1/admin/webhook-deliveries/:id/retry` re-queues. Surfaced in the SPA on the Webhooks page (defaults to the "Failed" filter so the problem case is what an operator sees first).',
|
||||
'',
|
||||
'**Opt-in community analytics.** Off by default. When enabled (Overview page in the admin UI), the daemon sends a daily anonymous heartbeat: install_uuid (random, not derived from operator identity), daemon version, tier label, and counts (products / active licenses / settled invoices) floored to the nearest 5 to prevent fingerprinting an operator by their exact license count. Uptime is bucketed (<1d / 1-7d / 1-4w / >4w). Operator name, public URL, store id, API keys, buyer email are NEVER sent — and the test suite asserts none of those strings appear in the heartbeat payload.',
|
||||
'',
|
||||
'**Five-language SDK parity.** The Go SDK (github.com/keysat-xyz/keysat-client-go) lands alongside this release. Stdlib only — no third-party Go dependencies. All five implementations of the LIC1 wire format (daemon, Rust SDK, TypeScript SDK, Python SDK, Go SDK) pass the same crosscheck vectors at tests/crosscheck/vector.json byte-for-byte across v1 legacy, v2 trial-with-entitlements, and v2 perpetual-unbound fixtures.',
|
||||
'',
|
||||
'**PaymentProvider trait abstraction.** Internally, the four daemon code paths that talked to BTCPay (purchase, webhook, reconcile, tipping) all now go through the abstract PaymentProvider trait. BTCPay-specific concerns (URL rewriting, status-string normalization, metadata enrichment, payment-hash extraction) live inside the BtcpayProvider impl. This unblocks Zaprite (v0.3) — its impl drops in cleanly without touching call sites.',
|
||||
'',
|
||||
'**Test coverage.** The daemon\'s automated test count grew from ~9 in alpha-iteration :24 to 32 in :47: 9 unit + 12 API integration + 4 SQL migration regression + 4 wire-format crosscheck + 3 webhook-worker integration. Plus the four Go SDK crosscheck tests in the separate Go repo.',
|
||||
'',
|
||||
'**Upgrade from v0.1.0:N.** Straight drop-in. No new SQLite migrations on the v0.2.0:0 cut itself (those landed individually during the alpha iteration). Existing licenses, invoices, products, policies, and discount codes are untouched. Web UI password, BTCPay connection, operator name, tip-recipient configuration all carry over.',
|
||||
'',
|
||||
'**What\'s next (v0.3).** Zaprite payment provider for card payments. Recurring subscriptions. In-place tier upgrades for end customers. Multi-currency pricing (USD + sats with auto-conversion at invoice creation).',
|
||||
].join('\n')
|
||||
|
||||
export const v0_2_0 = VersionInfo.of({
|
||||
version: '0.2.0:0',
|
||||
releaseNotes: { en_US: RELEASE_NOTES },
|
||||
// No on-disk transformation needed — v0.2.0:0 is a label change.
|
||||
// SQLite-level migrations live separately under
|
||||
// licensing-service/migrations/ and run at daemon boot regardless
|
||||
// of the ExVer-level version graph.
|
||||
migrations: {},
|
||||
})
|
||||
Reference in New Issue
Block a user