@teamscala/service-boot 0.23.345 → 0.23.347

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 (138) hide show
  1. package/package.json +1064 -1093
  2. package/src/fn-routers/index.ts +0 -2
  3. package/src/two-phase-boot.ts +2 -1
  4. package/src/agreement-gate.d.ts +0 -19
  5. package/src/asset-cache-key.d.ts +0 -11
  6. package/src/asset-optimize.d.ts +0 -17
  7. package/src/asset-proxy.d.ts +0 -11
  8. package/src/asset-social-card.d.ts +0 -17
  9. package/src/attach-agreement-magic.d.ts +0 -20
  10. package/src/attach-portal-members.d.ts +0 -23
  11. package/src/attach-proposal-accept.d.ts +0 -26
  12. package/src/attach-proposal-magic.d.ts +0 -18
  13. package/src/attach-transfers-upload.d.ts +0 -27
  14. package/src/auth-gate.d.ts +0 -29
  15. package/src/bind-page-handler.d.ts +0 -18
  16. package/src/build-client-assets/input-hashing.d.ts +0 -25
  17. package/src/build-client-assets/source-candidates.d.ts +0 -27
  18. package/src/build-client-assets.d.ts +0 -50
  19. package/src/compose-page-title.d.ts +0 -41
  20. package/src/content-gate/access.d.ts +0 -15
  21. package/src/content-gate/config-types.d.ts +0 -99
  22. package/src/content-gate/gate.d.ts +0 -13
  23. package/src/content-gate/parse-cookie.d.ts +0 -9
  24. package/src/content-gate/token.d.ts +0 -48
  25. package/src/content-page-floor.d.ts +0 -39
  26. package/src/discover-blocks.d.ts +0 -28
  27. package/src/expand-roster.d.ts +0 -34
  28. package/src/fn-routers/agreement-completion-email.d.ts +0 -14
  29. package/src/fn-routers/agreement-send-router.d.ts +0 -82
  30. package/src/fn-routers/ai-health-router.d.ts +0 -35
  31. package/src/fn-routers/auth-router.d.ts +0 -47
  32. package/src/fn-routers/cache-admin-router.d.ts +0 -57
  33. package/src/fn-routers/chat-session-router.d.ts +0 -219
  34. package/src/fn-routers/email-defaults.d.ts +0 -1
  35. package/src/fn-routers/email-link-ttl.d.ts +0 -1
  36. package/src/fn-routers/index.d.ts +0 -18
  37. package/src/fn-routers/orchestrator-registration-router.d.ts +0 -60
  38. package/src/fn-routers/orchestrator-registration-router.ts +0 -77
  39. package/src/fn-routers/orchestrator-registration.live.test.ts +0 -70
  40. package/src/fn-routers/orchestrator-registration.test.ts +0 -23
  41. package/src/fn-routers/panel-data-router.d.ts +0 -19
  42. package/src/fn-routers/persist-chat-messages.d.ts +0 -36
  43. package/src/fn-routers/plans-router.d.ts +0 -25
  44. package/src/fn-routers/proposal-send-router.d.ts +0 -58
  45. package/src/fn-routers/public-agreement-router.d.ts +0 -289
  46. package/src/fn-routers/public-assessment-router.d.ts +0 -68
  47. package/src/fn-routers/public-gpt-session-router.d.ts +0 -143
  48. package/src/fn-routers/sign-in-flow-router.d.ts +0 -107
  49. package/src/fn-routers/task-sync-assign.d.ts +0 -307
  50. package/src/fn-routers/task-sync-brief.d.ts +0 -98
  51. package/src/fn-routers/task-sync-close.d.ts +0 -41
  52. package/src/fn-routers/task-sync-dispatch.d.ts +0 -47
  53. package/src/fn-routers/task-sync-dispatch.ts +0 -81
  54. package/src/fn-routers/task-sync-lane.d.ts +0 -49
  55. package/src/fn-routers/task-sync-lane.ts +0 -114
  56. package/src/fn-routers/task-sync-projection.d.ts +0 -144
  57. package/src/fn-routers/task-sync-rename.d.ts +0 -91
  58. package/src/fn-routers/task-sync-router.d.ts +0 -455
  59. package/src/fn-routers/task-sync-router.test.ts +0 -361
  60. package/src/fn-routers/task-sync-router.ts +0 -1075
  61. package/src/fn-routers/tasks-router.d.ts +0 -25
  62. package/src/fn-routers/user-preferences-router.d.ts +0 -77
  63. package/src/fn-routers/work-items-router.d.ts +0 -102
  64. package/src/gate-ttls.d.ts +0 -6
  65. package/src/gpt-gate.d.ts +0 -25
  66. package/src/handlers/bun-stable-watch.d.ts +0 -1
  67. package/src/handlers/pane-outbox-drain.d.ts +0 -1
  68. package/src/handlers/projects-digest.d.ts +0 -26
  69. package/src/handlers/prune-stale-data.d.ts +0 -30
  70. package/src/handlers/registry-stale-inactive-sweep.d.ts +0 -1
  71. package/src/handlers/scala-tools-currency-watch.d.ts +0 -1
  72. package/src/handlers/singleton-tools-currency-watch.d.ts +0 -1
  73. package/src/handlers/sweep-event-log.d.ts +0 -1
  74. package/src/handlers/sweep-host-cache.d.ts +0 -24
  75. package/src/handlers/upstream-issue-sync.d.ts +0 -1
  76. package/src/handlers/whatsapp-liveness-logic.d.ts +0 -34
  77. package/src/handlers/whatsapp-liveness.d.ts +0 -4
  78. package/src/hono-error-handler.d.ts +0 -25
  79. package/src/install-harness.d.ts +0 -45
  80. package/src/install-logging.d.ts +0 -34
  81. package/src/load-service-install-row.d.ts +0 -24
  82. package/src/load-service-prisma-client.d.ts +0 -18
  83. package/src/mount-orpc-router.d.ts +0 -15
  84. package/src/mount-server-packages.d.ts +0 -29
  85. package/src/mount.d.ts +0 -9
  86. package/src/mutable-app-ref.d.ts +0 -44
  87. package/src/page-route-shell-types.d.ts +0 -12
  88. package/src/page-server-orpc/audit-revert-router.d.ts +0 -37
  89. package/src/page-server-orpc/hub-mcp-client.d.ts +0 -12
  90. package/src/page-server-orpc/portal-diagnostics-router.d.ts +0 -49
  91. package/src/page-server-orpc/puck-pages-router.d.ts +0 -58
  92. package/src/page-server-orpc/server-client.d.ts +0 -16
  93. package/src/page-server-orpc/templates-router.d.ts +0 -34
  94. package/src/page-server-orpc/user-preferences-router.d.ts +0 -79
  95. package/src/pg-boss-handler-registry.d.ts +0 -27
  96. package/src/pg-boss-mount.d.ts +0 -42
  97. package/src/proposal-authorized-emails.d.ts +0 -28
  98. package/src/proposal-chat-inject.d.ts +0 -17
  99. package/src/proposal-gate.d.ts +0 -18
  100. package/src/read-html-template.d.ts +0 -38
  101. package/src/read-viewport-from-request.d.ts +0 -18
  102. package/src/readiness-gate.d.ts +0 -16
  103. package/src/register-api-routes.d.ts +0 -18
  104. package/src/register-crash-handlers.d.ts +0 -15
  105. package/src/register-page-routes.d.ts +0 -27
  106. package/src/register-page-server-client.d.ts +0 -11
  107. package/src/register-registry-page-route/ai-chat-prefetch.d.ts +0 -13
  108. package/src/register-registry-page-route/graph-cache.d.ts +0 -30
  109. package/src/register-registry-page-route/meta-title.d.ts +0 -2
  110. package/src/register-registry-page-route/proposal-approval-email-token.d.ts +0 -14
  111. package/src/register-registry-page-route/proposal-brand-stamping.d.ts +0 -28
  112. package/src/register-registry-page-route/resolve-page-data.d.ts +0 -1
  113. package/src/register-registry-page-route.d.ts +0 -27
  114. package/src/register-static-assets.d.ts +0 -13
  115. package/src/register-ws-routes.d.ts +0 -28
  116. package/src/resolve-app-base-url.d.ts +0 -13
  117. package/src/resolve-brand-favicon.d.ts +0 -28
  118. package/src/resolve-from-loader-config.d.ts +0 -32
  119. package/src/resolve-service-context.d.ts +0 -17
  120. package/src/resolve-service-port.d.ts +0 -75
  121. package/src/resolve-theme-mode.d.ts +0 -42
  122. package/src/resolvers/fetch-org-brand.d.ts +0 -2
  123. package/src/resolvers/portal-route-paths.d.ts +0 -39
  124. package/src/resolvers/website-by-host-map.d.ts +0 -12
  125. package/src/resource-chat-inject.d.ts +0 -46
  126. package/src/resource-not-found.d.ts +0 -60
  127. package/src/run-boot-hook-list.d.ts +0 -19
  128. package/src/run-boot-hooks.d.ts +0 -31
  129. package/src/scala-tools-currency-watchdog.d.ts +0 -10
  130. package/src/security-headers.d.ts +0 -12
  131. package/src/serve/serve.d.ts +0 -46
  132. package/src/serve/stamp-transport-identity.d.ts +0 -39
  133. package/src/server-error-page.d.ts +0 -29
  134. package/src/start-scheduled-jobs.d.ts +0 -18
  135. package/src/two-phase-boot.d.ts +0 -176
  136. package/src/types.d.ts +0 -46
  137. package/src/ui-service.d.ts +0 -46
  138. package/src/ws-boot-self-test.d.ts +0 -19
