create-planet 1.0.2 → 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 +2 -3
- package/index.js +21 -45
- package/package.json +6 -1
- package/template/.github/workflows/publish.yml +6 -4
- package/template/AGENTS.md +8 -8
- package/template/PROTOCOL_DIAGRAMS.md +39 -40
- package/template/package.json +2 -2
- package/template/scripts/simulate-universe.js +0 -10
- package/template/src/env.d.ts +0 -2
- package/template/src/lib/config.ts +8 -2
- package/template/src/lib/consensus.ts +14 -8
- package/template/src/lib/do-storage.ts +39 -0
- package/template/src/lib/identity.ts +12 -10
- package/template/src/pages/api/v1/port.ts +172 -102
- package/template/src/pages/index.astro +26 -29
- package/template/src/tests/protocol.test.ts +162 -9
- package/template/src/tests/warp-links.test.ts +5 -8
- package/template/src/traffic-control.ts +104 -1
- package/template/wrangler.build.jsonc +1 -15
- package/template/wrangler.dev.jsonc +2 -15
- package/template/schema.sql +0 -22
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The interactive setup will ask you for:
|
|
|
16
16
|
- **Warp links** — URLs of neighboring planets to link to (leave empty for defaults)
|
|
17
17
|
- **Cloudflare worker name** — the name of your Worker on Cloudflare
|
|
18
18
|
|
|
19
|
-
It will then
|
|
19
|
+
It will then generate a ready-to-deploy `wrangler.jsonc` configured with a Durable Object for storage.
|
|
20
20
|
|
|
21
21
|
## Prerequisites
|
|
22
22
|
|
|
@@ -45,8 +45,7 @@ my-planet/
|
|
|
45
45
|
│ └── lib/ # Protocol logic (consensus, crypto, travel)
|
|
46
46
|
├── public/ # CSS, 3D star map (Three.js), static assets
|
|
47
47
|
├── scripts/ # Dev utilities (simulate universe, inject DO exports)
|
|
48
|
-
├──
|
|
49
|
-
├── wrangler.jsonc # Generated with your worker name, KV ID, D1 ID
|
|
48
|
+
├── wrangler.jsonc # Generated with your worker name and Durable Object config
|
|
50
49
|
├── wrangler.dev.jsonc # Local development config
|
|
51
50
|
└── package.json
|
|
52
51
|
```
|
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 {
|
|
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) =>
|
|
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
|
|
166
|
+
? answers.warpLinks
|
|
167
|
+
.split(",")
|
|
168
|
+
.map((u) => u.trim())
|
|
169
|
+
.filter(Boolean)
|
|
169
170
|
: [];
|
|
170
|
-
patchConfigTs(
|
|
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",
|
|
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.
|
|
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
|
}
|
|
@@ -5,12 +5,14 @@ on:
|
|
|
5
5
|
tags:
|
|
6
6
|
- "v*"
|
|
7
7
|
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
8
12
|
jobs:
|
|
9
13
|
publish:
|
|
10
14
|
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
contents: read
|
|
13
|
-
id-token: write
|
|
15
|
+
|
|
14
16
|
steps:
|
|
15
17
|
- uses: actions/checkout@v4
|
|
16
18
|
|
|
@@ -24,5 +26,5 @@ jobs:
|
|
|
24
26
|
working-directory: create-planet
|
|
25
27
|
|
|
26
28
|
- name: Sync template and publish
|
|
27
|
-
run: npm publish
|
|
29
|
+
run: npm publish
|
|
28
30
|
working-directory: create-planet
|
package/template/AGENTS.md
CHANGED
|
@@ -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
|
-
- **
|
|
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 (
|
|
30
|
+
### 2.2 Database Schema (Durable Object SQLite)
|
|
32
31
|
|
|
33
|
-
|
|
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,
|
|
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
|
|
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
|
|
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->>
|
|
19
|
-
|
|
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->>
|
|
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->>
|
|
34
|
-
|
|
31
|
+
Origin->>DO: Read Ed25519 identity keys
|
|
32
|
+
DO-->>Origin: Key pair
|
|
35
33
|
Origin->>Origin: Sign Plan (Ed25519)
|
|
36
|
-
Origin->>
|
|
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,
|
|
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->>
|
|
44
|
+
TCs->>DO: Read own identity keys
|
|
47
45
|
TCs->>TCs: Sign Plan
|
|
48
|
-
TCs->>
|
|
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,
|
|
57
|
-
Origin->>
|
|
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->>
|
|
62
|
+
Dest->>DO: Store plan (incoming traffic)
|
|
65
63
|
Dest-->>Origin: 200 OK
|
|
66
|
-
Origin->>
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
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`
|
|
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 →
|
|
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
|
|
114
|
-
(
|
|
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
|
|
119
|
-
Registered at destination
|
|
120
|
-
|
|
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
|
-
|
|
179
|
-
TEXT
|
|
180
|
-
TEXT
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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{
|
|
192
|
+
TRAVEL_PLANS ||--o{ CONSENSUS_PLANS : "built during PBFT"
|
|
194
193
|
TRAVEL_PLANS }o--o{ TRAFFIC_CONTROLLERS : "controllers elected from"
|
|
195
194
|
```
|
package/template/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@federated-planets/planet",
|
|
3
|
-
"version": "1.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
16
|
"test:warp-links": "npm run build && vitest run src/tests/warp-links.test.ts",
|
|
@@ -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,
|
package/template/src/env.d.ts
CHANGED
|
@@ -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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
111
|
+
* Saves plan state to DO SQLite for active consensus tracking
|
|
110
112
|
*/
|
|
111
|
-
static async savePlanState(
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
124
|
+
* Retrieves plan state from DO SQLite
|
|
119
125
|
*/
|
|
120
126
|
static async getPlanState(
|
|
121
|
-
|
|
127
|
+
TRAFFIC_CONTROL: DurableObjectNamespace,
|
|
122
128
|
planId: string,
|
|
123
129
|
): Promise<TravelPlan | null> {
|
|
124
|
-
const
|
|
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
|
|
6
|
+
* Retrieves or generates the planet's Ed25519 identity keys from DO storage.
|
|
6
7
|
*/
|
|
7
|
-
static async getIdentity(
|
|
8
|
+
static async getIdentity(TRAFFIC_CONTROL: DurableObjectNamespace): Promise<{
|
|
8
9
|
publicKey: CryptoKey;
|
|
9
10
|
privateKey: CryptoKey;
|
|
10
11
|
publicKeyBase64: string;
|
|
11
12
|
}> {
|
|
12
|
-
const
|
|
13
|
-
const existingPrivate = await KV.get("identity_private");
|
|
13
|
+
const result: any = await doStorage(TRAFFIC_CONTROL, "getIdentity");
|
|
14
14
|
|
|
15
|
-
if (
|
|
16
|
-
const publicKey = await CryptoCore.importKey(
|
|
17
|
-
const privateKey = await CryptoCore.importKey(
|
|
18
|
-
return { publicKey, privateKey, publicKeyBase64:
|
|
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
|
|
27
|
-
|
|
26
|
+
await doStorage(TRAFFIC_CONTROL, "setIdentity", {
|
|
27
|
+
publicKey: publicB64,
|
|
28
|
+
privateKey: privateB64,
|
|
29
|
+
});
|
|
28
30
|
|
|
29
31
|
return {
|
|
30
32
|
publicKey: keyPair.publicKey,
|