Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cloud/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
## P1 — Lumina Cloud client integration

### C1 — Scaffold `src/services/luminaCloud/`
- [ ] **Goal:** Empty-but-typed module structure: `client.ts`, `types.ts`, `PUBLIC_KEY.ts`, `verify.ts`, `store.ts`, `revocations.ts`, `index.ts` (barrel).
- [x] **Goal:** Empty-but-typed module structure: `client.ts`, `types.ts`, `PUBLIC_KEY.ts`, `verify.ts`, `store.ts`, `revocations.ts`, `index.ts` (barrel).
- **Acceptance:**
- `types.ts` mirrors `cloud/CONTRACT.md` §1.1 license payload + §2.4 usage response + §6 error shapes.
- `index.ts` re-exports the public surface.
Expand Down Expand Up @@ -126,3 +126,5 @@
## Done log

(Loop agent appends `[x] C<n> — <date> — <commit hash> — <one-line note>` here as tasks complete, mirroring the `[x]` above.)

[x] C1 — 2026-04-28 — ba66b60 — scaffolded `src/services/luminaCloud/` (types + stubs); typecheck passes; no new runtime deps
12 changes: 12 additions & 0 deletions src/services/luminaCloud/PUBLIC_KEY.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Ed25519 public key for verifying Lumina Cloud licenses (CONTRACT.md §1.2, §7).
*
* Format: base64-encoded 32-byte raw Ed25519 public key.
*
* LEAD: replace with real public key from lumina-cloud T3 output.
*
* Until the real key is delivered, tests use a fixture keypair (see C2 task).
* The placeholder below is deliberately a recognizably-fake all-`A` string so
* any accidental ship-to-prod fails verification immediately.
*/
export const PUBLIC_KEY_B64 = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
32 changes: 32 additions & 0 deletions src/services/luminaCloud/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type {
LicenseVerifyResponse,
ModelsResponse,
RevocationsResponse,
UsageResponse,
} from './types';

/**
* Typed HTTP client for the Lumina Cloud REST surface (CONTRACT.md §2).
* Implemented in task C5 — this scaffold only fixes the public shape.
*
* Base URL is configurable via `LUMINA_CLOUD_BASE_URL`, default
* `https://api.lumina-note.com`.
*/

export const DEFAULT_BASE_URL = 'https://api.lumina-note.com';

export async function verifyLicenseOnline(_license: string): Promise<LicenseVerifyResponse> {
throw new Error('luminaCloud.client.verifyLicenseOnline: not implemented yet (task C5)');
}

export async function getModels(_license: string): Promise<ModelsResponse> {
throw new Error('luminaCloud.client.getModels: not implemented yet (task C5)');
}

export async function getUsage(_license: string): Promise<UsageResponse> {
throw new Error('luminaCloud.client.getUsage: not implemented yet (task C5)');
}

export async function getRevocations(_since?: string): Promise<RevocationsResponse> {
throw new Error('luminaCloud.client.getRevocations: not implemented yet (task C5)');
}
34 changes: 34 additions & 0 deletions src/services/luminaCloud/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Lumina Cloud client — public barrel.
*
* Wire types and behaviors are pinned to `cloud/CONTRACT.md`. See
* `cloud/TASKS.md` for the per-file rollout (C1…C13).
*/

export type {
CloudErrorBody,
CloudErrorCode,
CloudModel,
LicensePayload,
LicenseStatus,
LicenseVerifyResponse,
ModelsResponse,
RevocationsResponse,
UsageResponse,
} from './types';

export { PUBLIC_KEY_B64 } from './PUBLIC_KEY';

export { verifyLicense } from './verify';

export { loadLicense, removeLicense, saveLicense } from './store';

export { isRevoked } from './revocations';