@@ -1,2 +0,0 @@
1
- export declare function buildMetaFromContext(metaContext: Record<string, Record<string, string>> | undefined, resolved: Record<string, unknown>): Record<string, unknown>;
2
- export declare function resolveTemplateTitle(service: string, resolved: Record<string, unknown>): string | undefined;
@@ -1,14 +0,0 @@
1
- /**
2
- * Resolve the `{{approvalEmail}}` placeholder in a proposal page's button props
3
- * (e.g. the Approve CTA) in place. Routing: the organisation's registered
4
- * company email FIRST (organisation_profile.contact_email, already resolved
5
- * into pageData.brand.contactEmail); the proposal creator's user email only as
6
- * a fallback when the org has none — so a cloned proposal can never carry a
7
- * stale hard-coded address. The creator lookup is null-safe and adds no user
8
- * exposure: it resolves only where the in-process ORPC client exposes `user`
9
- * (super-admin), degrading to the org email elsewhere. Called only for proposal
10
- * pages; other registry pages are untouched.
11
- */
12
- export declare function applyApprovalEmailToken(pageData: Record<string, unknown>, proposal: {
13
- created_by?: string;
14
- } | undefined): Promise<void>;
@@ -1,28 +0,0 @@
1
- /**
2
- * Stash a proposal's connected partner logo/colour on `pageData` from the
3
- * `partnerOrg` loader source (`crm_organisations` loaded by the proposal's
4
- * `partner_id`, via the SAME proven ORPC loader path that loads the proposal
5
- * itself). resolve-page-brand (service-runtime) prefers `pageData.partnerLogo`
6
- * over any inline body-Hero prop and fans it into the shared `/header` so a
7
- * `source: "partner"` OrgLogo fills automatically — the partner logo lives on
8
- * the CRM record, never duplicated per proposal. No partner → partnerOrg is
9
- * null → nothing set → the partner logo block renders blank. Reading the
10
- * already-resolved loader value (not a second hand-rolled getServerClient call)
11
- * is what makes this reliable: the earlier direct `crm_organisations.findFirst`
12
- * with a Prisma-style object `select` resolved intermittently/never against the
13
- * generated ORPC, leaving the header partner blank (2026-06-12).
14
- */
15
- export declare function applyPartnerBrand(pageData: Record<string, unknown>, partnerOrg: {
16
- logo_url?: string | null;
17
- brand_primary_color?: string | null;
18
- } | undefined | null): void;
19
- /**
20
- * Auto-resolve the client's company logo from the `clientOrg` loader source
21
- * (contact_id → company_id → crm_organisations.logo_url). Injects into the
22
- * Hero block's clientLogo prop when it's empty — no manual logo URL needed
23
- * per proposal when the contact's company profile has a logo.
24
- */
25
- export declare function applyClientLogo(pageData: Record<string, unknown>, clientOrg: {
26
- logo_url?: string | null;
27
- name?: string | null;
28
- } | undefined | null): void;
@@ -1 +0,0 @@
1
- export declare function resolvedToPageData(resolved: Record<string, unknown>, meta: Record<string, unknown>, request: Request, providers: string[] | undefined, regionSlots?: string[], themeOverride?: "light" | "dark"): Record<string, unknown>;
@@ -1,27 +0,0 @@
1
- /**
2
- * @system ui-service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Reads `page_route` registry rows for the current service slug and registers
7
- * Hono routes that resolve data via `resource_loader` graph-based sources.
8
- *
9
- * Every page_route uses pattern `/:id` and every resource_loader uses a
10
- * `sources[]` dependency graph. When header/footer sources are present in
11
- * the resolved graph, the output includes a `regions` array for shell rendering.
12
- * Otherwise it produces single-page puckData.
13
- */
14
- import type { RenderModeLiteral } from "./page-route-shell-types.ts";
15
- import type { Hono } from "hono";
16
- import type { CreatePageHandler, RendererComponentEntry } from "./types.ts";
17
- interface RegistryRouteArgs {
18
- app: Hono;
19
- serviceSlug: string;
20
- components: Record<string, RendererComponentEntry>;
21
- createPageHandler: CreatePageHandler;
22
- authMode: string;
23
- renderMode: RenderModeLiteral;
24
- providers: string[];
25
- }
26
- export declare function registerRegistryPageRoute(args: RegistryRouteArgs): Promise<string[]>;
27
- export {};
@@ -1,13 +0,0 @@
1
- /**
2
- * @system server-handlers
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Routes every service exposes regardless of registry config: /health
7
- * (liveness probe), /favicon.svg (default brand mark), /api/assets/*
8
- * (S3 asset proxy), and /assets/* (client bundle + stylesheet produced
9
- * by build-client-assets.ts into dist/client/, served identically in
10
- * dev and prod per scala-os/ui.md `bun-build-only`).
11
- */
12
- import type { Hono } from "hono";
13
- export declare function registerStaticAssets(app: Hono, serviceSlug: string, brandOrg?: string): Promise<void>;
@@ -1,28 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Registers each `service_install.websocket_routes[]` entry as a Hono WS route.
7
- * The ORPC WS (`/api/rpc/ws`) is mounted by the canonical primitive
8
- * `mountOrpcWs` (@teamscala/orpc/ws/server) over the FULLY-COMPOSED app router —
9
- * this runs AFTER registerFnRouters, so the eager RPCHandler snapshots every
10
- * namespace, and any `output: eventIterator(...)` procedure dispatches (rule
11
- * `orpc-ws-via-mount-orpc-ws`). It is NOT a server_package WsRouteHandler — it
12
- * is exempt from the legacy invariant below.
13
- *
14
- * The legacy `WsRouteHandler` interface (open/message/close on a Bun
15
- * ServerWebSocket) is preserved by an adapter for already-migrated
16
- * server_packages (terminal-ws); new WS handlers should use Hono's WSEvents
17
- * directly.
18
- *
19
- * INVARIANT (legacy paths only): every handler registered via
20
- * mountCtx.registerWsRoute MUST have a matching entry in config.websocket_routes
21
- * (minus /api/rpc/ws), and vice versa. A mismatch is fatal — the service cannot
22
- * boot with handlers declared but never wired (silent 404 on upgrade), or routes
23
- * that point at handlers no server_package registered.
24
- */
25
- import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
26
- import type { MountRegistry } from "@teamscala/os/runtime-contracts/mount-context";
27
- import type { Hono } from "hono";
28
- export declare function registerWsRoutes(app: Hono, config: ServiceInstallConfig, mountRegistry: MountRegistry, serviceSlug: string): Promise<number>;
@@ -1,13 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The scala-projects app's own public hostname, read from its `service_install`
7
- * row rather than hardcoded — a service knows its own deploy config only
8
- * through the registry (registries-are-source-of-truth), never a literal in
9
- * source. Cached for the process lifetime: this changes only on a redeploy,
10
- * not per-request. Shared by every server-side path that builds a link back
11
- * into the app (approval emails, mention emails, digest emails).
12
- */
13
- export declare function resolveAppBaseUrl(): Promise<string>;
@@ -1,28 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Host-level brand favicon resolution. A single-brand UI service's host has
7
- * a KNOWN owner (`service_install.config.brand_org`) — serving the generic
8
- * platform "S" at the implicit /favicon.ico path there is a defect: browsers
9
- * and crawlers hit that path directly and their favicon caches are sticky
10
- * (operator-observed 2026-06-05 on launcha's proposal pages).
11
- *
12
- * Resolution is by convention from the org's asset store:
13
- * `<brand_org>/logos/favicon.png`. Existence is probed once through the
14
- * in-process asset proxy and cached (positive AND negative) so the favicon
15
- * route never adds S3 latency per request. No brand_org, or no asset at the
16
- * convention key → null, and the caller serves the generic fallback.
17
- */
18
- export declare function resolveBrandFaviconPath(brandOrg: string | undefined): Promise<string | null>;
19
- /**
20
- * Eagerly resolve + cache the favicon at boot so the FIRST user request never
21
- * pays the S3 existence-probe latency. The probe (`handleAssetProxy` →
22
- * `s3.file(key).stat()`) is a COLD S3 call — the connection setup to the asset
23
- * store takes 6s locally / up to 21s in prod, and the in-process cache resets
24
- * on every restart (deploys, dep-sync). Without warming, the first
25
- * /favicon.ico after each restart stalls for that duration (operator-reported
26
- * 23s). Fire-and-forget — runs in the background during boot, never blocks.
27
- */
28
- export declare function warmBrandFaviconCache(brandOrg: string | undefined): void;
@@ -1,32 +0,0 @@
1
- /**
2
- * @system ui-service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Walks a resource_loader.sources[] graph and resolves each source in
7
- * dependency order. Returns an assembled data object keyed by source key.
8
- *
9
- * Source types: "orpc", "prisma", "resolver_fn".
10
- * Each source may declare depends_on for ordering.
11
- * Params are interpolated from URL params and prior resolved data.
12
- */
13
- interface SourceConfig {
14
- key: string;
15
- source: "orpc" | "prisma" | "resolver_fn";
16
- model?: string;
17
- where?: Record<string, unknown>;
18
- select?: string[];
19
- depends_on?: string[];
20
- optional?: boolean;
21
- resolver_fn?: string;
22
- resolver_input?: Record<string, unknown>;
23
- registry_filter?: Record<string, unknown>;
24
- transforms?: string[];
25
- }
26
- interface LoaderConfig {
27
- sources: SourceConfig[];
28
- merge_strategy?: Record<string, unknown>;
29
- }
30
- export declare function resolveFromLoaderConfig(config: LoaderConfig, params: Record<string, unknown>): Promise<Record<string, unknown>>;
31
- export declare function interpolateMeta(template: Record<string, unknown>, data: Record<string, unknown>): Record<string, unknown>;
32
- export {};
@@ -1,17 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Resolves the minimum service context the bin needs to choose its launch
7
- * path: connect DB, load registry, read service_install row, return kind +
8
- * port. Caller is responsible for disconnecting.
9
- */
10
- import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
11
- export interface ServiceContext {
12
- kind: "ui" | "backend" | "runtime";
13
- port: number;
14
- config: ServiceInstallConfig;
15
- disconnect: () => Promise<void>;
16
- }
17
- export declare function resolveServiceContext(serviceSlug: string, databaseUrl: string): Promise<ServiceContext>;
@@ -1,75 +0,0 @@
1
- /**
2
- * @system service-runtime
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Decides which port a service binds, from the two sources that can claim one:
7
- * the PORT environment variable and the port the registry declares for that
8
- * service (`service_routing.local_port` for a backend, `service_install.port`
9
- * for a UI service).
10
- *
11
- * This is the resolver for BOTH kinds. It lives in service-boot rather than
12
- * service-runtime because the UI branch boots from here and may not import
13
- * upward; keeping one copy is the point, since the second copy is exactly what
14
- * went wrong (below).
15
- *
16
- * The rule, and why it is not "env always wins":
17
- *
18
- * Railway injects PORT and healthchecks that exact port, reporting the service
19
- * unavailable if it listens elsewhere. So on Railway the injected PORT is
20
- * authoritative period — a `service_install`/`service_routing` row carries the
21
- * service's LOCAL dev port (e.g. portal 5200), and on Railway binding that
22
- * instead of PORT means Railway probes a port nothing listens on and the deploy
23
- * fails healthcheck. The registry-wins rule below exists to stop LOCAL port
24
- * collisions (one host, many services); inside a Railway container there are no
25
- * sibling services to collide with, so PORT must win. `isRailway` is the caller's
26
- * assertion that it is booting inside Railway (from `RAILWAY_ENVIRONMENT`).
27
- *
28
- * Off Railway, a `service_routing`/`service_install` row means this service has
29
- * a DECLARED port on this host. There, a PORT that disagrees is a polluted launcher environment, never
30
- * an intention, and obeying it binds someone else's port. That is not a
31
- * hypothetical: mcp-ai-chat-system booted onto 3021 — session-picker's port —
32
- * while its own MCP_SERVER_URL still pointed at 8080. Both processes bound 3021,
33
- * SO_REUSEPORT load-balanced between them, and every symptom appeared somewhere
34
- * else: a chat loopback that resolved to nothing, tool-description curation
35
- * failing for every tool and disabling itself via its circuit breaker, RPC calls
36
- * landing in the wrong service, and 59 restarts. Nothing failed loudly because
37
- * nothing ever compared the two numbers.
38
- *
39
- * So the registry wins wherever it has an opinion, and the disagreement is
40
- * REPORTED rather than silently resolved — a wrong port that boots cleanly is
41
- * the expensive shape, because the damage surfaces far from the cause.
42
- *
43
- * That reasoning was written for the backend branch and wired only there, so
44
- * the UI branch kept taking `process.env.PORT` unconditionally — and on
45
- * 2026-07-31 reproduced the identical incident in the other direction:
46
- * scala-quiz (kind:ui, declared 5203) inherited a leaked PORT=3021 from the
47
- * tmux server environment and bound session-picker's port. Half of every RPC
48
- * to the session daemon was answered by a quiz service with no /rpc routes.
49
- *
50
- * A rule enforced on one of two branches is not enforced. Both branches now
51
- * call this function, and `port-resolution-is-registry-authoritative` fails
52
- * the push if either stops.
53
- */
54
- export interface PortResolution {
55
- /** The port to bind. */
56
- port: number;
57
- /** Which source decided it — for the boot log, so the choice is auditable. */
58
- source: "env" | "service_routing";
59
- /** Set when PORT disagreed with the row; the caller logs it. */
60
- conflict?: {
61
- envPort: number;
62
- declaredPort: number;
63
- };
64
- }
65
- /**
66
- * @param envPort parsed PORT env, or undefined when unset/unparseable
67
- * @param declaredPort `service_routing.local_port`, or undefined when the
68
- * service has no routing row (the Railway-only case)
69
- * @param isRailway true when booting inside Railway (caller derives from
70
- * `RAILWAY_ENVIRONMENT`); there the injected PORT is authoritative
71
- * regardless of the row, because Railway healthchecks it
72
- */
73
- export declare function resolveServicePort(envPort: number | undefined, declaredPort: number | undefined, isRailway?: boolean): PortResolution | {
74
- error: "no-port-source";
75
- };
@@ -1,42 +0,0 @@
1
- /**
2
- * @system ui-service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The ONE theme-mode decider for every page render, across every UI app
7
- * (portal, proposals, sign, forms, quiz, gpt, public). Every route handler in service-boot
8
- * resolves light/dark through this single function — never its own inline
9
- * `(... === "dark" ? "dark" : "light")` chain. That inline form was duplicated
10
- * across every route branch, so flipping one app's theme required editing
11
- * every copy (the heavy lifting this removes).
12
- *
13
- * Resolution order (first match wins):
14
- * 1. ?theme=light|dark on the URL — the embed override. Set when a page is
15
- * framed inside another shell (a form in a GPT tool panel) so the framed
16
- * page matches the host's scheme. Also marks the render `embedded` so the
17
- * page can drop its own brand page-surface and render the host's clean
18
- * surface.
19
- * 2. candidates[] — page rootProps.theme, brand theme, website default, in
20
- * whatever priority the caller passes them.
21
- * 3. "light".
22
- *
23
- * Pairs with the brand/token decider (resolveBrand → computeThemeVars,
24
- * `unified-brand-theme`): that owns the SURFACE, this owns the MODE.
25
- */
26
- export interface ResolvedThemeMode {
27
- theme: "light" | "dark";
28
- /** True iff the mode came from the ?theme= embed override. */
29
- embedded: boolean;
30
- }
31
- export declare function resolveThemeMode(opts: {
32
- /** Request URL (or its href) — read for the ?theme= embed override. */
33
- url?: URL | string | null;
34
- /**
35
- * Explicit embed override, when the caller already extracted ?theme= from
36
- * the URL upstream (e.g. a pure data-shaper that has no Request). Same
37
- * effect and priority as the URL form.
38
- */
39
- override?: unknown;
40
- /** Theme candidates in priority order: rootTheme, brandTheme, websiteTheme. */
41
- candidates?: Array<unknown>;
42
- }): ResolvedThemeMode;
@@ -1,2 +0,0 @@
1
- import type { ResolverArgs } from "./portal-route-paths.ts";
2
- export declare function fetchOrgBrandWithFallback({ input, client }: ResolverArgs): Promise<unknown>;
@@ -1,39 +0,0 @@
1
- /**
2
- * @system ui-service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * portal-route-paths.ts — pure path mapper for portal region routing.
7
- *
8
- * Extracted from resolve-from-loader-config.ts (the hardcoded
9
- * `resolvePortalRoutePaths` arm) so it can be invoked via a resolver_fn
10
- * registry row instead of an if-chain. Faithful extraction — no logic change.
11
- * The request path is the center page's builda path ("/" falls back to
12
- * home_path), and the per-route toolbar page follows the flat naming convention
13
- * `/<last-segment>-toolbar`. Pages without a matching toolbar page simply
14
- * resolve an empty toolbar region (the source is declared optional).
15
- */
16
- export interface ResolverArgs {
17
- input: Record<string, unknown>;
18
- client: unknown;
19
- params: Record<string, unknown>;
20
- }
21
- /**
22
- * Extract the organisation id from a portal-shaped callback/path — the single
23
- * authority for "which segment is the org" so the sign-in brand resolver and the
24
- * route resolver cannot drift on the answer (portal-branding-resolves-page-org-first).
25
- *
26
- * The canonical portal shape is `/<websiteId>/<orgId>/<path>` — the org is
27
- * segment 1, NOT segment 0. Reading segment 0 fed the WEBSITE id into brand
28
- * resolution, which resolved no org and fell back to the super-admin org — the
29
- * exact cross-tenant mis-branding this exists to prevent (bind-page-handler.ts,
30
- * found 2026-07-21).
31
- *
32
- * Single-UUID hosts keep segment 0 unchanged: scala-projects routes `/<orgId>/…`
33
- * and proposal/form/quiz hosts route `/<resourceId>/…` (a non-org first segment
34
- * that resolves to no brand and falls back as before — no regression, only the
35
- * correct-org improvement for the two-UUID portal shape). Returns undefined when
36
- * neither leading segment is a UUID.
37
- */
38
- export declare function extractOrgIdFromCallbackPath(callbackUrl: string): string | undefined;
39
- export declare function resolvePortalRoutePaths({ input, params }: ResolverArgs): Record<string, unknown>;
@@ -1,12 +0,0 @@
1
- /**
2
- * @system ui-service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * website-by-host-map.ts — resolves the website + body path for a host.
7
- *
8
- * Extracted from resolve-from-loader-config.ts (the hardcoded
9
- * `resolveWebsiteByHostMap` arm). Faithful extraction — no logic change.
10
- */
11
- import type { ResolverArgs } from "./portal-route-paths.ts";
12
- export declare function resolveWebsiteByHostMap({ input, client, params }: ResolverArgs): Promise<Record<string, unknown> | null>;
@@ -1,46 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Injects the generic ResourceChatLauncher block into an AUTHORISED resource
7
- * page (proposal, agreement, or any gated document) — called from a gate's
8
- * onAuthorized, so the floating GPT bubble lives strictly behind the email gate
9
- * and every resource of that type gets it with no per-page Puck edit. It builds
10
- * the resource's page content into a runtime-context string and passes it to the
11
- * launcher, which forwards it to the chat as <runtime_context> — so ONE shared
12
- * assistant agent answers about whichever resource is being viewed.
13
- *
14
- * RESOURCE-AGNOSTIC by design (one-ai-chat-system / chat-ui-is-one-canonical-block):
15
- * the launcher, the chat engine, and this injector are generic; only the
16
- * per-resource CONFIG differs (which agent, the copy, which resolved key holds
17
- * the record). The agent id is config, not code (registries-are-source-of-truth):
18
- * each resource type names a `config / <slug>-assistant-agent` row carrying
19
- * { agentId }. No row (or no agent) → no injection, page renders unchanged
20
- * (fail-soft).
21
- */
22
- /** Per-resource-type injection config — the only thing that varies. */
23
- export interface ResourceChatOptions {
24
- /** Key in `resolved` holding the record (e.g. "proposal", "agreement"). */
25
- resourceKey: string;
26
- /** `config` row slug carrying { agentId } (e.g. "agreement-assistant-agent"). */
27
- agentConfigSlug: string;
28
- /** Org id to scope the chat. If omitted, read from resolved[resourceKey].organisation_id
29
- * (proposals); agreements pass it explicitly (org isn't on the resolved record). */
30
- organisationId?: string;
31
- /** Overlay title-bar label (e.g. "Agreement Assistant"). */
32
- title: string;
33
- /** Floating button label / speech bubble (e.g. "Ask about this agreement"). */
34
- launchLabel: string;
35
- /** First assistant message. */
36
- welcomeMessage?: string;
37
- /** Input placeholder. */
38
- placeholder?: string;
39
- /** Suggested starter questions. */
40
- conversationStarters?: string[];
41
- }
42
- /**
43
- * Inject the generic ResourceChatLauncher behind the gate. `opts` is the only
44
- * per-resource-type variation; everything else is shared.
45
- */
46
- export declare function injectResourceChatLauncher(resolved: Record<string, unknown>, viewerEmail: string, opts: ResourceChatOptions): Promise<void>;
@@ -1,60 +0,0 @@
1
- /**
2
- * @system service-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- * @indivisible-unit
6
- *
7
- * One cohesive unit — the branded resource-not-found / holding page: the
8
- * PrimaryResourceMissingError trigger, the org-logo resolver the page
9
- * renders, and the HTML builder are tightly coupled (the builder consumes
10
- * the logo; the error drives the page) and cannot be split without churn.
11
- *
12
- * Branded 404 for registry page routes whose PRIMARY resource is missing.
13
- *
14
- * A route that declares `resource_type` (proposal, form, quiz, agreement…)
15
- * is meaningless without that entity. Before this existed, a dead link
16
- * (wrong id, deleted row) fell through the loader with nulls and rendered
17
- * whatever default content the shell could scrape together — leaking
18
- * internal pages (the "Vision Toolbar" incident, operator-observed
19
- * 2026-06-05) on the org's own branded host, with a 200 status that
20
- * search engines and link unfurlers would happily cache.
21
- *
22
- * The replacement: loadData throws PrimaryResourceMissingError; the page
23
- * binder maps it to a minimal branded 404 — correct status, noindex, the
24
- * host's own favicon (which resolves the owning org per brand_org), and a
25
- * plain human sentence. Deliberately NOT a puck page: the error surface
26
- * must render even when data loading is the thing that failed. A
27
- * website-editable /404 puck page can layer on top later; this is the
28
- * floor that must always exist.
29
- */
30
- /**
31
- * The host org's LOGO (wordmark) for the dark error page — the favicon is a
32
- * tab glyph, the wrong asset at page scale (operator-observed: a lone "h"
33
- * where the launcha wordmark belonged). Resolution: the service's own
34
- * install row names its owner (brand_org); the org's dark-theme logo (a
35
- * light-coloured mark, correct on the dark page) is preferred, light-theme
36
- * second. Every step skew-tolerant: any miss → null → the builder renders a
37
- * logo-less dark page.
38
- *
39
- * Returns the logo as an INLINE data URI (not a `/api/assets/` proxy URL) so
40
- * the branded holding/error page ships the logo IN the initial HTML bytes —
41
- * no second fetch, no post-paint pop-in, no dependency on the asset proxy the
42
- * degraded service is failing to serve (incident 2026-07-27: the logo on a
43
- * DEGRADED render-layer holding page appeared after the page painted). The
44
- * logo sibling of icons-render-ssr-inline. Size-capped: a pathological
45
- * (>MAX_INLINE_LOGO_BYTES) logo falls back to null rather than bloating the
46
- * HTML; the page renders logo-less, never broken.
47
- */
48
- export declare function resolveErrorPageLogo(serviceSlug: string): Promise<string | null>;
49
- export declare class PrimaryResourceMissingError extends Error {
50
- readonly resourceType: string;
51
- readonly httpStatus: number;
52
- readonly empty: boolean;
53
- constructor(resourceType: string, httpStatus?: number, opts?: {
54
- empty?: boolean;
55
- });
56
- }
57
- export declare function buildResourceNotFoundResponse(resourceType: string, logoUrl?: string | null, status?: number, opts?: {
58
- homeUrl?: string;
59
- empty?: boolean;
60
- }): Response;
@@ -1,19 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Runs the service's boot hooks. A server_package registers a hook via
7
- * ctx.registerBootHook during the mount phase; a hook registered by an
8
- * INSTALLED server_package is intended to run (the package is installed
9
- * because the service wants it). This therefore invokes EVERY registered
10
- * hook — those declared in `service_install.boot_hooks[]` first (preserving
11
- * their declared order), then any registered-but-undeclared hooks. Requiring a
12
- * hook to be BOTH registered by its package AND re-declared in boot_hooks is a
13
- * double-declaration footgun — it left the realtime-utils pg-notify bridge
14
- * registered-but-never-invoked (boot_hooks was null), so the live-refresh
15
- * relay never started (2026-06-29).
16
- */
17
- import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
18
- import type { MountRegistry } from "@teamscala/os/runtime-contracts/mount-context";
19
- export declare function runBootHookList(config: ServiceInstallConfig, mountRegistry: MountRegistry, serviceSlug: string): Promise<void>;
@@ -1,31 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Runs service_install.boot_hooks in declaration order. A boot hook is a
7
- * registry-declared, one-shot startup function that needs to execute after
8
- * prisma + registry are ready but before the HTTP server starts serving.
9
- * Used for custom startup logic (stale-run reconciliation, cache hydration,
10
- * ALS seeding, long-running watcher start) that doesn't fit the pg-boss
11
- * recurring model and is too specific to be a generic installer step.
12
- *
13
- * Each hook is resolved by dynamic-importing its handler_slug (or slug when
14
- * handler_slug is absent) and awaiting its default export. The export must
15
- * be an async function accepting a BootHookContext. Hooks are invoked in
16
- * ascending order of their `order` field (default 100).
17
- *
18
- * Failures are fatal. The installer has no partial-boot concept: if a
19
- * declared hook cannot run, the service is broken, and booting anyway would
20
- * mask the regression. BootFatalError is thrown to propagate to the
21
- * top-level boot-error handler.
22
- */
23
- import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
24
- export interface BootHookContext {
25
- serviceSlug: string;
26
- config: ServiceInstallConfig;
27
- }
28
- export type BootHookHandler = (ctx: BootHookContext) => Promise<void> | void;
29
- export declare function runBootHooks(serviceSlug: string, config: ServiceInstallConfig): Promise<{
30
- ran: string[];
31
- }>;
@@ -1,10 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Polls the installed scala-tools package against GitHub Packages and repairs
7
- * stale daemon tooling by reinstalling and letting PM2 restart the process.
8
- */
9
- export declare function startScalaToolsCurrencyWatchdog(): void;
10
- export declare function stopScalaToolsCurrencyWatchdog(): void;
@@ -1,12 +0,0 @@
1
- /**
2
- * @system server-boot
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Middleware that injects security headers into every response.
7
- * Uses Hono's secureHeaders as the base for standard headers (X-Content-Type-Options,
8
- * X-Frame-Options, Referrer-Policy, etc.), then layers registry-driven CSP with
9
- * nonce injection on top.
10
- */
11
- import type { Context, Next } from "hono";
12
- export declare function injectSecurityHeaders(c: Context, next: Next): Promise<void>;
@@ -1,46 +0,0 @@
1
- /**
2
- * @system runtime-serve
3
- * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Listener primitives. Provides the Bun.serve production listener (serveStart)
7
- * and the WebSocket upgrade helper (upgradeWebSocket) that work identically
8
- * dev and prod.
9
- */
10
- import type { Hono } from "hono";
11
- /**
12
- * Hono `upgradeWebSocket` helper bound to the same `createBunWebSocket()`
13
- * pair that drives Bun.serve.websocket. Use exclusively from this package so
14
- * the upgrade path and the message dispatch share state.
15
- */
16
- export declare const upgradeWebSocket: import("hono/ws").UpgradeWebSocket<unknown, any, import("hono/ws").WSEvents<unknown>>;
17
- export interface ServeOptions {
18
- /** TCP port to bind. */
19
- port: number;
20
- /** For logging only. */
21
- serviceSlug: string;
22
- /**
23
- * Optional logger; defaults to console.
24
- */
25
- logger?: {
26
- info(msg: string): void;
27
- warn(msg: string): void;
28
- };
29
- }
30
- export interface ServeHandle {
31
- server: ReturnType<typeof Bun.serve>;
32
- stop(): void;
33
- }
34
- /**
35
- * Start Bun.serve listening on the configured port. Used by:
36
- * - bootUiService when running the production bundle (`bun dist/server/index.js`)
37
- * - bootBackendService for non-UI services
38
- *
39
- * UI services in dev mode also call this — they run `bun src/index.ts`
40
- * directly under PM2 with the same Bun.serve listener.
41
- *
42
- * Two-phase boot variant: when useMutableRef is true, `app` is used as the
43
- * initial app for the mutable reference (minimal tool-only surface), and the
44
- * caller can swap to the full app later via swapApp().
45
- */
46
- export declare function serveStart(app: Hono, options: ServeOptions, useMutableRef?: boolean): ServeHandle;