@teamscala/service-boot 0.23.524 → 0.23.525

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.
Files changed (81) hide show
  1. package/package.json +1 -9
  2. package/src/agreement-gate/authorized-emails.ts +0 -7
  3. package/src/agreement-gate/placeholder-strip.ts +0 -2
  4. package/src/agreement-gate/settings.ts +0 -15
  5. package/src/agreement-gate.ts +0 -34
  6. package/src/attach-proposal-accept.ts +1 -1
  7. package/src/attach-proposal-magic.ts +1 -1
  8. package/src/auth/sign-out.ts +0 -2
  9. package/src/auth-gate/public-paths.ts +0 -86
  10. package/src/auth-gate.ts +0 -19
  11. package/src/bind-page-handler.ts +0 -76
  12. package/src/build-client-assets/hydrate-bundle.ts +0 -126
  13. package/src/build-client-assets/manifest.ts +0 -18
  14. package/src/build-client-assets/source-candidates.ts +0 -34
  15. package/src/build-client-assets/sourcemaps-upload.ts +0 -42
  16. package/src/build-client-assets/styles-build.ts +0 -50
  17. package/src/capability-slug.ts +0 -10
  18. package/src/compose-page-title.ts +0 -9
  19. package/src/content-gate/access.ts +0 -26
  20. package/src/content-gate/catalog.ts +0 -29
  21. package/src/content-gate/config-types.ts +3 -84
  22. package/src/content-gate/gate.ts +0 -20
  23. package/src/content-page-floor.ts +0 -11
  24. package/src/error-page-logo.ts +2 -12
  25. package/src/expand-roster.ts +0 -9
  26. package/src/gpt-gate.ts +1 -1
  27. package/src/hono-error-handler.ts +0 -4
  28. package/src/install-logging.ts +0 -32
  29. package/src/load-page-manifest.ts +0 -6
  30. package/src/load-service-install-row.ts +41 -4
  31. package/src/module-not-found.ts +0 -11
  32. package/src/mount-orpc-router.ts +1 -88
  33. package/src/mount-server-packages.ts +0 -29
  34. package/src/mutable-app-ref.ts +0 -11
  35. package/src/orpc-contract-rows.json +289 -0
  36. package/src/orpc-openapi-document.ts +0 -6
  37. package/src/orpc-openapi-gate.ts +0 -10
  38. package/src/pg-boss-handler-registry.ts +0 -12
  39. package/src/pg-boss-mount.ts +0 -101
  40. package/src/proposal-gate.ts +1 -41
  41. package/src/quiz-gate.ts +1 -9
  42. package/src/register-page-server-client.ts +0 -53
  43. package/src/register-registry-page-route/ai-chat-prefetch.ts +0 -18
  44. package/src/register-registry-page-route/apply-page-branding.ts +0 -90
  45. package/src/register-registry-page-route/captured-loader-config.ts +0 -1
  46. package/src/register-registry-page-route/graph-cache.ts +0 -17
  47. package/src/register-registry-page-route/meta-title.ts +0 -24
  48. package/src/register-registry-page-route/page-data-shared.ts +0 -6
  49. package/src/register-registry-page-route/prefetch-org-id.ts +0 -17
  50. package/src/register-registry-page-route/proposal-approval-email-token.ts +0 -11
  51. package/src/register-registry-page-route/proposal-brand-stamping.ts +0 -20
  52. package/src/register-registry-page-route/public-shell-page-data.ts +0 -26
  53. package/src/register-registry-page-route/resolve-request-graph.ts +0 -57
  54. package/src/register-registry-page-route/run-content-gates.ts +2 -54
  55. package/src/register-registry-page-route/warn-uncovered-blocks.ts +0 -10
  56. package/src/register-registry-page-route.ts +0 -75
  57. package/src/register-static-assets.ts +0 -45
  58. package/src/resolve-brand-favicon.ts +0 -9
  59. package/src/resolve-service-port.ts +0 -11
  60. package/src/resolve-theme-mode.ts +0 -8
  61. package/src/resolvers/app-header.ts +0 -1
  62. package/src/resolvers/fetch-org-brand.ts +0 -1
  63. package/src/resolvers/portal-route-paths.ts +0 -17
  64. package/src/resource-not-found.ts +0 -24
  65. package/src/select-service-routes.ts +0 -14
  66. package/src/theme-fonts-link.ts +0 -5
  67. package/src/two-phase-full-app.ts +0 -76
  68. package/src/two-phase-minimal-app.ts +1 -105
  69. package/src/types.ts +0 -14
  70. package/src/ui-service.ts +1 -42
  71. package/src/content-gate/gate-entry-marker.test.ts +0 -111
  72. package/src/content-gate/token.segmentation.test.ts +0 -76
  73. package/src/content-gate/token.test.ts +0 -65
  74. package/src/content-gate/token.ts +0 -197
  75. package/src/is-react-element-type.test.ts +0 -45
  76. package/src/is-react-element-type.ts +0 -10
  77. package/src/page-server-orpc/hub-mcp-client.ts +0 -14
  78. package/src/pg-boss-mount.test.ts +0 -101
  79. package/src/resource-chat-inject.page-assistant.test.ts +0 -145
  80. package/src/resource-chat-inject.test.ts +0 -240
  81. package/src/resource-chat-inject.ts +0 -357
