Skip to content

Commit d40a522

Browse files
feat(sim-backend): lock v1 decisions + scaffold Postgres + sync docs
Lock v1 constants (Postgres + RPC + im_online eligibility; era length is off-chain) and update simulation docs to reflect current repo state Add API contract reference (vortex-simulation-api-contract.md) + DTO types (api.ts) to freeze backend/frontend payload shapes Add Postgres/Drizzle scaffolding: schema.ts, drizzle.config.ts, initial migration under db/migrations/, and seed script db-seed.ts (+ yarn scripts) Add tests for DB scaffolding (migrations.test.js) and deterministic/JSON-safe seed payloads (db-seed.test.js) Adjust mock module exports/imports for Node/seed compatibility (proposal page getters + explicit .ts extensions)
1 parent bb8807c commit d40a522

20 files changed

Lines changed: 2068 additions & 58 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
CREATE TABLE "clock_state" (
2+
"id" integer PRIMARY KEY NOT NULL,
3+
"current_era" integer DEFAULT 0 NOT NULL,
4+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
5+
);
6+
--> statement-breakpoint
7+
CREATE TABLE "eligibility_cache" (
8+
"address" text PRIMARY KEY NOT NULL,
9+
"is_active_human_node" integer NOT NULL,
10+
"checked_at" timestamp with time zone DEFAULT now() NOT NULL,
11+
"source" text DEFAULT 'rpc' NOT NULL,
12+
"expires_at" timestamp with time zone NOT NULL,
13+
"reason_code" text
14+
);
15+
--> statement-breakpoint
16+
CREATE TABLE "read_models" (
17+
"key" text PRIMARY KEY NOT NULL,
18+
"payload" jsonb NOT NULL,
19+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
20+
);
21+
--> statement-breakpoint
22+
CREATE TABLE "users" (
23+
"address" text PRIMARY KEY NOT NULL,
24+
"display_name" text,
25+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
26+
);
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
{
2+
"id": "1416fd76-c86c-4384-90bd-43856c9d4db3",
3+
"prevId": "00000000-0000-0000-0000-000000000000",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.clock_state": {
8+
"name": "clock_state",
9+
"schema": "",
10+
"columns": {
11+
"id": {
12+
"name": "id",
13+
"type": "integer",
14+
"primaryKey": true,
15+
"notNull": true
16+
},
17+
"current_era": {
18+
"name": "current_era",
19+
"type": "integer",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"default": 0
23+
},
24+
"updated_at": {
25+
"name": "updated_at",
26+
"type": "timestamp with time zone",
27+
"primaryKey": false,
28+
"notNull": true,
29+
"default": "now()"
30+
}
31+
},
32+
"indexes": {},
33+
"foreignKeys": {},
34+
"compositePrimaryKeys": {},
35+
"uniqueConstraints": {},
36+
"policies": {},
37+
"checkConstraints": {},
38+
"isRLSEnabled": false
39+
},
40+
"public.eligibility_cache": {
41+
"name": "eligibility_cache",
42+
"schema": "",
43+
"columns": {
44+
"address": {
45+
"name": "address",
46+
"type": "text",
47+
"primaryKey": true,
48+
"notNull": true
49+
},
50+
"is_active_human_node": {
51+
"name": "is_active_human_node",
52+
"type": "integer",
53+
"primaryKey": false,
54+
"notNull": true
55+
},
56+
"checked_at": {
57+
"name": "checked_at",
58+
"type": "timestamp with time zone",
59+
"primaryKey": false,
60+
"notNull": true,
61+
"default": "now()"
62+
},
63+
"source": {
64+
"name": "source",
65+
"type": "text",
66+
"primaryKey": false,
67+
"notNull": true,
68+
"default": "'rpc'"
69+
},
70+
"expires_at": {
71+
"name": "expires_at",
72+
"type": "timestamp with time zone",
73+
"primaryKey": false,
74+
"notNull": true
75+
},
76+
"reason_code": {
77+
"name": "reason_code",
78+
"type": "text",
79+
"primaryKey": false,
80+
"notNull": false
81+
}
82+
},
83+
"indexes": {},
84+
"foreignKeys": {},
85+
"compositePrimaryKeys": {},
86+
"uniqueConstraints": {},
87+
"policies": {},
88+
"checkConstraints": {},
89+
"isRLSEnabled": false
90+
},
91+
"public.read_models": {
92+
"name": "read_models",
93+
"schema": "",
94+
"columns": {
95+
"key": {
96+
"name": "key",
97+
"type": "text",
98+
"primaryKey": true,
99+
"notNull": true
100+
},
101+
"payload": {
102+
"name": "payload",
103+
"type": "jsonb",
104+
"primaryKey": false,
105+
"notNull": true
106+
},
107+
"updated_at": {
108+
"name": "updated_at",
109+
"type": "timestamp with time zone",
110+
"primaryKey": false,
111+
"notNull": true,
112+
"default": "now()"
113+
}
114+
},
115+
"indexes": {},
116+
"foreignKeys": {},
117+
"compositePrimaryKeys": {},
118+
"uniqueConstraints": {},
119+
"policies": {},
120+
"checkConstraints": {},
121+
"isRLSEnabled": false
122+
},
123+
"public.users": {
124+
"name": "users",
125+
"schema": "",
126+
"columns": {
127+
"address": {
128+
"name": "address",
129+
"type": "text",
130+
"primaryKey": true,
131+
"notNull": true
132+
},
133+
"display_name": {
134+
"name": "display_name",
135+
"type": "text",
136+
"primaryKey": false,
137+
"notNull": false
138+
},
139+
"created_at": {
140+
"name": "created_at",
141+
"type": "timestamp with time zone",
142+
"primaryKey": false,
143+
"notNull": true,
144+
"default": "now()"
145+
}
146+
},
147+
"indexes": {},
148+
"foreignKeys": {},
149+
"compositePrimaryKeys": {},
150+
"uniqueConstraints": {},
151+
"policies": {},
152+
"checkConstraints": {},
153+
"isRLSEnabled": false
154+
}
155+
},
156+
"enums": {},
157+
"schemas": {},
158+
"sequences": {},
159+
"roles": {},
160+
"policies": {},
161+
"views": {},
162+
"_meta": {
163+
"columns": {},
164+
"schemas": {},
165+
"tables": {}
166+
}
167+
}

