@wenathlan/getry 2.0.167 → 2.0.168

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/cli.ts CHANGED
@@ -791,9 +791,18 @@ async function cmdmodels(versionarg: string | undefined): Promise<void> {
791
791
  // ---------------------------------------------------------------------------
792
792
 
793
793
  async function cmdserve(portarg: string | undefined): Promise<void> {
794
- const port = Number(portarg) || Number(process.env.PORT) || 3001;
795
- process.env.PORT = String(port);
796
- console.log(`${colors.cyan}starting gateway on port ${port}${colors.reset}`);
794
+ /* the gateway ports law (host/porta sorteados, never localhost literal):
795
+ * the caller pin rides when it carries a positive port (the arg lands in
796
+ * the env the transport main reads), the env pin rides when the env
797
+ * carries one, and when both stay silent the transport main draws the
798
+ * host and the port (binddraw.ts) — never a literal fallback */
799
+ const argport = Number(portarg);
800
+ if (portarg !== undefined && Number.isFinite(argport) && argport > 0) process.env.PORT = String(argport);
801
+ const pinned = Number(process.env.PORT);
802
+ const pinnedport = Number.isFinite(pinned) && pinned > 0 ? pinned : undefined;
803
+ console.log(
804
+ `${colors.cyan}starting gateway${pinnedport !== undefined ? ` on port ${pinnedport}` : " — the bind draws (host and port) since no env pin rides"}${colors.reset}`,
805
+ );
797
806
  /* the transport main loads lazily (the published-envelope law): the
798
807
  * console family — init through export, chat, help — runs on node
799
808
  * builtins alone inside the published envelope, and the serve seat
package/db.ts CHANGED
@@ -20,6 +20,7 @@
20
20
 
21
21
  /// <reference types="better-sqlite3" />
22
22
 
23
+ import { createOnboardingStore, type OnboardingLane, type OnboardingStore } from "@wenathlan/vault/onboardingstore";
23
24
  import { GATEWAYROUTES, GATEWAYVERSIONS, type GatewayRoute, type GatewayVersion } from "./gateway";
24
25
  import { LABEL_MANIFEST, LABEL_REVISION } from "./labels";
25
26
 
@@ -399,12 +400,6 @@ CREATE TABLE IF NOT EXISTS gateway_versions (
399
400
  );
400
401
  `;
401
402
 
402
- /** the prepared statements of the mirror, created once per process. */
403
- type OnboardingStatements = {
404
- select: import("better-sqlite3").Statement;
405
- upsert: import("better-sqlite3").Statement;
406
- };
407
-
408
403
  /** the prepared label statements of the mirror, created once per process. */
409
404
  type LabelStatements = {
410
405
  select: import("better-sqlite3").Statement;
@@ -421,7 +416,7 @@ type VersionStatements = {
421
416
  /** the cached driver bundle (null until the first node call). */
422
417
  let mirror: {
423
418
  db: import("better-sqlite3").Database;
424
- statements: OnboardingStatements;
419
+ onboarding: OnboardingStore;
425
420
  labels: LabelStatements;
426
421
  versions: VersionStatements;
427
422
  } | null = null;
@@ -437,7 +432,7 @@ let mirror: {
437
432
  */
438
433
  async function openMirror(): Promise<{
439
434
  db: import("better-sqlite3").Database;
440
- statements: OnboardingStatements;
435
+ onboarding: OnboardingStore;
441
436
  labels: LabelStatements;
442
437
  versions: VersionStatements;
443
438
  }> {
@@ -454,21 +449,31 @@ async function openMirror(): Promise<{
454
449
  db.exec(ONBOARDING_SCHEMA);
455
450
  db.exec(LABELS_SCHEMA);
456
451
  db.exec(GATEWAYVERSIONS_SCHEMA);
452
+ /* the onboarding store rides the vault factory over the mirror's own
453
+ * connection (the structural lane — execute, queryOne, exec — answered
454
+ * through one memoized prepare cache, so every statement stays prepared
455
+ * once per process): the seen-wins upsert —
456
+ * ON CONFLICT(visitor) DO UPDATE SET introseen = MAX(introseen, excluded.introseen),
457
+ * onboardingseen = MAX(onboardingseen, excluded.onboardingseen) — rides
458
+ * the shared mechanism, never a local copy of the sql, and the browser
459
+ * graph never gains a storage import — the factory module owns zero
460
+ * imports (the labelstore precedent). */
461
+ const prepared = new Map<string, import("better-sqlite3").Statement>();
462
+ const prepareCached = (sql: string): import("better-sqlite3").Statement => {
463
+ const cached = prepared.get(sql);
464
+ if (cached) return cached;
465
+ const statement = db.prepare(sql);
466
+ prepared.set(sql, statement);
467
+ return statement;
468
+ };
469
+ const onboardinglane: OnboardingLane = {
470
+ execute: (sql, params = []) => prepareCached(sql).run(...params),
471
+ queryOne: (sql, params = []) => prepareCached(sql).get(...params),
472
+ exec: (ddl) => db.exec(ddl),
473
+ };
457
474
  mirror = {
458
475
  db,
459
- statements: {
460
- select: db.prepare(
461
- "SELECT id, visitor, introseen, onboardingseen, updatedAt FROM onboarding_states WHERE visitor = ?",
462
- ),
463
- upsert: db.prepare(
464
- `INSERT INTO onboarding_states (visitor, introseen, onboardingseen, updatedAt)
465
- VALUES (?, ?, ?, datetime('now'))
466
- ON CONFLICT(visitor) DO UPDATE SET
467
- introseen = MAX(introseen, excluded.introseen),
468
- onboardingseen = MAX(onboardingseen, excluded.onboardingseen),
469
- updatedAt = datetime('now')`,
470
- ),
471
- },
476
+ onboarding: createOnboardingStore(onboardinglane),
472
477
  labels: {
473
478
  select: db.prepare("SELECT id, text, revision, updatedAt, translated FROM labels WHERE id = ?"),
474
479
  selectAll: db.prepare("SELECT id, text, revision, updatedAt, translated FROM labels ORDER BY id ASC"),
@@ -514,15 +519,14 @@ export type OnboardingFlags = { introseen: boolean; onboardingseen: boolean };
514
519
  */
515
520
  export async function getonboardingstate(visitor: string): Promise<OnboardingFlags | undefined> {
516
521
  if (!isnode()) return undefined;
517
- const { statements } = await openMirror();
518
- const row = statements.select.get(visitor) as { introseen: number; onboardingseen: number } | undefined;
519
- if (!row) return undefined;
520
- return { introseen: row.introseen === 1, onboardingseen: row.onboardingseen === 1 };
522
+ const { onboarding } = await openMirror();
523
+ return onboarding.getOnboardingState(visitor);
521
524
  }
522
525
 
523
526
  /**
524
- * Marks one onboarding stage seen for a visitor — idempotent seen-wins
525
- * upsert (a stage seen anywhere stays seen), prepared and bound, never
527
+ * Marks one onboarding stage seen for a visitor — the idempotent seen-wins
528
+ * upsert of the vault store (a stage seen anywhere stays seen; the later
529
+ * stage implies the earlier — the flow order), prepared and bound, never
526
530
  * concatenated SQL.
527
531
  *
528
532
  * @param visitor the visitor key of the row.
@@ -531,8 +535,8 @@ export async function getonboardingstate(visitor: string): Promise<OnboardingFla
531
535
  */
532
536
  export async function setonboardingstage(visitor: string, stage: "intro" | "onboarding"): Promise<OnboardingFlags> {
533
537
  if (!isnode()) return { introseen: false, onboardingseen: false };
534
- const { statements } = await openMirror();
535
- statements.upsert.run(visitor, stage === "intro" ? 1 : 0, stage === "onboarding" ? 1 : 0);
538
+ const { onboarding } = await openMirror();
539
+ onboarding.setOnboardingStage(visitor, stage);
536
540
  return (await getonboardingstate(visitor)) ?? { introseen: false, onboardingseen: false };
537
541
  }
538
542
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenathlan/getry",
3
- "version": "2.0.167",
3
+ "version": "2.0.168",
4
4
  "private": false,
5
5
  "description": "getry is the AI gateway of the wenathlan family: five provider gateways (v1 zai, v2 babel, v3 nvidia, v4 opencode+kilo, v5 openrouter) serving 35 OpenAI-compatible routes with the 7-level thinking system, the session store and the key rotation, described by schema.prisma and served as one static Sol surface. The envelope distributes the documentation set and the tests directory as one package.",
6
6
  "license": "GPL-3.0-only",
@@ -16,9 +16,9 @@
16
16
  "restore:entry": "node -e \"const{readFileSync}=require('node:fs');const{execSync}=require('node:child_process');const script=/script[^>]*App\\.tsx/;const triad=/family=Bricolage\\+Grotesque[\\s\\S]*Outfit[\\s\\S]*JetBrains\\+Mono/;const face=(h)=>script.test(h)&&triad.test(h);if(face(readFileSync('index.html','utf8'))){console.log('index.html already rides the source face');process.exit(0)}if(!face(execSync('git show HEAD:./index.html',{encoding:'utf8'}))){console.error('refusing: HEAD index.html is not the canonical source face yet (script App.tsx + the font triad) - land the source-face fix first; nothing was touched');process.exit(1)}execSync('git checkout -- index.html',{stdio:'inherit'});console.log('index.html restored from HEAD (the source face)')\""
17
17
  },
18
18
  "dependencies": {
19
- "@ai-sdk/openai": "^4.0.110",
20
- "@ai-sdk/openai-compatible": "^3.0.86",
21
- "@ai-sdk/provider-utils": "^5.0.75",
19
+ "@ai-sdk/openai": "^4.0.111",
20
+ "@ai-sdk/openai-compatible": "^3.0.87",
21
+ "@ai-sdk/provider-utils": "^5.0.76",
22
22
  "@anthropic-ai/sdk": "^1.6.0",
23
23
  "@biomejs/biome": "^2.6.6",
24
24
  "@capacitor/android": "^8.6.0",
@@ -88,15 +88,15 @@
88
88
  "@types/react-dom": "^19.3.0",
89
89
  "@types/three": "^0.187.0",
90
90
  "@vitejs/plugin-react": "^6.2.1",
91
- "@wenathlan/argan": "~2.0.166",
92
- "@wenathlan/cadria": "~2.0.166",
93
- "@wenathlan/debonair": "~2.0.166",
94
- "@wenathlan/devthink": "~2.0.166",
95
- "@wenathlan/getry": "~2.0.166",
96
- "@wenathlan/saddle": "~2.0.166",
97
- "@wenathlan/vault": "~2.0.166",
91
+ "@wenathlan/argan": "~2.0.167",
92
+ "@wenathlan/cadria": "~2.0.167",
93
+ "@wenathlan/debonair": "~2.0.167",
94
+ "@wenathlan/devthink": "~2.0.167",
95
+ "@wenathlan/getry": "~2.0.167",
96
+ "@wenathlan/saddle": "~2.0.167",
97
+ "@wenathlan/vault": "~2.0.167",
98
98
  "add": "^2.0.9",
99
- "ai": "^7.0.164",
99
+ "ai": "^7.0.165",
100
100
  "autoprefixer": "^10.6.2",
101
101
  "axios": "^1.21.0",
102
102
  "baseline-browser-mapping": "^2.11.35",
@@ -107,7 +107,7 @@
107
107
  "class-variance-authority": "^0.7.1",
108
108
  "clsx": "^2.1.1",
109
109
  "cmdk": "^1.1.1",
110
- "commander": "^15.0.0",
110
+ "commander": "^15.1.0",
111
111
  "date-fns": "^4.4.0",
112
112
  "deepmerge-ts": "^8.0.2",
113
113
  "dockerode": "^5.0.1",
@@ -125,13 +125,12 @@
125
125
  "framer-motion": "^14.10.1",
126
126
  "glob": "^14.0.4",
127
127
  "gsap": "^3.15.1",
128
- "hono": "^4.13.16",
128
+ "hono": "^4.13.17",
129
129
  "ink": "^8.0.0",
130
130
  "input-otp": "^1.5.0",
131
131
  "js-yaml": "^5.5.0",
132
132
  "jsdom": "^31.0.0",
133
133
  "katex": "^0.19.1",
134
- "lucide-react": "^1.70.0",
135
134
  "minimatch": "^11.0.5",
136
135
  "motion": "^14.10.1",
137
136
  "mysql2": "^3.24.6",
@@ -190,9 +189,9 @@
190
189
  "zustand": "^5.1.0"
191
190
  },
192
191
  "devDependencies": {
193
- "@ai-sdk/openai": "^4.0.110",
194
- "@ai-sdk/openai-compatible": "^3.0.86",
195
- "@ai-sdk/provider-utils": "^5.0.75",
192
+ "@ai-sdk/openai": "^4.0.111",
193
+ "@ai-sdk/openai-compatible": "^3.0.87",
194
+ "@ai-sdk/provider-utils": "^5.0.76",
196
195
  "@anthropic-ai/sdk": "^1.6.0",
197
196
  "@biomejs/biome": "^2.6.6",
198
197
  "@capacitor/android": "^8.6.0",
@@ -262,15 +261,15 @@
262
261
  "@types/react-dom": "^19.3.0",
263
262
  "@types/three": "^0.187.0",
264
263
  "@vitejs/plugin-react": "^6.2.1",
265
- "@wenathlan/argan": "~2.0.166",
266
- "@wenathlan/cadria": "~2.0.166",
267
- "@wenathlan/debonair": "~2.0.166",
268
- "@wenathlan/devthink": "~2.0.166",
269
- "@wenathlan/getry": "~2.0.166",
270
- "@wenathlan/saddle": "~2.0.166",
271
- "@wenathlan/vault": "~2.0.166",
264
+ "@wenathlan/argan": "~2.0.167",
265
+ "@wenathlan/cadria": "~2.0.167",
266
+ "@wenathlan/debonair": "~2.0.167",
267
+ "@wenathlan/devthink": "~2.0.167",
268
+ "@wenathlan/getry": "~2.0.167",
269
+ "@wenathlan/saddle": "~2.0.167",
270
+ "@wenathlan/vault": "~2.0.167",
272
271
  "add": "^2.0.9",
273
- "ai": "^7.0.164",
272
+ "ai": "^7.0.165",
274
273
  "autoprefixer": "^10.6.2",
275
274
  "axios": "^1.21.0",
276
275
  "baseline-browser-mapping": "^2.11.35",
@@ -281,7 +280,7 @@
281
280
  "class-variance-authority": "^0.7.1",
282
281
  "clsx": "^2.1.1",
283
282
  "cmdk": "^1.1.1",
284
- "commander": "^15.0.0",
283
+ "commander": "^15.1.0",
285
284
  "date-fns": "^4.4.0",
286
285
  "deepmerge-ts": "^8.0.2",
287
286
  "dockerode": "^5.0.1",
@@ -299,13 +298,12 @@
299
298
  "framer-motion": "^14.10.1",
300
299
  "glob": "^14.0.4",
301
300
  "gsap": "^3.15.1",
302
- "hono": "^4.13.16",
301
+ "hono": "^4.13.17",
303
302
  "ink": "^8.0.0",
304
303
  "input-otp": "^1.5.0",
305
304
  "js-yaml": "^5.5.0",
306
305
  "jsdom": "^31.0.0",
307
306
  "katex": "^0.19.1",
308
- "lucide-react": "^1.70.0",
309
307
  "minimatch": "^11.0.5",
310
308
  "motion": "^14.10.1",
311
309
  "mysql2": "^3.24.6",
@@ -364,9 +362,9 @@
364
362
  "zustand": "^5.1.0"
365
363
  },
366
364
  "overrides": {
367
- "@ai-sdk/openai": "^4.0.110",
368
- "@ai-sdk/openai-compatible": "^3.0.86",
369
- "@ai-sdk/provider-utils": "^5.0.75",
365
+ "@ai-sdk/openai": "^4.0.111",
366
+ "@ai-sdk/openai-compatible": "^3.0.87",
367
+ "@ai-sdk/provider-utils": "^5.0.76",
370
368
  "@anthropic-ai/sdk": "^1.6.0",
371
369
  "@biomejs/biome": "^2.6.6",
372
370
  "@capacitor/android": "^8.6.0",
@@ -436,15 +434,15 @@
436
434
  "@types/react-dom": "^19.3.0",
437
435
  "@types/three": "^0.187.0",
438
436
  "@vitejs/plugin-react": "^6.2.1",
439
- "@wenathlan/argan": "~2.0.166",
440
- "@wenathlan/cadria": "~2.0.166",
441
- "@wenathlan/debonair": "~2.0.166",
442
- "@wenathlan/devthink": "~2.0.166",
443
- "@wenathlan/getry": "~2.0.166",
444
- "@wenathlan/saddle": "~2.0.166",
445
- "@wenathlan/vault": "~2.0.166",
437
+ "@wenathlan/argan": "~2.0.167",
438
+ "@wenathlan/cadria": "~2.0.167",
439
+ "@wenathlan/debonair": "~2.0.167",
440
+ "@wenathlan/devthink": "~2.0.167",
441
+ "@wenathlan/getry": "~2.0.167",
442
+ "@wenathlan/saddle": "~2.0.167",
443
+ "@wenathlan/vault": "~2.0.167",
446
444
  "add": "^2.0.9",
447
- "ai": "^7.0.164",
445
+ "ai": "^7.0.165",
448
446
  "autoprefixer": "^10.6.2",
449
447
  "axios": "^1.21.0",
450
448
  "baseline-browser-mapping": "^2.11.35",
@@ -455,7 +453,7 @@
455
453
  "class-variance-authority": "^0.7.1",
456
454
  "clsx": "^2.1.1",
457
455
  "cmdk": "^1.1.1",
458
- "commander": "^15.0.0",
456
+ "commander": "^15.1.0",
459
457
  "date-fns": "^4.4.0",
460
458
  "deepmerge-ts": "^8.0.2",
461
459
  "dockerode": "^5.0.1",
@@ -473,13 +471,12 @@
473
471
  "framer-motion": "^14.10.1",
474
472
  "glob": "^14.0.4",
475
473
  "gsap": "^3.15.1",
476
- "hono": "^4.13.16",
474
+ "hono": "^4.13.17",
477
475
  "ink": "^8.0.0",
478
476
  "input-otp": "^1.5.0",
479
477
  "js-yaml": "^5.5.0",
480
478
  "jsdom": "^31.0.0",
481
479
  "katex": "^0.19.1",
482
- "lucide-react": "^1.70.0",
483
480
  "minimatch": "^11.0.5",
484
481
  "motion": "^14.10.1",
485
482
  "mysql2": "^3.24.6",
package/server.ts CHANGED
@@ -61,6 +61,7 @@ import { createversion } from "@wenathlan/getry/engine";
61
61
  import type { gatewayconfig, gatewaydefinition } from "@wenathlan/getry/types";
62
62
  import { corsheaders, ctndjson, ctsse, safejsonparse, safestringify } from "@wenathlan/getry/utils";
63
63
  import { GATEWAYROUTES } from "./gateway.js";
64
+ import { resolvegatewaybind } from "./binddraw.js";
64
65
 
65
66
  // ---------------------------------------------------------------------------
66
67
  // sse transport — the stream pipeline of the gateway http transport
@@ -620,8 +621,12 @@ function consoleroots(): string[] {
620
621
  return roots;
621
622
  }
622
623
 
623
- /** main — bootstrap the server on the configured port
624
- * host and port honor env — never hardcoded per open infrastructure rules */
624
+ /** main — bootstrap the server on the drawn bind
625
+ * the gateway ports law (host/porta sorteados, never localhost literal):
626
+ * the env pin rides when the env carries one, otherwise the host and the
627
+ * port draw — the port inside the documented cryptographic band
628
+ * 30000..59999, the host inside the loopback band with drawn octets —
629
+ * never a literal fallback (the drawn bind grammar rides binddraw.ts) */
625
630
  async function main(): Promise<void> {
626
631
  const app = await createserver();
627
632
  const definition = await loadconfig();
@@ -635,9 +640,8 @@ async function main(): Promise<void> {
635
640
  console.error("[server] unhandled rejection:", reason);
636
641
  });
637
642
 
638
- const port = Number(process.env.PORT) || 3001;
639
- const hostname = process.env.HOST || "0.0.0.0";
640
- serve({ fetch: app.fetch, port, hostname }, (info: { address: string; port: number }) => {
643
+ const bind = resolvegatewaybind(process.env.PORT, process.env.HOST);
644
+ serve({ fetch: app.fetch, port: bind.port, hostname: bind.hostname }, (info: { address: string; port: number }) => {
641
645
  console.log(`[server] gateway listening on http://${info.address}:${info.port}`);
642
646
  console.log(`[server] config loaded — ${versioncount} versions`);
643
647
  });
@@ -197,9 +197,9 @@ describe("flow contract — intro → onboarding → the platform", () => {
197
197
 
198
198
  it("gates the home anchor on the seen state before anything shows", () => {
199
199
  /* the widget surface law: the dotted widget name is written exactly
200
- once (inside sol/wingets/index.ts) and the page answers the surface
201
- import with its canonical alias — never the dotted path again */
202
- expect(HOME).toMatch(/import \{ HomeEntrygate as EntryGate \} from "\.\.\/wingets"/);
200
+ once (inside wingets.ts at the app root) and the page answers the
201
+ surface import with its canonical alias — never the dotted path again */
202
+ expect(HOME).toMatch(/import \{ HomeEntrygate as EntryGate \} from "\.\.\/\.\.\/wingets"/);
203
203
  expect(GATE).toContain('navigate("/intro", { replace: true })');
204
204
  expect(GATE).toContain('navigate("/onboarding", { replace: true })');
205
205
  expect(GATE).toContain("flowthrough");
@@ -0,0 +1,220 @@
1
+ // # familyseats.test — the two family seats battery (runnable with the
2
+ // vitest runner):
3
+ // pnpm vitest run test/familyseats.test.ts
4
+ //
5
+ // The laws the battery pins (the owner's rules, the two gateway/account
6
+ // lane seats):
7
+ // 1. the gateway ports law (law 39 — "Gateway proxy ports: host/porta
8
+ // sorteados, never localhost literal", beside the anti localhost law
9
+ // the corpus records — "random host octets, port drawn from the
10
+ // cryptographic band 30000 to 59999"): the gateway boot never answers
11
+ // a literal host or port — the env pin rides when the env carries one,
12
+ // otherwise the port draws inside the documented cryptographic band
13
+ // (30000..59999, the saddle orchestrator grammar crypto.randomInt(30000)
14
+ // + 30000) and the host draws inside the loopback band 127.x.y.z with
15
+ // three drawn octets; the retired literal fallbacks (the || 3001 chain,
16
+ // the "0.0.0.0" host) answer nowhere in the gateway lane sources;
17
+ // 2. the one signup law (law 40 — "One signup family-wide (guest identity
18
+ // never deleted)", beside the family sharing law the corpus records —
19
+ // "one signup and one account recognized in every app... the guest
20
+ // identity born on the first visit and never deleted"): the family
21
+ // carries ONE signup lane (the vault person's door, POST
22
+ // /api/v1/auth/signup over signupAccount in vault/account.ts) and the
23
+ // visitor identity rows ride zero delete lanes family-wide — the
24
+ // onboarding lanes upsert seen-wins (ON CONFLICT(visitor) DO UPDATE)
25
+ // and no sweep, retention or cleanup ever deletes the guest.
26
+ import { readdirSync, readFileSync, statSync } from "node:fs";
27
+ import { dirname, join } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { describe, expect, it } from "vitest";
30
+ import { drawgatewayhost, drawgatewayport, gatewayportband, resolvegatewaybind } from "../binddraw.ts";
31
+
32
+ /** the monorepo root resolved from this test file location. */
33
+ const monoreporoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
34
+
35
+ /** the family app roots (the deploy units the law names). */
36
+ const FAMILYROOTS = [
37
+ "argan",
38
+ "cadria",
39
+ "cobaia",
40
+ "debonair",
41
+ "devthink",
42
+ "forge",
43
+ "foundry",
44
+ "getry",
45
+ "saddle",
46
+ "stealthhead",
47
+ "vault",
48
+ ];
49
+
50
+ /** the walks the source scan skips (the tests-are-exempt law rides the
51
+ * test fixtures, the build ghosts never ride the tree). */
52
+ const SKIPNAMES = new Set(["node_modules", "dist", "build", "test", "tests", ".git"]);
53
+
54
+ /** walks every .ts/.tsx source under one app root (skips the exempt lanes). */
55
+ function walksources(relroot: string): string[] {
56
+ const out: string[] = [];
57
+ const walk = (absdir: string): void => {
58
+ for (const entry of readdirSync(absdir)) {
59
+ const abs = join(absdir, entry);
60
+ const st = statSync(abs);
61
+ if (st.isDirectory()) {
62
+ if (SKIPNAMES.has(entry)) continue;
63
+ walk(abs);
64
+ } else if ((entry.endsWith(".ts") || entry.endsWith(".tsx")) && !entry.endsWith(".d.ts")) {
65
+ out.push(abs);
66
+ }
67
+ }
68
+ };
69
+ walk(join(monoreporoot, relroot));
70
+ return out;
71
+ }
72
+
73
+ /** reads one repo file as text (repo-root relative). */
74
+ function source(relative: string): string {
75
+ return readFileSync(join(monoreporoot, relative), "utf8");
76
+ }
77
+
78
+ describe("the gateway ports law (host/porta sorteados, never localhost literal)", () => {
79
+ it("draws the port inside the documented cryptographic band 30000..59999", () => {
80
+ const draws = new Set<number>();
81
+ for (let i = 0; i < 400; i += 1) {
82
+ const port = drawgatewayport();
83
+ expect(port).toBeGreaterThanOrEqual(gatewayportband.first);
84
+ expect(port).toBeLessThanOrEqual(gatewayportband.last);
85
+ draws.add(port);
86
+ }
87
+ // the band answers a real draw, not a degenerate constant
88
+ expect(draws.size).toBeGreaterThanOrEqual(100);
89
+ });
90
+
91
+ it("draws the host inside the loopback band 127.x.y.z with three drawn octets", () => {
92
+ const draws = new Set<string>();
93
+ for (let i = 0; i < 200; i += 1) {
94
+ const host = drawgatewayhost();
95
+ const match = host.match(/^127\.(\d+)\.(\d+)\.(\d+)$/);
96
+ expect(match, `the drawn host ${host} rides 127.a.b.c`).not.toBeNull();
97
+ if (!match) continue;
98
+ for (const octet of match.slice(1)) {
99
+ const value = Number(octet);
100
+ expect(value).toBeGreaterThanOrEqual(1);
101
+ expect(value).toBeLessThanOrEqual(254);
102
+ }
103
+ draws.add(host);
104
+ }
105
+ expect(draws.size).toBeGreaterThanOrEqual(50);
106
+ });
107
+
108
+ it("rides the env pin when the env carries one (the pin is a pin, never a literal)", () => {
109
+ const pinned = resolvegatewaybind("41234", "127.9.9.9");
110
+ expect(pinned.port).toBe(41234);
111
+ expect(pinned.hostname).toBe("127.9.9.9");
112
+ expect(pinned.portdrawn).toBe(false);
113
+ expect(pinned.hostdrawn).toBe(false);
114
+ // an env value the operator set rides verbatim — the law bans the source
115
+ // literal fallback, never the operator's own pin
116
+ const operatorpin = resolvegatewaybind("3001", undefined);
117
+ expect(operatorpin.port).toBe(3001);
118
+ expect(operatorpin.portdrawn).toBe(false);
119
+ expect(operatorpin.hostdrawn).toBe(true);
120
+ });
121
+
122
+ it("draws when the env stays silent or lies (empty, zero, negative, junk)", () => {
123
+ for (const [portenv, hostenv] of [
124
+ [undefined, undefined],
125
+ ["", ""],
126
+ [" ", " "],
127
+ ["0", undefined],
128
+ ["-1", ""],
129
+ ["abc", undefined],
130
+ ] as const) {
131
+ const drawn = resolvegatewaybind(portenv, hostenv);
132
+ expect(drawn.portdrawn, `PORT=${String(portenv)} must not pin`).toBe(true);
133
+ expect(drawn.port).toBeGreaterThanOrEqual(gatewayportband.first);
134
+ expect(drawn.port).toBeLessThanOrEqual(gatewayportband.last);
135
+ expect(drawn.hostdrawn, `HOST=${String(hostenv)} must not pin`).toBe(true);
136
+ expect(drawn.hostname).toMatch(/^127\.\d+\.\d+\.\d+$/);
137
+ }
138
+ });
139
+
140
+ it("carries no literal bind fallback in the gateway lane sources", () => {
141
+ const servertxt = source("getry/server.ts");
142
+ expect(servertxt, "the transport main retired the || 3001 chain").not.toMatch(/\|\|\s*3001/);
143
+ expect(servertxt, "the transport main retired the 0.0.0.0 host").not.toContain('"0.0.0.0"');
144
+ expect(servertxt, "the transport main rides the drawn bind resolver").toContain("resolvegatewaybind(");
145
+ const clitxt = source("getry/cli.ts");
146
+ expect(clitxt, "the console serve seat retired the || 3001 chain").not.toMatch(/\|\|\s*3001/);
147
+ expect(clitxt, "the console serve seat retired the 0.0.0.0 host").not.toContain('"0.0.0.0"');
148
+ const bindtxt = source("getry/binddraw.ts");
149
+ expect(bindtxt, "the draw rides node:crypto randomInt").toContain('from "node:crypto"');
150
+ expect(bindtxt, "the port draw names the documented band").toMatch(/30000/);
151
+ // the localhost literal answers nowhere in the gateway boot lane
152
+ for (const file of ["getry/server.ts", "getry/cli.ts", "getry/binddraw.ts"]) {
153
+ expect(source(file), `${file} carries no localhost bind literal`).not.toMatch(/localhost:\d+/);
154
+ }
155
+ });
156
+
157
+ it("keeps the oauth callback seat on the os draw (port 0, env pin)", () => {
158
+ const oauthtxt = source("getry/oauth.ts");
159
+ expect(oauthtxt, "the callback server asks the os for the port").toMatch(/server\.listen\(0,/);
160
+ expect(oauthtxt, "the callback pin rides the env, never a literal").toContain("OAUTH_CALLBACK_PORT");
161
+ expect(oauthtxt, "the callback host rides the env loopback, never 0.0.0.0").not.toContain('"0.0.0.0"');
162
+ });
163
+ });
164
+
165
+ describe("the one signup law (one signup family-wide, the guest identity never deleted)", () => {
166
+ it("carries one signup lane — the vault person's door, and no sibling mounts one", () => {
167
+ const routestxt = source("vault/routes.ts");
168
+ expect(routestxt, "the one signup door rides the vault route table").toContain('signup: "/api/v1/auth/signup"');
169
+ const accounttxt = source("vault/account.ts");
170
+ expect(accounttxt, "the signup account lane lives in the vault account seat").toContain(
171
+ "export function signupAccount(",
172
+ );
173
+ expect(accounttxt, "the signup http door answers the route").toMatch(/POST \/api\/v1\/auth\/signup/);
174
+ // no sibling carries the signup lane (the walk answers only vault files)
175
+ const carriers: string[] = [];
176
+ for (const root of FAMILYROOTS) {
177
+ for (const file of walksources(root)) {
178
+ const text = readFileSync(file, "utf8");
179
+ if (text.includes("signupAccount") || text.includes("/api/v1/auth/signup")) carriers.push(file);
180
+ }
181
+ }
182
+ const strangers = carriers.filter((file) => !file.replaceAll("\\", "/").includes("/vault/"));
183
+ expect(strangers, `only vault carries the signup lane — strangers answered: ${strangers.join(", ")}`).toEqual([]);
184
+ });
185
+
186
+ it("upserts the visitor identity seen-wins — born on the first write, never replayed", () => {
187
+ expect(source("vault/db.ts"), "the vault onboarding lane upserts").toMatch(/ON CONFLICT\(visitor\) DO UPDATE SET/);
188
+ expect(source("vault/onboardingstore.ts"), "the onboarding store lane upserts").toMatch(
189
+ /ON CONFLICT\(visitor\) DO UPDATE SET/,
190
+ );
191
+ expect(source("getry/db.ts"), "the getry onboarding lane upserts").toMatch(/ON CONFLICT\(visitor\) DO UPDATE/);
192
+ expect(source("vault/db.ts"), "the visitor row rides the platform db").toMatch(
193
+ /CREATE TABLE IF NOT EXISTS onboarding_states/,
194
+ );
195
+ });
196
+
197
+ it("carries zero delete lanes against the visitor and person identity rows family-wide", () => {
198
+ // the identity rows: the visitor's onboarding_states and the person's
199
+ // accounts — no source in the family ever deletes them
200
+ const identitydelete = /DELETE\s+FROM\s+(IF\s+EXISTS\s+)?"?(onboarding_states|accounts|visitors|visitor)"?\b/i;
201
+ const carriers: string[] = [];
202
+ for (const root of FAMILYROOTS) {
203
+ for (const file of walksources(root)) {
204
+ const text = readFileSync(file, "utf8");
205
+ if (identitydelete.test(text)) carriers.push(file);
206
+ }
207
+ }
208
+ expect(carriers, `the guest identity never deletes — carriers answered: ${carriers.join(", ")}`).toEqual([]);
209
+ });
210
+
211
+ it("keeps the sweep lanes honest — the retention clock plans, it never deletes an identity", () => {
212
+ const retentiontxt = source("vault/retention.ts");
213
+ expect(retentiontxt, "the retention engine is a pure plan").not.toMatch(/\bDELETE\s+FROM\b/i);
214
+ expect(retentiontxt, "the retention clock never rides an identity lane").not.toMatch(/visitor|account/i);
215
+ const objectstxt = source("vault/objects.ts");
216
+ expect(objectstxt, "the object sweep never rides an identity lane").not.toMatch(
217
+ /DELETE\s+FROM\s+"?(onboarding_states|accounts)/i,
218
+ );
219
+ });
220
+ });