create-planet 1.0.0 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Create a Federated Planet
2
+
3
+ Scaffold a new planet for the [Federated Planets](https://github.com/Federated-Planets/federated-planets) universe — a decentralized space exploration game where every planet is a sovereign website.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm create planet
9
+ ```
10
+
11
+ The interactive setup will ask you for:
12
+
13
+ - **Output directory** — where to scaffold the project
14
+ - **Planet name** — displayed on your landing site and shared with neighboring planets
15
+ - **Planet description** — shown to visitors on your landing site
16
+ - **Warp links** — URLs of neighboring planets to link to (leave empty for defaults)
17
+ - **Cloudflare worker name** — the name of your Worker on Cloudflare
18
+
19
+ It will then generate a ready-to-deploy `wrangler.jsonc` configured with a Durable Object for storage.
20
+
21
+ ## Prerequisites
22
+
23
+ - [Node.js](https://nodejs.org/) 18+
24
+ - [Wrangler](https://developers.cloudflare.com/workers/wrangler/) authenticated with your Cloudflare account (`npx wrangler login`)
25
+
26
+ ## After Scaffolding
27
+
28
+ ```bash
29
+ cd my-planet
30
+ npm install
31
+ npm run dev # local development
32
+ ```
33
+
34
+ To deploy, connect your project to [Cloudflare Workers CI](https://developers.cloudflare.com/workers/ci-cd/) and set:
35
+
36
+ - **Build command:** `npm run build`
37
+ - **Deploy command:** `npx wrangler deploy`
38
+
39
+ ## What Gets Created
40
+
41
+ ```
42
+ my-planet/
43
+ ├── src/
44
+ │ ├── pages/ # Astro pages (landing site, space port, manifest)
45
+ │ └── lib/ # Protocol logic (consensus, crypto, travel)
46
+ ├── public/ # CSS, 3D star map (Three.js), static assets
47
+ ├── scripts/ # Dev utilities (simulate universe, inject DO exports)
48
+ ├── wrangler.jsonc # Generated with your worker name and Durable Object config
49
+ ├── wrangler.dev.jsonc # Local development config
50
+ └── package.json
51
+ ```
52
+
53
+ ## Learn More
54
+
55
+ - [Federated Planets specification](https://github.com/Federated-Planets/federated-planets)
56
+ - [Space Travel Protocol](https://github.com/Federated-Planets/federated-planets/blob/main/TRAVEL.md)
57
+ - [Planet reference implementation](https://github.com/Federated-Planets/planet)
package/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import * as p from "@clack/prompts";
3
3
  import { execSync, execFileSync } from "child_process";
4
- import { cpSync, readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
4
+ import {
5
+ cpSync,
6
+ readFileSync,
7
+ writeFileSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ } from "fs";
5
12
  import path from "path";
6
13
  import { fileURLToPath } from "url";
7
14
 
@@ -39,16 +46,6 @@ const runWrangler = (args) => {
39
46
  }
40
47
  };
41
48
 
42
- const parseKvId = (output) => {
43
- const match = output.match(/"id":\s*"([^"]+)"/);
44
- return match?.[1] ?? null;
45
- };
46
-
47
- const parseD1Id = (output) => {
48
- const match = output.match(/"uuid":\s*"([^"]+)"/);
49
- return match?.[1] ?? null;
50
- };
51
-
52
49
  const patchConfigTs = (filePath, name, description, warpLinks) => {
53
50
  let content = readFileSync(filePath, "utf-8");
54
51
 
@@ -95,7 +92,8 @@ const main = async () => {
95
92
  message: "Output directory",
96
93
  placeholder: "my-planet",
97
94
  defaultValue: "my-planet",
98
- validate: (v) => (v.trim() ? undefined : "Directory name is required"),
95
+ validate: (v) =>
96
+ v.trim() ? undefined : "Directory name is required",
99
97
  }),
100
98
  planetName: () =>
101
99
  p.text({
@@ -165,9 +163,17 @@ const main = async () => {
165
163
  s.start("Configuring planet...");
166
164
  const configPath = path.join(outDir, "src/lib/config.ts");
167
165
  const warpLinks = answers.warpLinks
168
- ? answers.warpLinks.split(",").map((u) => u.trim()).filter(Boolean)
166
+ ? answers.warpLinks
167
+ .split(",")
168
+ .map((u) => u.trim())
169
+ .filter(Boolean)
169
170
  : [];
170
- patchConfigTs(configPath, answers.planetName, answers.planetDescription, warpLinks);
171
+ patchConfigTs(
172
+ configPath,
173
+ answers.planetName,
174
+ answers.planetDescription,
175
+ warpLinks,
176
+ );
171
177
  s.stop("Planet configured.");
172
178
 
173
179
  // Update package.json name
@@ -176,27 +182,6 @@ const main = async () => {
176
182
  pkg.name = answers.workerName;
177
183
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
178
184
 
179
- // Create Cloudflare resources
180
- s.start("Creating KV namespace...");
181
- const kvOutput = runWrangler(["kv", "namespace", "create", "KV", "--json"]);
182
- const kvId = parseKvId(kvOutput);
183
- if (!kvId) {
184
- s.stop("Failed to create KV namespace.");
185
- p.note(kvOutput, "Wrangler output");
186
- process.exit(1);
187
- }
188
- s.stop(`KV namespace created: ${kvId}`);
189
-
190
- s.start("Creating D1 database...");
191
- const d1Output = runWrangler(["d1", "create", "planet_db", "--json"]);
192
- const d1Id = parseD1Id(d1Output);
193
- if (!d1Id) {
194
- s.stop("Failed to create D1 database.");
195
- p.note(d1Output, "Wrangler output");
196
- process.exit(1);
197
- }
198
- s.stop(`D1 database created: ${d1Id}`);
199
-
200
185
  // Write wrangler.jsonc
201
186
  s.start("Writing wrangler.jsonc...");
202
187
  const wranglerConfig = {
@@ -204,19 +189,10 @@ const main = async () => {
204
189
  main: "dist/server/entry.mjs",
205
190
  compatibility_date: "2026-03-31",
206
191
  assets: { directory: "dist/client", binding: "STATIC_ASSETS" },
207
- d1_databases: [
208
- {
209
- binding: "DB",
210
- database_name: "planet_db",
211
- database_id: d1Id,
212
- migrations_dir: "migrations",
213
- },
214
- ],
215
- kv_namespaces: [{ binding: "KV", id: kvId }],
216
192
  durable_objects: {
217
193
  bindings: [{ name: "TRAFFIC_CONTROL", class_name: "TrafficControl" }],
218
194
  },
219
- migrations: [{ tag: "v1", new_classes: ["TrafficControl"] }],
195
+ migrations: [{ tag: "v1", new_sqlite_classes: ["TrafficControl"] }],
220
196
  };
221
197
  writeFileSync(
222
198
  path.join(outDir, "wrangler.jsonc"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-planet",
3
- "version": "1.0.0",
3
+ "version": "1.0.5",
4
4
  "description": "Scaffold a new Federated Planets planet",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,11 @@
14
14
  "sync-template": "node scripts/sync-template.js",
15
15
  "prepublishOnly": "node scripts/sync-template.js"
16
16
  },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Federated-Planets/planet.git",
20
+ "directory": "create-planet"
21
+ },
17
22
  "dependencies": {
18
23
  "@clack/prompts": "^0.9.0"
19
24
  }
@@ -0,0 +1,30 @@
1
+ name: Publish create-planet to npm
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "22"
22
+ registry-url: "https://registry.npmjs.org"
23
+
24
+ - name: Install create-planet dependencies
25
+ run: npm install
26
+ working-directory: create-planet
27
+
28
+ - name: Sync template and publish
29
+ run: npm publish
30
+ working-directory: create-planet
@@ -15,8 +15,7 @@ Run these commands one after another to make sure they match permissions.
15
15
 
16
16
  - **Framework:** Astro (SSR mode)
17
17
  - **Runtime:** Cloudflare Workers (Note: This is a Worker project, NOT a Pages project)
18
- - **Database:** Cloudflare D1 (Local state, Mission Archive, Traffic Logs)
19
- - **State/Cache:** Cloudflare KV (Cryptographic keys, Distributed Ledger cache)
18
+ - **Storage:** Cloudflare Durable Objects with SQLite (Local state, Mission Archive, Traffic Logs, Cryptographic keys, Consensus state)
20
19
  - **Validation:** Zod
21
20
  - **Hashing:** MD5 (via `md5` package)
22
21
  - **Cryptography:** Web Crypto API (Ed25519 signatures)
@@ -28,18 +27,19 @@ Run these commands one after another to make sure they match permissions.
28
27
  - Add `space_port` field to `public/manifest.json`.
29
28
  - Endpoint: `https://<domain>/api/v1/port`
30
29
 
31
- ### 2.2 Database Schema (D1)
30
+ ### 2.2 Database Schema (Durable Object SQLite)
32
31
 
33
- Create `schema.sql`:
32
+ Tables are created automatically in the TrafficControl Durable Object constructor:
34
33
 
35
- - `travel_plans`: `id`, `ship_id`, `origin_url`, `destination_url`, `start_timestamp`, `end_timestamp`, `status` (PREPARING, TRANSIT, ARRIVED), `signatures` (JSON array).
36
- - `mission_archive`: `id`, `ship_id`, `event` (ARRIVED, DEPARTED), `location_name`, `location_url`, `timestamp`.
34
+ - `travel_plans`: `id`, `ship_id`, `origin_url`, `destination_url`, `start_timestamp`, `end_timestamp`, `status` (PREPARING, PLAN_ACCEPTED), `signatures` (JSON).
37
35
  - `traffic_controllers`: Cache of known neighbor manifests and their `space_port` status.
36
+ - `identity`: Ed25519 key pair storage.
37
+ - `consensus_plans`: In-flight consensus plan state with expiration.
38
38
 
39
39
  ### 2.3 Cryptography Utility
40
40
 
41
41
  - Implement `crypto.ts` to manage the planet's identity.
42
- - Generate and store an Ed25519 KeyPair in KV if it doesn't exist.
42
+ - Generate and store an Ed25519 KeyPair in Durable Object SQLite if it doesn't exist.
43
43
  - Methods for `sign(data)` and `verify(data, signature, publicKey)`.
44
44
 
45
45
  ## 3. Phase 2: Core Protocol Logic
@@ -70,7 +70,7 @@ Create `schema.sql`:
70
70
 
71
71
  ### 4.1 Dynamic Star Map
72
72
 
73
- - Update `index.astro` to fetch live traffic from D1.
73
+ - Update `index.astro` to fetch live traffic from the Durable Object.
74
74
  - Pass real coordinates from the database to the ThreeJS map.
75
75
 
76
76
  ### 4.2 Flight Deck
@@ -5,22 +5,20 @@ The following diagram illustrates the lifecycle of a travel transaction using th
5
5
  ```mermaid
6
6
  sequenceDiagram
7
7
  participant Traveler as 🚀 Ship Browser
8
- participant DO as TrafficControl DO
8
+ participant DO as TrafficControl DO (SQLite)
9
9
  participant Origin as 🌍 Origin Space Port
10
10
  participant Dest as 🌍 Destination Space Port
11
- participant KV as KV Store
12
- participant D1 as D1 Database
13
11
  participant TCs as 🌐 Elected Traffic Controllers (3f+1)
14
12
 
15
13
  Note over Traveler, Dest: PHASE 1: INITIATION & SORTITION
16
14
  Traveler->>Origin: POST /initiate (Destination, ShipID)
17
15
  par Parallel discovery
18
- Origin->>D1: Lookup cached manifests for origin neighbors
19
- D1-->>Origin: Origin neighbor manifests
16
+ Origin->>DO: Lookup cached manifests for origin neighbors
17
+ DO-->>Origin: Origin neighbor manifests
20
18
  opt Cache miss
21
19
  Origin->>Dest: Fetch space-manifest link + manifest JSON
22
20
  Dest-->>Origin: PlanetManifest (name, landing_site, space_port)
23
- Origin->>D1: Store manifest in traffic_controllers cache
21
+ Origin->>DO: Store manifest in traffic_controllers cache
24
22
  end
25
23
  and
26
24
  Origin->>Dest: GET ?action=neighbors
@@ -30,22 +28,22 @@ sequenceDiagram
30
28
  Note over Origin: Mandatory TCs: Origin + Destination
31
29
  Note over Origin: Elected TCs: half from origin neighbors, half from dest neighbors
32
30
  Origin->>Origin: Elect TCs (seed-based sortition, dedup)
33
- Origin->>KV: Read Ed25519 identity keys (identity_public/private)
34
- KV-->>Origin: Key pair
31
+ Origin->>DO: Read Ed25519 identity keys
32
+ DO-->>Origin: Key pair
35
33
  Origin->>Origin: Sign Plan (Ed25519)
36
- Origin->>KV: Save consensus plan state (consensus_plan_{id})
34
+ Origin->>DO: Save consensus plan state
37
35
  par Fire-and-forget
38
36
  Origin-->>DO: INITIATE_TRAVEL event
39
37
  and Parallel POST ×N
40
38
  Origin->>TCs: PRE-PREPARE (Signed Plan)
41
39
  end
42
40
 
43
- Note over TCs, KV: PHASE 2: CONSENSUS (PBFT)
41
+ Note over TCs, DO: PHASE 2: CONSENSUS (PBFT)
44
42
  loop Each Traffic Controller
45
43
  TCs->>TCs: Verify Plan integrity (coordinates & travel time)
46
- TCs->>KV: Read own identity keys
44
+ TCs->>DO: Read own identity keys
47
45
  TCs->>TCs: Sign Plan
48
- TCs->>KV: Merge signatures into consensus plan state
46
+ TCs->>DO: Merge signatures into consensus plan state
49
47
  par Fire-and-forget
50
48
  TCs-->>DO: PREPARE_PLAN event
51
49
  and Parallel POST ×N
@@ -53,17 +51,17 @@ sequenceDiagram
53
51
  end
54
52
  end
55
53
 
56
- Note over Origin, D1: PHASE 3: RECORDING & TRANSIT
57
- Origin->>KV: Read accumulated signatures (consensus_plan_{id})
54
+ Note over Origin, DO: PHASE 3: RECORDING & TRANSIT
55
+ Origin->>DO: Read accumulated signatures
58
56
  Origin->>Origin: Check quorum (2f+1 signatures reached)
59
57
  Origin->>Dest: POST ?action=register (Approved Plan)
60
58
  Dest->>Dest: Verify destination URL
61
59
  Dest->>Dest: Verify travel time math
62
60
  Dest->>Dest: Verify end_timestamp not expired (anti-cheat)
63
61
  Dest->>Dest: Verify quorum (2f+1 signatures)
64
- Dest->>D1: Store plan (incoming traffic)
62
+ Dest->>DO: Store plan (incoming traffic)
65
63
  Dest-->>Origin: 200 OK
66
- Origin->>D1: Persist Approved Plan (travel_plans, status=PLAN_ACCEPTED)
64
+ Origin->>DO: Persist Approved Plan (travel_plans, status=PLAN_ACCEPTED)
67
65
  Origin->>DO: Broadcast QUORUM_REACHED event
68
66
  DO-->>Traveler: WebSocket push (status updates to live UI)
69
67
  Origin-->>Traveler: 200 OK (Travel Authorized)
@@ -93,31 +91,36 @@ sequenceDiagram
93
91
 
94
92
  ## Data Storage
95
93
 
96
- | Store | Purpose | Durability |
97
- | ------------------ | --------------------------------------------------------- | --------------------------------- |
98
- | **D1** | `travel_plans`, `traffic_controllers` cache | Persistent |
99
- | **KV** | Ed25519 identity key pair, in-flight consensus plan state | Persistent (keys), TTL 1h (plans) |
100
- | **Durable Object** | WebSocket sessions, last-50 event ring buffer | In-memory only (volatile) |
94
+ All persistent storage lives in the TrafficControl Durable Object's built-in SQLite database:
95
+
96
+ | Table | Purpose | Durability |
97
+ | --------------------- | ------------------------------------------------ | ---------------------- |
98
+ | `travel_plans` | Active and historical journeys | Persistent |
99
+ | `traffic_controllers` | Cached neighbor manifests (1h TTL via timestamp) | Persistent |
100
+ | `identity` | Ed25519 key pair | Persistent |
101
+ | `consensus_plans` | In-flight consensus plan state | Persistent (1h expiry) |
102
+
103
+ The DO also holds in-memory state: WebSocket sessions and a last-50 event ring buffer (volatile).
101
104
 
102
105
  ## Plan Data State Diagram
103
106
 
104
- Server-side status stored in `travel_plans.status` (D1) and the in-flight KV plan:
107
+ Server-side status stored in `travel_plans.status` and the in-flight `consensus_plans` table:
105
108
 
106
109
  ```mermaid
107
110
  stateDiagram-v2
108
111
  [*] --> PREPARING : Origin creates plan (handleInitiate)
109
- PREPARING --> PLAN_ACCEPTED : Quorum reached (2f+1 sigs) (handleCommit → D1 insert)
112
+ PREPARING --> PLAN_ACCEPTED : Quorum reached (2f+1 sigs) (handleCommit → travel_plans insert)
110
113
  PLAN_ACCEPTED --> [*] : end_timestamp passes (no server transition — record kept forever)
111
114
 
112
115
  note right of PREPARING
113
- Stored in KV only
114
- (consensus_plan_{id})
116
+ Stored in consensus_plans table
117
+ (expires after 1h)
115
118
  end note
116
119
 
117
120
  note right of PLAN_ACCEPTED
118
- Persisted to D1 travel_plans
119
- Registered at destination (fire-and-forget)
120
- KV entry expires after TTL
121
+ Persisted to travel_plans table
122
+ Registered at destination
123
+ consensus_plans entry expires
121
124
  end note
122
125
  ```
123
126
 
@@ -175,21 +178,17 @@ erDiagram
175
178
  INTEGER last_manifest_fetch "Unix ms, TTL 1h"
176
179
  }
177
180
 
178
- KV_IDENTITY {
179
- TEXT identity_public "Ed25519 public key (Base64)"
180
- TEXT identity_private "Ed25519 private key (Base64)"
181
- }
182
-
183
- KV_CONSENSUS {
184
- TEXT consensus_plan_id PK "consensus_plan_{uuid}"
185
- TEXT plan_json "TravelPlan with accumulated sigs"
181
+ IDENTITY {
182
+ TEXT key PK "identity_public or identity_private"
183
+ TEXT value "Ed25519 key (Base64)"
186
184
  }
187
185
 
188
- DO_TRAFFIC_CONTROL {
189
- SET sessions "Active WebSocket connections"
190
- ARRAY events "Ring buffer: last 50 API events"
186
+ CONSENSUS_PLANS {
187
+ TEXT id PK "plan UUID"
188
+ TEXT data "TravelPlan JSON with accumulated sigs"
189
+ INTEGER expires_at "Unix ms"
191
190
  }
192
191
 
193
- TRAVEL_PLANS ||--o{ KV_CONSENSUS : "built during PBFT"
192
+ TRAVEL_PLANS ||--o{ CONSENSUS_PLANS : "built during PBFT"
194
193
  TRAVEL_PLANS }o--o{ TRAFFIC_CONTROLLERS : "controllers elected from"
195
194
  ```
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "@federated-planets/planet",
3
- "version": "1.0.0",
3
+ "version": "1.0.5",
4
4
  "description": "A decentralized space exploration game where every planet is a sovereign website.",
5
5
  "scripts": {
6
6
  "dev": "npm run build && wrangler dev -c wrangler.dev.jsonc",
7
- "dev:db:init": "wrangler d1 execute planet_db --file=schema.sql --local -c wrangler.dev.jsonc",
8
7
  "dev:clean": "rm -rf .wrangler/state",
9
8
  "format": "prettier --write .",
10
9
  "build": "astro build",
11
10
  "postbuild": "node scripts/inject-do-exports.js",
12
11
  "preview": "wrangler preview",
13
12
  "simulate": "node scripts/simulate-universe.js",
13
+ "simulate:open": "node scripts/simulate-universe.js --open",
14
14
  "test": "npm run build && vitest run",
15
15
  "test:protocol": "npm run build && vitest run src/tests/protocol.test.ts",
16
- "test:warp-links": "npm run build && vitest run src/tests/warp-links.test.ts"
16
+ "test:warp-links": "npm run build && vitest run src/tests/warp-links.test.ts",
17
+ "release": "npm version patch -m \"Release v%s\"",
18
+ "version": "node -e \"const fs=require('fs'),p=JSON.parse(fs.readFileSync('create-planet/package.json'));p.version=process.env.npm_new_version;fs.writeFileSync('create-planet/package.json',JSON.stringify(p,null,2)+'\\n')\" && git add create-planet/package.json",
19
+ "postversion": "git push && git push --tags"
17
20
  },
18
21
  "dependencies": {
19
22
  "@astrojs/cloudflare": "^13.1.4",
@@ -34,16 +34,6 @@ const startPlanet = (planet) => {
34
34
  const { id, name, url, port } = planet;
35
35
  const inspectorPort = BASE_INSPECTOR_PORT + id;
36
36
 
37
- // Initialize Database first
38
- console.log(`[${name}] Initializing database...`);
39
- execSync(
40
- `npx wrangler d1 execute planet_db --file=schema.sql -c wrangler.dev.jsonc --local --persist-to=.wrangler/state/planet-${id}`,
41
- {
42
- cwd: path.join(__dirname, ".."),
43
- stdio: "inherit",
44
- },
45
- );
46
-
47
37
  const env = {
48
38
  ...process.env,
49
39
  PUBLIC_SIM_PLANET_NAME: name,
@@ -1,8 +1,6 @@
1
1
  /// <reference types="astro/client" />
2
2
 
3
3
  interface ImportMetaEnv {
4
- readonly KV: import("@cloudflare/workers-types").KVNamespace;
5
- readonly DB: import("@cloudflare/workers-types").D1Database;
6
4
  readonly TRAFFIC_CONTROL: import("@cloudflare/workers-types").DurableObjectNamespace;
7
5
  readonly PUBLIC_SIM_PLANET_NAME?: string;
8
6
  readonly PUBLIC_SIM_PLANET_DESCRIPTION?: string;
@@ -8,12 +8,18 @@ export const DEFAULT_WARP_LINKS = [
8
8
  name: "Federation Prime",
9
9
  url: "https://prime.federatedplanets.com/",
10
10
  },
11
- { name: "Waystation Meridian", url: "https://waystation.federatedplanets.com/" },
11
+ {
12
+ name: "Waystation Meridian",
13
+ url: "https://waystation.federatedplanets.com/",
14
+ },
12
15
  { name: "The Interchange", url: "https://interchange.federatedplanets.com/" },
13
16
  { name: "Port Cassini", url: "https://port-cassini.federatedplanets.com/" },
14
17
  { name: "Terminus Reach", url: "https://terminus.federatedplanets.com/" },
15
18
  { name: "Driftyard Seven", url: "https://driftyard.federatedplanets.com/" },
16
- { name: "Towel 42 Space Outpost", url: "https://towel-42.federatedplanets.com/" },
19
+ {
20
+ name: "Towel 42 Space Outpost",
21
+ url: "https://towel-42.federatedplanets.com/",
22
+ },
17
23
  { name: "Explorers Outpost", url: "https://www.nasa.gov/" },
18
24
  ];
19
25
 
@@ -2,6 +2,7 @@ import { CryptoCore } from "./crypto";
2
2
  import type { PlanetManifest } from "./travel";
3
3
  import { env as cloudflareEnv } from "cloudflare:workers";
4
4
  import { PLANET_NAME } from "./config";
5
+ import { doStorage } from "./do-storage";
5
6
 
6
7
  // Robust helper to get simulation variables from any available environment source
7
8
  const getSimVar = (name: string): string | undefined => {
@@ -34,6 +35,7 @@ export interface TravelPlan {
34
35
  status: "PREPARING" | "PLAN_ACCEPTED";
35
36
  traffic_controllers: string[]; // List of landing site URLs elected
36
37
  signatures: Record<string, string>; // planet_url -> signature
38
+ origin_lists_dest?: boolean; // Whether origin declared destination as a neighbor (set at initiation)
37
39
  }
38
40
 
39
41
  export class ConsensusEngine {
@@ -106,22 +108,26 @@ export class ConsensusEngine {
106
108
  }
107
109
 
108
110
  /**
109
- * Saves plan state to KV for active consensus tracking
111
+ * Saves plan state to DO SQLite for active consensus tracking
110
112
  */
111
- static async savePlanState(KV: KVNamespace, plan: TravelPlan) {
112
- await KV.put(`consensus_plan_${plan.id}`, JSON.stringify(plan), {
113
- expirationTtl: 3600,
113
+ static async savePlanState(
114
+ TRAFFIC_CONTROL: DurableObjectNamespace,
115
+ plan: TravelPlan,
116
+ ) {
117
+ await doStorage(TRAFFIC_CONTROL, "savePlan", {
118
+ planId: plan.id,
119
+ data: JSON.stringify(plan),
114
120
  });
115
121
  }
116
122
 
117
123
  /**
118
- * Retrieves plan state from KV
124
+ * Retrieves plan state from DO SQLite
119
125
  */
120
126
  static async getPlanState(
121
- KV: KVNamespace,
127
+ TRAFFIC_CONTROL: DurableObjectNamespace,
122
128
  planId: string,
123
129
  ): Promise<TravelPlan | null> {
124
- const data = await KV.get(`consensus_plan_${planId}`);
125
- return data ? JSON.parse(data) : null;
130
+ const result: any = await doStorage(TRAFFIC_CONTROL, "getPlan", { planId });
131
+ return result.data ? JSON.parse(result.data) : null;
126
132
  }
127
133
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Helper to call the TrafficControl DO's storage API.
3
+ */
4
+ export async function doStorage(
5
+ TRAFFIC_CONTROL: DurableObjectNamespace,
6
+ action: string,
7
+ data: Record<string, any> = {},
8
+ ): Promise<any> {
9
+ const id = TRAFFIC_CONTROL.idFromName("global");
10
+ const obj = TRAFFIC_CONTROL.get(id);
11
+ const res = await obj.fetch("http://do/storage", {
12
+ method: "POST",
13
+ body: JSON.stringify({ action, ...data }),
14
+ });
15
+ return res.json();
16
+ }
17
+
18
+ /**
19
+ * Execute a SQL SELECT query through the DO and return results.
20
+ */
21
+ export async function doQuery(
22
+ TRAFFIC_CONTROL: DurableObjectNamespace,
23
+ sql: string,
24
+ params: any[] = [],
25
+ ): Promise<any[]> {
26
+ const result = await doStorage(TRAFFIC_CONTROL, "query", { sql, params });
27
+ return result.results || [];
28
+ }
29
+
30
+ /**
31
+ * Execute a SQL write (INSERT/UPDATE/DELETE) through the DO.
32
+ */
33
+ export async function doExec(
34
+ TRAFFIC_CONTROL: DurableObjectNamespace,
35
+ sql: string,
36
+ params: any[] = [],
37
+ ): Promise<void> {
38
+ await doStorage(TRAFFIC_CONTROL, "exec", { sql, params });
39
+ }
@@ -1,21 +1,21 @@
1
1
  import { CryptoCore } from "./crypto";
2
+ import { doStorage } from "./do-storage";
2
3
 
3
4
  export class PlanetIdentity {
4
5
  /**
5
- * Retrieves or generates the planet's Ed25519 identity keys from KV.
6
+ * Retrieves or generates the planet's Ed25519 identity keys from DO storage.
6
7
  */
7
- static async getIdentity(KV: KVNamespace): Promise<{
8
+ static async getIdentity(TRAFFIC_CONTROL: DurableObjectNamespace): Promise<{
8
9
  publicKey: CryptoKey;
9
10
  privateKey: CryptoKey;
10
11
  publicKeyBase64: string;
11
12
  }> {
12
- const existingPublic = await KV.get("identity_public");
13
- const existingPrivate = await KV.get("identity_private");
13
+ const result: any = await doStorage(TRAFFIC_CONTROL, "getIdentity");
14
14
 
15
- if (existingPublic && existingPrivate) {
16
- const publicKey = await CryptoCore.importKey(existingPublic, "public");
17
- const privateKey = await CryptoCore.importKey(existingPrivate, "private");
18
- return { publicKey, privateKey, publicKeyBase64: existingPublic };
15
+ if (result.public && result.private) {
16
+ const publicKey = await CryptoCore.importKey(result.public, "public");
17
+ const privateKey = await CryptoCore.importKey(result.private, "private");
18
+ return { publicKey, privateKey, publicKeyBase64: result.public };
19
19
  }
20
20
 
21
21
  // Generate new if not found
@@ -23,8 +23,10 @@ export class PlanetIdentity {
23
23
  const publicB64 = await CryptoCore.exportKey(keyPair.publicKey);
24
24
  const privateB64 = await CryptoCore.exportKey(keyPair.privateKey);
25
25
 
26
- await KV.put("identity_public", publicB64);
27
- await KV.put("identity_private", privateB64);
26
+ await doStorage(TRAFFIC_CONTROL, "setIdentity", {
27
+ publicKey: publicB64,
28
+ privateKey: privateB64,
29
+ });
28
30
 
29
31
  return {
30
32
  publicKey: keyPair.publicKey,