db/migrations/meta/_journal.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "7",
3+
"dialect": "postgresql",
4+
"entries": [
5+
{
6+
"idx": 0,
7+
"version": "7",
8+
"when": 1766509637223,
9+
"tag": "0000_nosy_mastermind",
10+
"breakpoints": true
11+
}
12+
]
13+
}

db/schema.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2+
3+
export const users = pgTable("users", {
4+
address: text("address").primaryKey(),
5+
displayName: text("display_name"),
6+
createdAt: timestamp("created_at", { withTimezone: true })
7+
.notNull()
8+
.defaultNow(),
9+
});
10+
11+
export const eligibilityCache = pgTable("eligibility_cache", {
12+
address: text("address").primaryKey(),
13+
isActiveHumanNode: integer("is_active_human_node").notNull(), // 0/1 for portability
14+
checkedAt: timestamp("checked_at", { withTimezone: true })
15+
.notNull()
16+
.defaultNow(),
17+
source: text("source").notNull().default("rpc"),
18+
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
19+
reasonCode: text("reason_code"),
20+
});
21+
22+
export const clockState = pgTable("clock_state", {
23+
id: integer("id").primaryKey(),
24+
currentEra: integer("current_era").notNull().default(0),
25+
updatedAt: timestamp("updated_at", { withTimezone: true })
26+
.notNull()
27+
.defaultNow(),
28+
});
29+
30+
// Temporary storage for mock-equivalent page payloads during Phase 4 migration.
31+
// This lets us seed from `src/data/mock/*` while we build normalized tables + event log.
32+
export const readModels = pgTable("read_models", {
33+
key: text("key").primaryKey(),
34+
payload: jsonb("payload").notNull(),
35+
updatedAt: timestamp("updated_at", { withTimezone: true })
36+
.notNull()
37+
.defaultNow(),
38+
});

0 commit comments

Comments
 (0)