cargo CI flagged: `unresolved import ed25519_dalek::pkcs8::LineEnding`. The
`LineEnding` re-export path varies between pkcs8/spki/der versions, so the
test code that called `to_pkcs8_pem(LineEnding::LF)` won't compile against
the dependency tree we get with ed25519-dalek 2.2 + pkcs8 0.10.
Fix:
- Drop the `pem` feature from the ed25519-dalek dev-dependency.
- In tests, build the PKCS#8 v1 PrivateKeyInfo and SubjectPublicKeyInfo
DER blobs manually from the raw 32-byte Ed25519 seed/public key. The
Ed25519 layout is fixed (16-byte prefix + 32-byte key) so this is short
and stable.
- Pass the resulting DER bytes to `EncodingKey::from_ed_der` /
`DecodingKey::from_ed_der`.
Refactor:
- Extract `strict_validation()` and `embedded_decoding_key()` helpers so
the validation config (mandatory exp/iat for CWE-613) lives in one
place and production callers all share the same DecodingKey constructor.
- `validate_with_key` and `validate_activation_with_key` now take a
`&DecodingKey` instead of raw PEM bytes; production builds the key
once via `embedded_decoding_key()`.
- New canary test `embedded_public_key_pem_parses` fails fast if the
embedded PEM constant ever becomes malformed.
Introduces the offline license infrastructure for the Base/Premium editions.
- jsonwebtoken (EdDSA) verifies license JWTs against an embedded Ed25519
public key. The exp claim is mandatory (CWE-613) and is enforced via
Validation::set_required_spec_claims.
- Activation tokens (server-issued, machine-bound) prevent license.key
copying between machines. Storage is wired up; the actual issuance flow
ships with Issue #49.
- get_edition() fails closed to "free" when the license is missing,
invalid, expired, or activated for a different machine.
- New commands/entitlements module centralizes feature → tier mapping so
Issue #48 (and any future gate) reads from a single source of truth.
- machine-uid provides the cross-platform machine identifier; OS reinstall
invalidates the activation token by design.
- Tests cover happy path, expiry, wrong-key signature, malformed JWT,
unknown edition, and machine_id matching for activation tokens.
The embedded PUBLIC_KEY_PEM is the RFC 8410 §10.3 test vector, clearly
labelled as a development placeholder; replacing it with the production
public key is a release-time task.
The previous approach deleted migration records to force re-application,
but this is dangerous for migration 2 which DELETEs all categories and
keywords before re-inserting them, wiping user customizations.
Now computes the expected SHA-384 checksum (matching sqlx) and updates
the stored checksum in _sqlx_migrations, so the migration is recognized
as already applied without being re-run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add repair_migrations Tauri command that deletes stale migration 1
checksum from _sqlx_migrations before Database.load(). Migration 1
is idempotent (CREATE IF NOT EXISTS) so re-applying is safe.
Fixes "migration 1 was previously applied but has been modified".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each profile gets its own SQLite database file for complete data isolation.
Profile selection screen at launch, sidebar switcher for quick switching,
and optional 4-6 digit PIN for privacy. Existing database becomes the
default profile with seamless upgrade.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add export (JSON/CSV) and import (full replace) to the Settings page.
Export supports 3 modes (transactions+categories, transactions only,
categories only) with optional password encryption using Argon2id key
derivation. Import detects encrypted .sref files, prompts for password,
and shows a destructive confirmation modal before replacing data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>