@swanlabs/veil-core 1.0.0

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/server.ts ADDED
@@ -0,0 +1,302 @@
1
+ /**
2
+ * VEIL CORE Server v1.0.0
3
+ * Swan Labs — Ambient Web Entity Runtime
4
+ *
5
+ * Runs the entity clock. Serves visitors. Streams live state.
6
+ * Deploy on any Node.js host — Fly.io, Railway, your own VPS.
7
+ * No cloud vendor lock-in. The protocol is yours.
8
+ *
9
+ * Install: npm install express ws cors
10
+ * Run: npx ts-node server.ts
11
+ */
12
+
13
+ import express, { Request, Response, NextFunction } from "express";
14
+ import { WebSocketServer, WebSocket } from "ws";
15
+ import cors from "cors";
16
+ import http from "http";
17
+ import {
18
+ createEntity,
19
+ createRelationship,
20
+ processVisit,
21
+ recordDwell,
22
+ tickEntity,
23
+ deriveMorphology,
24
+ deriveVID,
25
+ formatClockAge,
26
+ VEILEntity,
27
+ VEILRelationship,
28
+ } from "./veil-runtime.js";
29
+
30
+ // ─── In-memory store (replace with DB in production) ─────────────────────────
31
+
32
+ const entities = new Map<string, VEILEntity>();
33
+ const relationships = new Map<string, Map<string, VEILRelationship>>(); // entityId → vid → rel
34
+ const streamClients = new Map<string, Set<WebSocket>>(); // entityId → clients
35
+
36
+ // ─── Bootstrap: create the first entity (VEIL itself) ─────────────────────────
37
+
38
+ const VEIL_ENTITY = createEntity("VEIL", {
39
+ warmth: 0.35,
40
+ density: 0.2,
41
+ tension: 0.4,
42
+ });
43
+ entities.set(VEIL_ENTITY.id, VEIL_ENTITY);
44
+ relationships.set(VEIL_ENTITY.id, new Map());
45
+ console.log(`[VEIL CORE] Entity bootstrapped: ${VEIL_ENTITY.id}`);
46
+ console.log(`[VEIL CORE] AWE-001 online. Clock age: 0m`);
47
+
48
+ // ─── Entity Clock Scheduler ───────────────────────────────────────────────────
49
+ // This is what makes entities alive between visits.
50
+ // It runs regardless of whether anyone is connected.
51
+
52
+ setInterval(() => {
53
+ const now = Date.now();
54
+
55
+ entities.forEach((entity, entityId) => {
56
+ const rels = Array.from(relationships.get(entityId)?.values() ?? []);
57
+ const { entity: ticked, relationships: tickedRels } = tickEntity(entity, rels);
58
+
59
+ // Persist updated entity
60
+ entities.set(entityId, ticked);
61
+
62
+ // Persist updated relationships
63
+ const relMap = relationships.get(entityId);
64
+ if (relMap) {
65
+ tickedRels.forEach(rel => relMap.set(rel.vid, rel));
66
+ }
67
+
68
+ // Broadcast tick to all connected stream clients
69
+ const clients = streamClients.get(entityId);
70
+ if (clients && clients.size > 0) {
71
+ const message = JSON.stringify({
72
+ type: "tick",
73
+ entity_id: entityId,
74
+ timestamp: now,
75
+ payload: {
76
+ clock_age: ticked.clock_age,
77
+ clock_age_display: formatClockAge(ticked.clock_age),
78
+ axes: ticked.axes,
79
+ phase: ticked.phase,
80
+ },
81
+ });
82
+ clients.forEach(client => {
83
+ if (client.readyState === WebSocket.OPEN) {
84
+ client.send(message);
85
+ }
86
+ });
87
+ }
88
+ });
89
+ }, 60_000); // Tick every minute
90
+
91
+ // ─── Express App ──────────────────────────────────────────────────────────────
92
+
93
+ const app = express();
94
+ app.use(cors());
95
+ app.use(express.json());
96
+
97
+ // Auth middleware (simplified — production uses JWT)
98
+ function requireAuth(req: Request, res: Response, next: NextFunction) {
99
+ const key = req.headers["x-veil-admin-key"];
100
+ if (key !== process.env.VEIL_ADMIN_KEY) {
101
+ return res.status(401).json({ error: "Unauthorized" });
102
+ }
103
+ next();
104
+ }
105
+
106
+ // ─── Entity Endpoints ─────────────────────────────────────────────────────────
107
+
108
+ // GET /v1/entity/:id — fetch entity state
109
+ app.get("/v1/entity/:id", (req, res) => {
110
+ const entity = entities.get(req.params.id);
111
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
112
+ res.json({
113
+ entity,
114
+ clock_age_display: formatClockAge(entity.clock_age),
115
+ relationship_count: relationships.get(entity.id)?.size ?? 0,
116
+ });
117
+ });
118
+
119
+ // GET /v1/entity/:id/morphology — morphology for anonymous visitor
120
+ app.get("/v1/entity/:id/morphology", (req, res) => {
121
+ const entity = entities.get(req.params.id);
122
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
123
+ const vid = req.query.vid as string;
124
+ const rel = vid ? relationships.get(entity.id)?.get(vid) : null;
125
+ const tempRel = rel ?? createRelationship(entity.id, "anonymous");
126
+ res.json({ morphology: deriveMorphology(entity, tempRel) });
127
+ });
128
+
129
+ // POST /v1/entity/:id/visit — record a visit
130
+ app.post("/v1/entity/:id/visit", (req, res) => {
131
+ const entity = entities.get(req.params.id);
132
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
133
+
134
+ const { vid: rawVid, fingerprint_inputs, metadata } = req.body;
135
+
136
+ // Derive VID if not provided
137
+ const vid = rawVid ?? deriveVID(fingerprint_inputs ?? ["anonymous"]);
138
+
139
+ const relMap = relationships.get(entity.id) ?? new Map();
140
+ const existingRel = relMap.get(vid) ?? null;
141
+
142
+ const result = processVisit(entity, existingRel, vid);
143
+
144
+ // Persist
145
+ entities.set(entity.id, result.entity);
146
+ relMap.set(vid, result.relationship);
147
+ relationships.set(entity.id, relMap);
148
+
149
+ // Broadcast visitor join to stream clients
150
+ broadcastToEntity(entity.id, {
151
+ type: "visitor_join",
152
+ entity_id: entity.id,
153
+ timestamp: Date.now(),
154
+ payload: { vid, phase: result.relationship.phase, is_returning: result.is_returning },
155
+ });
156
+
157
+ res.json({
158
+ ...result,
159
+ vid,
160
+ clock_age_display: formatClockAge(result.entity.clock_age),
161
+ });
162
+ });
163
+
164
+ // POST /v1/entity/:id/dwell — record dwell time
165
+ app.post("/v1/entity/:id/dwell", (req, res) => {
166
+ const entity = entities.get(req.params.id);
167
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
168
+
169
+ const { vid, dwell_seconds } = req.body;
170
+ if (!vid || !dwell_seconds) return res.status(400).json({ error: "vid and dwell_seconds required" });
171
+
172
+ const relMap = relationships.get(entity.id);
173
+ const rel = relMap?.get(vid);
174
+ if (!rel) return res.status(404).json({ error: "Relationship not found" });
175
+
176
+ const updated = recordDwell(rel, dwell_seconds);
177
+ relMap!.set(vid, updated);
178
+
179
+ res.json({ relationship: updated, morphology: deriveMorphology(entity, updated) });
180
+ });
181
+
182
+ // GET /v1/relationship/:entityId/:vid
183
+ app.get("/v1/relationship/:entityId/:vid", (req, res) => {
184
+ const rel = relationships.get(req.params.entityId)?.get(req.params.vid);
185
+ if (!rel) return res.status(404).json({ error: "Relationship not found" });
186
+ const entity = entities.get(req.params.entityId);
187
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
188
+ res.json({ relationship: rel, morphology: deriveMorphology(entity, rel) });
189
+ });
190
+
191
+ // POST /v1/entity — create new entity (admin only)
192
+ app.post("/v1/entity", requireAuth, (req, res) => {
193
+ const { name, axes } = req.body;
194
+ if (!name) return res.status(400).json({ error: "name required" });
195
+ const entity = createEntity(name, axes);
196
+ entities.set(entity.id, entity);
197
+ relationships.set(entity.id, new Map());
198
+ console.log(`[VEIL CORE] New entity created: ${entity.id} (${name})`);
199
+ res.status(201).json({ entity });
200
+ });
201
+
202
+ // GET /v1/entities — list all entities (admin only)
203
+ app.get("/v1/entities", requireAuth, (req, res) => {
204
+ const list = Array.from(entities.values()).map(e => ({
205
+ id: e.id,
206
+ name: e.name,
207
+ clock_age: e.clock_age,
208
+ clock_age_display: formatClockAge(e.clock_age),
209
+ visitor_count: e.visitor_count,
210
+ phase: e.phase,
211
+ relationship_count: relationships.get(e.id)?.size ?? 0,
212
+ }));
213
+ res.json({ entities: list, count: list.length });
214
+ });
215
+
216
+ // GET /v1/health
217
+ app.get("/v1/health", (req, res) => {
218
+ const entity = entities.get(VEIL_ENTITY.id);
219
+ res.json({
220
+ status: "alive",
221
+ entity_count: entities.size,
222
+ total_relationships: Array.from(relationships.values()).reduce((s, m) => s + m.size, 0),
223
+ primary_entity_age: entity ? formatClockAge(entity.clock_age) : "unknown",
224
+ protocol: "VEIL CORE v1.0.0",
225
+ origin: "Swan Labs",
226
+ });
227
+ });
228
+
229
+ // ─── HTTP + WebSocket Server ──────────────────────────────────────────────────
230
+
231
+ const server = http.createServer(app);
232
+ const wss = new WebSocketServer({ server, path: "/v1/stream" });
233
+
234
+ wss.on("connection", (ws, req) => {
235
+ // Parse entity ID from query string: /v1/stream?entity_id=...
236
+ const url = new URL(req.url ?? "", `http://${req.headers.host}`);
237
+ const entityId = url.searchParams.get("entity_id");
238
+ if (!entityId || !entities.has(entityId)) {
239
+ ws.close(1008, "Invalid entity_id");
240
+ return;
241
+ }
242
+
243
+ // Register client
244
+ if (!streamClients.has(entityId)) streamClients.set(entityId, new Set());
245
+ streamClients.get(entityId)!.add(ws);
246
+
247
+ console.log(`[VEIL SYNC] Stream client connected to ${entityId}. Total: ${streamClients.get(entityId)!.size}`);
248
+
249
+ // Send current state immediately
250
+ const entity = entities.get(entityId)!;
251
+ ws.send(JSON.stringify({
252
+ type: "connected",
253
+ entity_id: entityId,
254
+ timestamp: Date.now(),
255
+ payload: {
256
+ clock_age: entity.clock_age,
257
+ clock_age_display: formatClockAge(entity.clock_age),
258
+ axes: entity.axes,
259
+ phase: entity.phase,
260
+ visitor_count: entity.visitor_count,
261
+ },
262
+ }));
263
+
264
+ ws.on("close", () => {
265
+ streamClients.get(entityId)?.delete(ws);
266
+ console.log(`[VEIL SYNC] Stream client disconnected from ${entityId}`);
267
+ });
268
+
269
+ ws.on("error", (err) => {
270
+ console.error(`[VEIL SYNC] Stream error:`, err.message);
271
+ streamClients.get(entityId)?.delete(ws);
272
+ });
273
+ });
274
+
275
+ function broadcastToEntity(entityId: string, message: object) {
276
+ const clients = streamClients.get(entityId);
277
+ if (!clients) return;
278
+ const payload = JSON.stringify(message);
279
+ clients.forEach(client => {
280
+ if (client.readyState === WebSocket.OPEN) client.send(payload);
281
+ });
282
+ }
283
+
284
+ // ─── Start ────────────────────────────────────────────────────────────────────
285
+
286
+ const PORT = process.env.PORT ?? 3000;
287
+ server.listen(PORT, () => {
288
+ console.log(`\n╔════════════════════════════════════════╗`);
289
+ console.log(`║ VEIL CORE Runtime v1.0.0 ║`);
290
+ console.log(`║ Swan Labs — Ambient Web Entity Engine ║`);
291
+ console.log(`╚════════════════════════════════════════╝`);
292
+ console.log(`\nRunning on http://localhost:${PORT}`);
293
+ console.log(`Primary entity: ${VEIL_ENTITY.id}`);
294
+ console.log(`Protocol: VEIL CORE v1.0.0`);
295
+ console.log(`\nEndpoints:`);
296
+ console.log(` GET /v1/health`);
297
+ console.log(` GET /v1/entity/:id`);
298
+ console.log(` POST /v1/entity/:id/visit`);
299
+ console.log(` POST /v1/entity/:id/dwell`);
300
+ console.log(` WS /v1/stream?entity_id=:id`);
301
+ console.log(`\nThe entity is alive. Clock ticking.\n`);
302
+ });