export {
DEFAULT_BASE_URL,
getModels,
getRevocations,
getUsage,
verifyLicenseOnline,
} from './client';
8 changes: 8 additions & 0 deletions src/services/luminaCloud/revocations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Local revocation cache, refreshed daily from `GET /v1/license/revocations`
* (CONTRACT.md §2.5). Implemented in task C6.
*/

export async function isRevoked(_lid: string): Promise<boolean> {
throw new Error('luminaCloud.isRevoked: not implemented yet (task C6)');
}
19 changes: 19 additions & 0 deletions src/services/luminaCloud/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* License storage in the OS keychain (Electron `safeStorage`) with a Linux
* file fallback. Implemented in task C3.
*
* The functions are async because the IPC bridge to the Electron main process
* is async; the in-memory derived state lives in `useLicenseStore` (task C4).
*/

export async function saveLicense(_license: string): Promise<void> {
throw new Error('luminaCloud.saveLicense: not implemented yet (task C3)');
}

export async function loadLicense(): Promise<string | null> {
throw new Error('luminaCloud.loadLicense: not implemented yet (task C3)');
}

export async function removeLicense(): Promise<void> {
throw new Error('luminaCloud.removeLicense: not implemented yet (task C3)');
}
88 changes: 88 additions & 0 deletions src/services/luminaCloud/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Wire types mirroring `cloud/CONTRACT.md`. Keep this file in lock-step with
* the contract — when the contract changes, update here and bump tests.
*/

// §1.1 License payload (canonical JSON, sorted keys, embedded in the license token)
export interface LicensePayload {
/** Schema version — currently 1. */
v: number;
/** License id, ULID-shaped. */
lid: string;
/** Buyer email, lowercased. */
email: string;
/** SKU identifier — see CONTRACT.md §3. */
sku: string;
/** Feature flags — see CONTRACT.md §4. Unknown flags must be ignored by the client. */
features: string[];
/** ISO 8601 UTC, `Z` suffix. */
issued_at: string;
/** ISO 8601 UTC `Z`, or null for lifetime licenses. */
expires_at: string | null;
/** Upstream order id from Creem. */
order_id: string;
/** Soft, advisory device cap — clients may ignore. */
device_limit: number;
}

// §2.1 Online verification response
export type LicenseVerifyResponse =
| {
valid: true;
payload: LicensePayload;
revoked: boolean;
usage: UsageResponse;
}
| {
valid: false;
reason: 'signature_invalid' | 'revoked' | 'expired' | 'malformed';
};

// §2.3 Models list
export interface CloudModel {
id: string;
upstream: string;
context: number;
}

export interface ModelsResponse {
data: CloudModel[];
}

// §2.4 Usage response
export interface UsageResponse {
period_start: string;
period_end: string;
tokens_used: number;
tokens_quota: number;
requests_count: number;
}

// §2.5 Revocations
export interface RevocationsResponse {
as_of: string;
revoked_lids: string[];
}

// §6 Error format
export type CloudErrorCode =
| 'bad_request'
| 'invalid_license'
| 'revoked_license'
| 'expired_license'
| 'quota_exceeded'
| 'feature_disabled'
| 'not_found'
| 'rate_limit'
| 'internal'
| 'upstream_unavailable';

export interface CloudErrorBody {
error: {
code: CloudErrorCode;
message: string;
};
}

// Client-side derived state — used by the Zustand store (C4)
export type LicenseStatus = 'idle' | 'loading' | 'valid' | 'invalid';
14 changes: 14 additions & 0 deletions src/services/luminaCloud/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { LicensePayload } from './types';

/**
* Offline license verification per CONTRACT.md §1.3.
*
* Returns the decoded payload iff the Ed25519 signature verifies against the
* bundled public key, otherwise returns `null`. Never throws — malformed
* input yields `null` too.
*
* Implemented in task C2 with `@noble/ed25519`.
*/
export function verifyLicense(_license: string): LicensePayload | null {
throw new Error('luminaCloud.verifyLicense: not implemented yet (task C2)');
}