@@ -1,111 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Locks the OPT-IN gate-entry marker. A gate that does NOT declare
7
- * entryCookieName must behave exactly as before — a valid token authorizes on
8
- * any load and nothing is recorded for clearing (condition 1: every non-opting
9
- * service unchanged). An opting gate authorizes ONLY the request that carries
10
- * the marker (the identity submit's own redirect) and returns the marker name
11
- * so the page Response clears it — one view, then the gate is presented again.
12
- *
13
- * The clears are asserted from the CALL'S RETURN VALUE: the store lives only
14
- * inside the run() scope, and the first shipped version read it afterwards,
15
- * saw nothing, and left the marker in the browser (measured live 2026-09-14).
16
- *
17
- * Operator ruling 2026-09-14 (refresh must return to the identity gate),
18
- * mechanism measured live: the quiz_token cookie alone was the carrier that
19
- * skipped the gate on reload.
20
- */
21
- import { describe, expect, test } from "bun:test";
22
- import type { ContentGateConfig } from "./config-types.ts";
23
- import { runContentGate } from "./gate.ts";
24
- import { withGateEntryClears } from "./gate-entry-clears.ts";
25
-
26
- function makeConfig(entryCookieName?: string) {
27
- const calls = { authorized: 0, unauthorized: 0 };
28
- const cfg: ContentGateConfig = {
29
- cookieName: "quiz_token",
30
- entryCookieName,
31
- getResourceId: () => "res-1",
32
- verifyToken: (token) =>
33
- token === "good" ? { email: "v@example.com", name: "V" } : null,
34
- onAuthorized: () => {
35
- calls.authorized++;
36
- },
37
- onUnauthorized: () => {
38
- calls.unauthorized++;
39
- },
40
- };
41
- return { cfg, calls };
42
- }
43
-
44
- describe("gate entry marker is opt-in and one-view", () => {
45
- test("non-opting gate: a valid token authorizes on any load, nothing cleared — behaviour unchanged", async () => {
46
- const { cfg, calls } = makeConfig();
47
- const { clears } = await withGateEntryClears(async () => {
48
- const out = await runContentGate({}, "quiz_token=good", Date.now(), cfg);
49
- expect(out.authorized).toBe(true);
50
- expect(calls.authorized).toBe(1);
51
- });
52
- expect(clears).toEqual([]);
53
- });
54
-
55
- test("opting gate: token WITHOUT the marker is unauthorized — a refresh shows the gate", async () => {
56
- const { cfg, calls } = makeConfig("quiz_token_entered");
57
- const { clears } = await withGateEntryClears(async () => {
58
- const out = await runContentGate({}, "quiz_token=good", Date.now(), cfg);
59
- expect(out.authorized).toBe(false);
60
- expect(calls.unauthorized).toBe(1);
61
- expect(calls.authorized).toBe(0);
62
- });
63
- expect(clears).toEqual([]);
64
- });
65
-
66
- test("opting gate: token + marker authorizes and RETURNS the marker for clearing — one view", async () => {
67
- const { cfg, calls } = makeConfig("quiz_token_entered");
68
- const { clears } = await withGateEntryClears(async () => {
69
- const out = await runContentGate(
70
- {},
71
- "quiz_token=good; quiz_token_entered=1",
72
- Date.now(),
73
- cfg,
74
- );
75
- expect(out.authorized).toBe(true);
76
- expect(calls.authorized).toBe(1);
77
- });
78
- expect(clears).toEqual(["quiz_token_entered"]);
79
- });
80
-
81
- test("opting gate: marker present but token invalid stays unauthorized and clears nothing", async () => {
82
- const { cfg, calls } = makeConfig("quiz_token_entered");
83
- const { clears } = await withGateEntryClears(async () => {
84
- const out = await runContentGate(
85
- {},
86
- "quiz_token=bad; quiz_token_entered=1",
87
- Date.now(),
88
- cfg,
89
- );
90
- expect(out.authorized).toBe(false);
91
- expect(calls.unauthorized).toBe(1);
92
- });
93
- expect(clears).toEqual([]);
94
- });
95
-
96
- test("two gates in one request each report their own marker, drained once", async () => {
97
- const first = makeConfig("gate_a_entered");
98
- const second = makeConfig("gate_b_entered");
99
- const { clears } = await withGateEntryClears(async () => {
100
- await runContentGate({}, "quiz_token=good; gate_a_entered=1", Date.now(), first.cfg);
101
- await runContentGate({}, "quiz_token=good; gate_b_entered=1", Date.now(), second.cfg);
102
- });
103
- expect(clears).toEqual(["gate_a_entered", "gate_b_entered"]);
104
- });
105
-
106
- test("the quiz gate and its access endpoint both opt in on the same marker name", async () => {
107
- const { quizAccessConfig, quizGateConfig } = await import("../quiz-gate.ts");
108
- expect(quizGateConfig.entryCookieName).toBe("quiz_token_entered");
109
- expect(quizAccessConfig.entryCookieName).toBe("quiz_token_entered");
110
- });
111
- });
@@ -1,76 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Segmentation carriage in the content-gate token. The quiz's composed gate
7
- * screen captures company/linkedin_url/website/team_size alongside the
8
- * identity; the token carries them to the next render so the submit can put
9
- * all seven fields on the ONE response row with no client storage. The
10
- * generated `token.test.ts` owns the identity/binding suite; these are the
11
- * segmentation-specific cases (including the backward-compatibility contract:
12
- * a token minted before `s` existed verifies identically).
13
- */
14
-
15
- process.env.AUTH_SECRET = "local-test-fixture-entropy-0001";
16
-
17
- import { describe, expect, test } from "bun:test";
18
- import { signAccessToken, verifyAccessTokenIdentity } from "./token.ts";
19
-
20
- const NOW = 1_000_000_000_000;
21
- const TTL = 60_000;
22
-
23
- function decodePayload(token: string): Record<string, unknown> {
24
- return JSON.parse(Buffer.from(token.split(".")[0], "base64url").toString());
25
- }
26
-
27
- describe("content-gate token — segmentation carriage", () => {
28
- test("round-trips the four captured fields", () => {
29
- const t = signAccessToken("quiz_token", "quiz-1", "a@b.com", NOW, TTL, "Ann", "+614", {
30
- company: "Acme",
31
- linkedinUrl: "https://www.linkedin.com/in/ann",
32
- website: "https://acme.test",
33
- teamSize: "Me + team of 2–5",
34
- });
35
- const id = verifyAccessTokenIdentity(t, "quiz_token", "quiz-1", NOW + 1);
36
- expect(id?.email).toBe("a@b.com");
37
- expect(id?.name).toBe("Ann");
38
- expect(id?.phone).toBe("+614");
39
- expect(id?.segmentation).toEqual({
40
- company: "Acme",
41
- linkedinUrl: "https://www.linkedin.com/in/ann",
42
- website: "https://acme.test",
43
- teamSize: "Me + team of 2–5",
44
- });
45
- });
46
-
47
- test("BACKWARD COMPATIBILITY: a pre-widening token (no s key) verifies with segmentation undefined — never an error", () => {
48
- // Mint with the old shape by omitting segmentation entirely, then assert
49
- // the exact contract a deploy-window survivor must satisfy.
50
- const t = signAccessToken("quiz_token", "quiz-1", "a@b.com", NOW, TTL, "Ann", "+614");
51
- expect(decodePayload(t as string).s).toBeUndefined();
52
- const id = verifyAccessTokenIdentity(t, "quiz_token", "quiz-1", NOW + 1);
53
- expect(id).toEqual({ email: "a@b.com", name: "Ann", phone: "+614", segmentation: undefined });
54
- });
55
-
56
- test("empty/whitespace segmentation values produce no s key at all", () => {
57
- const t = signAccessToken("quiz_token", "quiz-1", "a@b.com", NOW, TTL, undefined, undefined, {
58
- company: " ",
59
- linkedinUrl: "",
60
- website: undefined,
61
- teamSize: undefined,
62
- });
63
- expect(decodePayload(t as string).s).toBeUndefined();
64
- const id = verifyAccessTokenIdentity(t, "quiz_token", "quiz-1", NOW + 1);
65
- expect(id?.segmentation).toBeUndefined();
66
- });
67
-
68
- test("values are trimmed on sign", () => {
69
- const t = signAccessToken("quiz_token", "quiz-1", "a@b.com", NOW, TTL, undefined, undefined, {
70
- company: " Acme ",
71
- teamSize: "Just me",
72
- });
73
- const id = verifyAccessTokenIdentity(t, "quiz_token", "quiz-1", NOW + 1);
74
- expect(id?.segmentation).toEqual({ company: "Acme", teamSize: "Just me" });
75
- });
76
- });
@@ -1,65 +0,0 @@
1
- // @system codegen
2
- // @status generated
3
- // @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
4
- //
5
- // This suite's assertions are OWNED by the codegen band: the band module
6
- // carries them verbatim, this file is the emission, and hand edits here are
7
- // overwritten on the next run. The rationale each assertion carries moved
8
- // with it into the band.
9
-
10
- process.env.AUTH_SECRET = "test-secret-at-least-16-chars-long";
11
-
12
- import { describe, expect, test } from "bun:test";
13
- import {
14
- signAccessToken,
15
- verifyAccessToken,
16
- verifyAccessTokenIdentity,
17
- } from "./token.ts";
18
-
19
- const NOW = 1_000_000_000_000;
20
- const TTL = 60_000;
21
-
22
- describe("content-gate token — identity", () => {
23
- test("signs + verifies email AND name (GPT capture)", () => {
24
- const t = signAccessToken("gpt_token", "gpt-1", "A@B.com", NOW, TTL, "Ann");
25
- expect(t).toBeTruthy();
26
- const id = verifyAccessTokenIdentity(t, "gpt_token", "gpt-1", NOW + 1);
27
- expect(id).toEqual({ email: "a@b.com", name: "Ann" });
28
- });
29
-
30
- test("name omitted → identity has email, name undefined", () => {
31
- const t = signAccessToken("gpt_token", "gpt-1", "a@b.com", NOW, TTL);
32
- const id = verifyAccessTokenIdentity(t, "gpt_token", "gpt-1", NOW + 1);
33
- expect(id).toEqual({ email: "a@b.com", name: undefined });
34
- });
35
-
36
- test("email-only verifyAccessToken still works (proposals/agreements)", () => {
37
- const t = signAccessToken("prop_token", "p-1", "a@b.com", NOW, TTL);
38
- expect(verifyAccessToken(t, "prop_token", "p-1", NOW + 1)).toBe("a@b.com");
39
- });
40
- });
41
-
42
- describe("content-gate token — binding (withholds content)", () => {
43
- const t = signAccessToken("gpt_token", "gpt-1", "a@b.com", NOW, TTL, "Ann");
44
-
45
- test("wrong gate (cookieName) → null", () => {
46
- expect(verifyAccessTokenIdentity(t, "prop_token", "gpt-1", NOW + 1)).toBeNull();
47
- });
48
-
49
- test("wrong resource → null", () => {
50
- expect(verifyAccessTokenIdentity(t, "gpt_token", "gpt-2", NOW + 1)).toBeNull();
51
- });
52
-
53
- test("expired → null", () => {
54
- expect(verifyAccessTokenIdentity(t, "gpt_token", "gpt-1", NOW + TTL + 1)).toBeNull();
55
- });
56
-
57
- test("tampered signature → null", () => {
58
- const bad = `${t?.split(".")[0]}.deadbeef`;
59
- expect(verifyAccessTokenIdentity(bad, "gpt_token", "gpt-1", NOW + 1)).toBeNull();
60
- });
61
-
62
- test("missing token → null", () => {
63
- expect(verifyAccessTokenIdentity(undefined, "gpt_token", "gpt-1", NOW)).toBeNull();
64
- });
65
- });
@@ -1,197 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- */
5
-
6
- import type { GateSegmentation } from "./config-types.ts";
7
- import { resolveDbUrlOrNull } from "@teamscala/db/db-url";
8
-
9
- function secret(): string | null {
10
- // The last resort is the tenant DB address, read through the named-need
11
- // resolver (wrong-database guard, 2026-09-27) — never `process.env` here;
12
- // the resolver is the only env-aware surface and it names the plane. It is
13
- // used only as a stable per-deployment string (≥16 chars), never as a
14
- // connection target.
15
- for (const s of [
16
- process.env.AUTH_SECRET,
17
- process.env.ENCRYPTION_KEY,
18
- resolveDbUrlOrNull("tenant"),
19
- ]) {
20
- if (s && s.length >= 16) return s;
21
- }
22
- return null;
23
- }
24
-
25
- function hmac(payloadB64: string, key: string): string {
26
- return new Bun.CryptoHasher("sha256", key).update(payloadB64).digest("base64url");
27
- }
28
-
29
- /** Constant-time string compare (equal-length); avoids node:crypto. */
30
- function safeEqual(a: string, b: string): boolean {
31
- if (a.length !== b.length) return false;
32
- let diff = 0;
33
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
34
- return diff === 0;
35
- }
36
-
37
- /** Mint a token proving `email` is authorized for `resourceId` under `cookieName`. */
38
- export function signAccessToken(
39
- cookieName: string,
40
- resourceId: string,
41
- email: string,
42
- nowMs: number,
43
- ttlMs: number,
44
- /**
45
- * Optional display name to carry in the token (the public GPT lead-capture
46
- * gate uses this so a returning visitor's name is known at SSR — first paint
47
- * renders the full identity, not just the email). Allow-list gates
48
- * (proposals/agreements) omit it.
49
- */
50
- name?: string,
51
- /**
52
- * Optional captured phone, same reasoning as `name`. It exists so a capture
53
- * gate needs NO browser storage to remember it: the operator's ruling is
54
- * that identity lives in the database and the request, never in
55
- * localStorage.
56
- */
57
- phone?: string,
58
- /**
59
- * Optional prospect segmentation captured alongside the identity on the
60
- * quiz's composed gate screen (company / LinkedIn / website / team size).
61
- * Same reasoning as name/phone: it rides the signed request, never client
62
- * storage. OPTIONAL and absent from older tokens — a token without it
63
- * verifies exactly as before.
64
- */
65
- segmentation?: GateSegmentation,
66
- ): string | null {
67
- const key = secret();
68
- if (!key) return null;
69
- const payload: {
70
- c: string;
71
- r: string;
72
- e: string;
73
- x: number;
74
- n?: string;
75
- p?: string;
76
- s?: GateSegmentation;
77
- } = {
78
- c: cookieName,
79
- r: resourceId,
80
- e: email.trim().toLowerCase(),
81
- x: nowMs + ttlMs,
82
- };
83
- const trimmedName = name?.trim();
84
- if (trimmedName) payload.n = trimmedName;
85
- const trimmedPhone = phone?.trim();
86
- if (trimmedPhone) payload.p = trimmedPhone;
87
- const seg: GateSegmentation = {};
88
- const segCompany = segmentation?.company?.trim();
89
- if (segCompany) seg.company = segCompany;
90
- const segLinkedin = segmentation?.linkedinUrl?.trim();
91
- if (segLinkedin) seg.linkedinUrl = segLinkedin;
92
- const segWebsite = segmentation?.website?.trim();
93
- if (segWebsite) seg.website = segWebsite;
94
- const segTeamSize = segmentation?.teamSize?.trim();
95
- if (segTeamSize) seg.teamSize = segTeamSize;
96
- if (Object.keys(seg).length > 0) payload.s = seg;
97
- const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
98
- return `${payloadB64}.${hmac(payloadB64, key)}`;
99
- }
100
-
101
- /**
102
- * Validate a token for `cookieName` + `resourceId`. Returns the verified email
103
- * on success, null otherwise (bad signature, wrong gate, wrong resource,
104
- * expired, no secret).
105
- */
106
- export function verifyAccessToken(
107
- token: string | undefined | null,
108
- cookieName: string,
109
- resourceId: string,
110
- nowMs: number,
111
- ): string | null {
112
- if (!token) return null;
113
- const key = secret();
114
- if (!key) return null;
115
- const dot = token.indexOf(".");
116
- if (dot <= 0) return null;
117
- const payloadB64 = token.slice(0, dot);
118
- const sig = token.slice(dot + 1);
119
- if (!safeEqual(sig, hmac(payloadB64, key))) return null;
120
- try {
121
- const p = JSON.parse(Buffer.from(payloadB64, "base64url").toString()) as {
122
- c?: string;
123
- r?: string;
124
- e?: string;
125
- x?: number;
126
- };
127
- if (p.c !== cookieName) return null;
128
- if (p.r !== resourceId) return null;
129
- if (typeof p.x !== "number" || p.x < nowMs) return null;
130
- return typeof p.e === "string" ? p.e : null;
131
- } catch {
132
- return null;
133
- }
134
- }
135
-
136
- /**
137
- * Like `verifyAccessToken` but returns the full verified identity
138
- * ({email, name, phone, segmentation}) so a gate that carries a display name in
139
- * the token (the public GPT lead-capture gate) can seed the visitor's name — not
140
- * just the email — at SSR, and the quiz gate can seed the captured prospect
141
- * segmentation the same way. Same signature/expiry/gate-binding checks; null on
142
- * any failure. A token minted BEFORE segmentation existed carries no `s` key and
143
- * verifies identically (segmentation comes back undefined) — never an error.
144
- */
145
- export function verifyAccessTokenIdentity(
146
- token: string | undefined | null,
147
- cookieName: string,
148
- resourceId: string,
149
- nowMs: number,
150
- ): {
151
- email: string;
152
- name?: string;
153
- phone?: string;
154
- segmentation?: GateSegmentation;
155
- } | null {
156
- if (!token) return null;
157
- const key = secret();
158
- if (!key) return null;
159
- const dot = token.indexOf(".");
160
- if (dot <= 0) return null;
161
- const payloadB64 = token.slice(0, dot);
162
- const sig = token.slice(dot + 1);
163
- if (!safeEqual(sig, hmac(payloadB64, key))) return null;
164
- try {
165
- const p = JSON.parse(Buffer.from(payloadB64, "base64url").toString()) as {
166
- c?: string;
167
- r?: string;
168
- e?: string;
169
- x?: number;
170
- n?: string;
171
- p?: string;
172
- s?: GateSegmentation;
173
- };
174
- if (p.c !== cookieName) return null;
175
- if (p.r !== resourceId) return null;
176
- if (typeof p.x !== "number" || p.x < nowMs) return null;
177
- if (typeof p.e !== "string") return null;
178
- const segmentation =
179
- p.s && typeof p.s === "object"
180
- ? {
181
- company: typeof p.s.company === "string" ? p.s.company : undefined,
182
- linkedinUrl:
183
- typeof p.s.linkedinUrl === "string" ? p.s.linkedinUrl : undefined,
184
- website: typeof p.s.website === "string" ? p.s.website : undefined,
185
- teamSize: typeof p.s.teamSize === "string" ? p.s.teamSize : undefined,
186
- }
187
- : undefined;
188
- return {
189
- email: p.e,
190
- name: typeof p.n === "string" ? p.n : undefined,
191
- phone: typeof p.p === "string" ? p.p : undefined,
192
- segmentation,
193
- };
194
- } catch {
195
- return null;
196
- }
197
- }
@@ -1,45 +0,0 @@
1
- // @system codegen
2
- // @status generated
3
- // @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
4
- //
5
- // This suite's assertions are OWNED by the codegen band: the band module
6
- // carries them verbatim, this file is the emission, and hand edits here are
7
- // overwritten on the next run. The rationale each assertion carries moved
8
- // with it into the band.
9
-
10
- import { expect, test } from "bun:test";
11
- import { forwardRef, lazy, memo } from "react";
12
- import { isReactElementType } from "./is-react-element-type.ts";
13
-
14
- function Plain() {
15
- return null;
16
- }
17
-
18
- test("accepts a plain function component", () => {
19
- expect(isReactElementType(Plain)).toBe(true);
20
- });
21
-
22
- test("accepts memo() — the PresenceAvatars shape that rendered blank", () => {
23
- expect(isReactElementType(memo(Plain))).toBe(true);
24
- });
25
-
26
- test("accepts forwardRef()", () => {
27
- expect(isReactElementType(forwardRef(() => null))).toBe(true);
28
- });
29
-
30
- test("accepts lazy()", () => {
31
- expect(isReactElementType(lazy(async () => ({ default: Plain })))).toBe(true);
32
- });
33
-
34
- test("still rejects what genuinely cannot render", () => {
35
- expect(isReactElementType(undefined)).toBe(false);
36
- expect(isReactElementType(null)).toBe(false);
37
- expect(isReactElementType({})).toBe(false);
38
- expect(isReactElementType("PresenceAvatarsBlock")).toBe(false);
39
- expect(isReactElementType(42)).toBe(false);
40
- });
41
-
42
- test("rejects a plain object wearing a non-symbol $$typeof", () => {
43
- // A data blob with the key but not the symbol must not pass as renderable.
44
- expect(isReactElementType({ $$typeof: "react.memo" })).toBe(false);
45
- });
@@ -1,10 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- */
5
-
6
- export function isReactElementType(value: unknown): boolean {
7
- if (typeof value === "function") return true;
8
- if (typeof value !== "object" || value === null) return false;
9
- return typeof (value as { $$typeof?: unknown }).$$typeof === "symbol";
10
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- */
5
-
6
- let _hubMcpClient: unknown | null = null;
7
-
8
- export function setHubMcpClient(client: unknown): void {
9
- _hubMcpClient = client;
10
- }
11
-
12
- export function getHubMcpClient(): unknown | undefined {
13
- return _hubMcpClient ?? undefined;
14
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The mount's poll-only contract: pg-boss v12 dispatches by polling, so the
7
- * mount dials the INJECTED transaction-pooler URL unchanged (no endpoint
8
- * derivation of its own — the session-port mapping died with the door,
9
- * 2026-09-14) and constructs with useListenNotify false spelled explicitly
10
- * rather than inherited from the supplier default, so a pg-boss bump cannot
11
- * silently re-introduce a LISTEN dependency. The mock captures the opts
12
- * PgBoss is constructed with — the claim is about what the mount DIALS and
13
- * how it dispatches, so the assertions are on the captured opts.
14
- */
15
-
16
- import { afterEach, describe, expect, it, mock } from "bun:test";
17
-
18
- interface CapturedOpts {
19
- connectionString?: string;
20
- max?: number;
21
- useListenNotify?: boolean;
22
- }
23
-
24
- const POOLED_URL =
25
- "postgresql://scala_container_gbg.gbg:s3cret@10.205.50.1:6543/scala_container_gbg?sslmode=require";
26
-
27
- function seedScheduledJob() {
28
- return {
29
- id: "test-job-1",
30
- type: "scheduled_job",
31
- slug: "scheduled_job:test-sync",
32
- label: "Test Sync Job",
33
- description: null,
34
- config: {
35
- handler_slug: "@teamscala/sync-worker:processSyncQueue",
36
- cron: "*/5 * * * *",
37
- queue_name: "sync-worker",
38
- },
39
- is_active: true,
40
- sort_order: 0,
41
- created_at: new Date(),
42
- updated_at: new Date(),
43
- noun: "scheduled_job",
44
- kind: "scheduled_job",
45
- };
46
- }
47
-
48
- describe("mountPgBoss poll-only contract", () => {
49
- afterEach(() => {
50
- mock.restore();
51
- });
52
-
53
- it("dials the injected transaction pooler unchanged and dispatches poll-only", async () => {
54
- let captured: CapturedOpts | null = null;
55
-
56
- mock.module("pg-boss", () => ({
57
- PgBoss: mock((opts: CapturedOpts) => {
58
- captured = opts;
59
- return {
60
- start: mock(() => Promise.resolve()),
61
- stop: mock(() => Promise.resolve()),
62
- on: mock(() => undefined),
63
- createQueue: mock(() => Promise.resolve()),
64
- work: mock(() => Promise.resolve("worker-id")),
65
- schedule: mock(() => Promise.resolve()),
66
- getSchedules: mock(() => Promise.resolve([])),
67
- unschedule: mock(() => Promise.resolve()),
68
- };
69
- }),
70
- }));
71
- mock.module("@teamscala/db/registry/accessors", () => ({
72
- getAllEntries: () => [seedScheduledJob()],
73
- }));
74
- mock.module("@teamscala/event-log/record-event", () => ({
75
- recordEvent: () => undefined,
76
- }));
77
-
78
- const { mountPgBoss } = await import("./pg-boss-mount.ts");
79
- await mountPgBoss({
80
- databaseUrl: POOLED_URL,
81
- serviceSlug: "scala-sync-worker",
82
- packages: ["@teamscala/sync-worker"],
83
- });
84
-
85
- const capturedOpts = captured as unknown as CapturedOpts | undefined;
86
- const connectionString = capturedOpts?.connectionString ?? "";
87
- // The transaction pooler, exactly as injected — the mount adds no
88
- // endpoint derivation.
89
- expect(connectionString).toContain(":6543");
90
- // NEVER the session port: the door is closing, not widening.
91
- expect(connectionString).not.toContain(":5999");
92
- // The tenant suffix the pooler routes by is kept.
93
- expect(connectionString).toContain("scala_container_gbg.gbg:");
94
- // The pg sslmode dialect rewrite still applies.
95
- expect(connectionString).toContain("sslmode=no-verify");
96
- // Poll-only, spelled: no LISTEN dependency, now and after a bump.
97
- expect(capturedOpts?.useListenNotify).toBe(false);
98
- // pg-boss stays a single sibling connection (pool sizing doctrine).
99
- expect(capturedOpts?.max).toBe(1);
100
- });
101
- });