@teamscala/service-boot 0.23.523 → 0.23.524
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/package.json +1 -1
- package/src/agreement-gate/agreement-lookup.ts +0 -7
- package/src/agreement-gate/authorized-emails.ts +0 -7
- package/src/agreement-gate/placeholder-strip.ts +0 -8
- package/src/agreement-gate/resolution-caches.ts +0 -5
- package/src/agreement-gate/settings.ts +0 -7
- package/src/agreement-gate/signing-session.ts +0 -8
- package/src/agreement-gate/signing-shell.ts +0 -9
- package/src/agreement-gate.ts +0 -18
- package/src/apply-logger-config.ts +1 -9
- package/src/asset-cache-key.ts +1 -5
- package/src/asset-optimize.ts +1 -7
- package/src/asset-proxy.ts +1 -10
- package/src/asset-social-card.ts +1 -9
- package/src/attach-agreement-magic.ts +0 -14
- package/src/attach-portal-members.ts +0 -17
- package/src/attach-proposal-accept.ts +0 -20
- package/src/attach-proposal-magic.ts +0 -12
- package/src/attach-transfers-upload.ts +0 -21
- package/src/auth/sign-out.ts +1 -42
- package/src/auth-gate/public-paths.ts +1 -10
- package/src/auth-gate.ts +1 -22
- package/src/bind-page-handler.ts +1 -7
- package/src/build-client-assets/build-lock.ts +1 -11
- package/src/build-client-assets/hashed-output.ts +1 -11
- package/src/build-client-assets/hydrate-bundle.ts +1 -11
- package/src/build-client-assets/manifest.ts +1 -12
- package/src/build-client-assets/source-candidates.ts +0 -17
- package/src/build-client-assets/sourcemaps-upload.ts +1 -10
- package/src/build-client-assets/styles-build.ts +1 -21
- package/src/build-client-assets.ts +1 -30
- package/src/capability-slug.ts +1 -35
- package/src/compose-page-title.ts +1 -25
- package/src/content-gate/access.ts +0 -8
- package/src/content-gate/catalog.ts +0 -23
- package/src/content-gate/config-types.ts +0 -6
- package/src/content-gate/gate-entry-clears.ts +0 -18
- package/src/content-gate/gate.ts +0 -7
- package/src/content-gate/parse-cookie.ts +0 -4
- package/src/content-gate/token.ts +0 -22
- package/src/content-page-floor.ts +0 -17
- package/src/csrf-protection.ts +1 -28
- package/src/error-page-logo.ts +0 -23
- package/src/expand-roster.ts +1 -16
- package/src/gate-ttls.ts +0 -12
- package/src/gpt-gate.ts +0 -18
- package/src/hono-error-handler.ts +0 -15
- package/src/install-harness.ts +1 -12
- package/src/install-logging.ts +1 -15
- package/src/is-react-element-type.ts +1 -11
- package/src/load-page-manifest.ts +1 -23
- package/src/load-service-install-row.ts +1 -6
- package/src/module-not-found.ts +0 -6
- package/src/mount-orpc-router.ts +0 -6
- package/src/mount-server-packages.ts +1 -8
- package/src/mount.ts +1 -4
- package/src/mutable-app-ref.ts +0 -10
- package/src/orpc-openapi-document.ts +1 -13
- package/src/orpc-openapi-gate.ts +1 -13
- package/src/page-route-shell-types.ts +1 -8
- package/src/page-server-loader-graph.ts +0 -10
- package/src/page-server-orpc/hub-mcp-client.ts +1 -7
- package/src/page-server-orpc/server-client.ts +1 -23
- package/src/pg-boss-handler-registry.ts +1 -12
- package/src/pg-boss-mount.ts +1 -8
- package/src/proposal-authorized-emails.ts +0 -32
- package/src/proposal-gate.ts +0 -11
- package/src/quiz-gate.ts +0 -22
- package/src/read-html-template.ts +1 -49
- package/src/readiness-gate.ts +0 -8
- package/src/register-api-routes.ts +1 -11
- package/src/register-crash-handlers.ts +1 -6
- package/src/register-page-routes.ts +1 -12
- package/src/register-page-server-client.ts +1 -13
- package/src/register-registry-page-route/ai-chat-prefetch.ts +1 -6
- package/src/register-registry-page-route/apply-page-branding.ts +1 -10
- package/src/register-registry-page-route/captured-loader-config.ts +1 -11
- package/src/register-registry-page-route/graph-cache.ts +1 -21
- package/src/register-registry-page-route/live-loader-config.ts +1 -22
- package/src/register-registry-page-route/meta-title.ts +1 -12
- package/src/register-registry-page-route/page-data-shared.ts +1 -12
- package/src/register-registry-page-route/prefetch-org-id.ts +1 -7
- package/src/register-registry-page-route/proposal-approval-email-token.ts +1 -11
- package/src/register-registry-page-route/proposal-brand-stamping.ts +1 -11
- package/src/register-registry-page-route/public-shell-page-data.ts +1 -8
- package/src/register-registry-page-route/region-slots-page-data.ts +1 -8
- package/src/register-registry-page-route/resolve-page-data.ts +1 -10
- package/src/register-registry-page-route/resolve-panel-page.ts +1 -10
- package/src/register-registry-page-route/resolve-request-graph.ts +1 -11
- package/src/register-registry-page-route/run-content-gates.ts +1 -10
- package/src/register-registry-page-route/single-page-data.ts +1 -9
- package/src/register-registry-page-route/stamp-session-context.ts +1 -10
- package/src/register-registry-page-route/warn-uncovered-blocks.ts +1 -8
- package/src/register-registry-page-route.ts +1 -18
- package/src/register-static-assets.ts +1 -8
- package/src/register-ws-routes.ts +1 -21
- package/src/resolve-brand-favicon.ts +0 -13
- package/src/resolve-service-port.ts +1 -50
- package/src/resolve-theme-mode.ts +1 -22
- package/src/resolvers/app-header.ts +1 -19
- package/src/resolvers/fetch-org-brand.ts +1 -10
- package/src/resolvers/portal-route-paths.ts +1 -12
- package/src/resolvers/website-by-host-map.ts +1 -7
- package/src/resource-chat-inject.ts +0 -17
- package/src/resource-not-found.ts +0 -24
- package/src/run-boot-hook-list.ts +1 -13
- package/src/run-boot-hooks.ts +1 -19
- package/src/scala-tools-currency-watchdog.ts +1 -7
- package/src/security-headers.ts +1 -7
- package/src/select-service-routes.ts +1 -17
- package/src/server-error-page.ts +0 -18
- package/src/theme-fonts-link.ts +1 -16
- package/src/transfers/presigned-upload.ts +1 -27
- package/src/two-phase-full-app.ts +0 -6
- package/src/two-phase-minimal-app.ts +0 -6
- package/src/two-phase-readiness.ts +0 -6
- package/src/types.ts +0 -3
- package/src/ui-service.ts +1 -6
- package/src/ws-boot-self-test.ts +1 -14
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Stamps the resolved-in-progress loader graph with the request's session
|
|
7
|
-
* identity: sessionVisitor, portalContext (user/orgId/pane widths from the
|
|
8
|
-
* portal cookie and the user's saved preferences). Anonymous when no session.
|
|
9
|
-
*
|
|
10
|
-
* Split out of register-registry-page-route.ts (924 lines) — the loadData
|
|
11
|
-
* closure segment moved byte-identical; only the closure variables became
|
|
12
|
-
* explicit parameters.
|
|
13
4
|
*/
|
|
14
5
|
|
|
15
6
|
import { getServerClient } from "../page-server-orpc/server-client.ts";
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Block-coverage guard (protective-layers-fail-explicitly) for registered
|
|
7
|
-
* page routes.
|
|
8
|
-
*
|
|
9
|
-
* Split out of register-registry-page-route.ts (924 lines) — byte-for-byte
|
|
10
|
-
* move; only the import paths adjusted.
|
|
11
4
|
*/
|
|
12
5
|
|
|
13
6
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
|
-
* This module is the ROUTE-REGISTRATION unit: row loading, specificity
|
|
15
|
-
* ordering, handler binding. The per-request loadData pipeline segments live
|
|
16
|
-
* as siblings under ./register-registry-page-route/ (graph-cache,
|
|
17
|
-
* resolve-request-graph, stamp-session-context, run-content-gates,
|
|
18
|
-
* apply-page-branding, resolve-panel-page, warn-uncovered-blocks,
|
|
19
|
-
* resolve-page-data, meta-title, ai-chat-prefetch, captured-loader-config,
|
|
20
|
-
* proposal-*) — split when the parent held them all at 924 lines.
|
|
21
4
|
*/
|
|
22
5
|
|
|
23
6
|
import type { InferOutput } from "valibot";
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
12
5
|
|
|
13
6
|
import { join } from "node:path";
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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 the app router composition, 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
4
|
*/
|
|
25
5
|
|
|
26
6
|
import { getAppRouter } from "@teamscala/orpc/app-router";
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
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
4
|
*/
|
|
18
5
|
|
|
19
6
|
import { createCache } from "@teamscala/cache/create-cache";
|
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system service-
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
54
5
|
|
|
55
6
|
export interface PortResolution {
|
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
26
5
|
|
|
27
6
|
export interface ResolvedThemeMode {
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* app-header.ts — shared application header resolver_fn.
|
|
7
|
-
*
|
|
8
|
-
* Shapes an app's per-row resolver_input into the canonical header-data object
|
|
9
|
-
* the ONE shared header Puck page (`/shared-app-header`) binds through the
|
|
10
|
-
* unified page-data surface (`dataSource:"page-data"` + `fieldPath:"headerData.<key>"`).
|
|
11
|
-
* The loader row owns the mapping (title field path, label/value pairs — the
|
|
12
|
-
* per-app variation); this executor owns the shape and the two coercions the
|
|
13
|
-
* rows cannot express:
|
|
14
|
-
*
|
|
15
|
-
* - `Date` values (Prisma returns Date objects) → ISO strings, so
|
|
16
|
-
* __PAGE_DATA__ serialises them the same way the client reads them
|
|
17
|
-
* (server-serializes-client-reads-verbatim).
|
|
18
|
-
* - the literal `"today"` → a noon-UTC ISO instant of the AEST calendar date.
|
|
19
|
-
* Noon keeps the calendar date stable under every timezone offset, so the
|
|
20
|
-
* header shows the same day on the server and on any visitor's client —
|
|
21
|
-
* the deterministic replacement for the old `format:"today"` clock block.
|
|
22
4
|
*/
|
|
23
5
|
|
|
24
6
|
/** AEST calendar date (the operator timezone) as `YYYY-MM-DD`. */
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* fetch-org-brand.ts — org brand resolver with page-org-first fallback.
|
|
7
|
-
*
|
|
8
|
-
* Wraps @teamscala/resolver-engine's fetchOrgBrand with the page-org-first
|
|
9
|
-
* fallback (portal-branding-resolves-page-org-first): if the page (center) org
|
|
10
|
-
* carried no brand row, fall back to the website owning-org so pages with no
|
|
11
|
-
* tenant org render the website's brand, not a blank. Extracted from the
|
|
12
|
-
* hardcoded `fetchOrgBrand` arm in resolve-from-loader-config.ts.
|
|
13
4
|
*/
|
|
14
5
|
import { fetchOrgBrand } from "@teamscala/resolver-engine/data-resolvers/fetch-org-brand";
|
|
15
6
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
16
5
|
/**
|
|
17
6
|
* Extract the organisation id from a portal-shaped callback/path — the single
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
11
5
|
import type { ResolverArgs } from "./portal-route-paths.ts";
|
|
12
6
|
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
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
4
|
*/
|
|
22
5
|
|
|
23
6
|
/**
|
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* One cohesive unit — the branded resource-not-found / holding page FLOOR:
|
|
7
|
-
* the PrimaryResourceMissingError trigger and the HTML builder (the builder
|
|
8
|
-
* consumes a logoUrl argument; the org-logo resolvers live in
|
|
9
|
-
* error-page-logo.ts).
|
|
10
|
-
*
|
|
11
|
-
* Branded 404 for registry page routes whose PRIMARY resource is missing.
|
|
12
|
-
*
|
|
13
|
-
* A route that declares `resource_type` (proposal, form, quiz, agreement…)
|
|
14
|
-
* is meaningless without that entity. Before this existed, a dead link
|
|
15
|
-
* (wrong id, deleted row) fell through the loader with nulls and rendered
|
|
16
|
-
* whatever default content the shell could scrape together — leaking
|
|
17
|
-
* internal pages (the "Vision Toolbar" incident, operator-observed
|
|
18
|
-
* 2026-06-05) on the org's own branded host, with a 200 status that
|
|
19
|
-
* search engines and link unfurlers would happily cache.
|
|
20
|
-
*
|
|
21
|
-
* The replacement: loadData throws PrimaryResourceMissingError; the page
|
|
22
|
-
* binder maps it to a minimal branded 404 — correct status, noindex, the
|
|
23
|
-
* host's own favicon (which resolves the owning org per brand_org), and a
|
|
24
|
-
* plain human sentence. Deliberately NOT a puck page: the error surface
|
|
25
|
-
* must render even when data loading is the thing that failed. A
|
|
26
|
-
* website-editable /404 puck page can layer on top later; this is the
|
|
27
|
-
* floor that must always exist.
|
|
28
4
|
*/
|
|
29
5
|
|
|
30
6
|
import { brandLogoImgTag } from "@teamscala/os/brand/brand-logo-img-tag";
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
17
5
|
|
|
18
6
|
import type { ServiceInstallConfig } from "./load-service-install-row";
|
package/src/run-boot-hooks.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
23
5
|
|
|
24
6
|
import type { ServiceInstallConfig } from "./load-service-install-row";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
|
-
* The fetch routes through `getGithubPackagesLatestVersion` so the URL
|
|
9
|
-
* encoding and GitHub-Packages auth header live in the typed primitive.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
import { recordEvent } from "@teamscala/event-log/record-event";
|
package/src/security-headers.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
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
4
|
*/
|
|
11
5
|
|
|
12
6
|
import { getConfigObject } from "@teamscala/db/registry/config";
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Selecting and ordering the `page_route` rows one service serves.
|
|
7
|
-
*
|
|
8
|
-
* Two decisions live here, both about WHICH rows and IN WHAT ORDER — never
|
|
9
|
-
* about what a route does once matched:
|
|
10
|
-
*
|
|
11
|
-
* 1. SELECTION joins on the CAPABILITY, never on the deployable name. A
|
|
12
|
-
* service arrives under its LANE identity (`scala-projects-bun-dev` in
|
|
13
|
-
* dev, `scala-projects` in prod), while a page_route row names the
|
|
14
|
-
* APPLICATION and there is ONE row for both lanes. Raw string equality
|
|
15
|
-
* therefore matched at most one lane, silently — see `./capability-slug.ts`
|
|
16
|
-
* for the measured incident.
|
|
17
|
-
*
|
|
18
|
-
* 2. ORDERING puts specific patterns before parameterised and wildcard ones,
|
|
19
|
-
* because Hono matches in REGISTRATION order.
|
|
20
4
|
*/
|
|
21
5
|
|
|
22
6
|
import { toCapabilitySlug } from "./capability-slug.ts";
|
package/src/server-error-page.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The branded HTML error page for a FAILED PAGE RENDER — the page-server
|
|
7
|
-
* render catch's response surface (constitution `error-responses-via-error-
|
|
8
|
-
* report`). When SSR resolve+render fails (render budget exhausted, DEGRADED
|
|
9
|
-
* protective layer, a thrown loader), the browser is mid-navigation: serving
|
|
10
|
-
* a JSON body there is the violation this closes (2026-07-24: the operator
|
|
11
|
-
* saw a raw `{"error":"Request failed"}` blob where the dashboard should be).
|
|
12
|
-
*
|
|
13
|
-
* Classification stays in `buildErrorReport` (`@teamscala/os`) — the ONE
|
|
14
|
-
* classifier; this file is ONLY the HTML shaper for the page-navigation
|
|
15
|
-
* surface (API/ORPC `onError` keeps the JSON body, consumed by code). It
|
|
16
|
-
* renders the report's already-decided status + Retry-After + message, so the
|
|
17
|
-
* screen, the log line, and the GlitchTip event all share one `errorId`.
|
|
18
|
-
*
|
|
19
|
-
* Deliberately NOT a Puck page: it must render even when data loading is what
|
|
20
|
-
* failed. Sibling of `resource-not-found.ts` (the missing-resource floor) —
|
|
21
|
-
* same dark branded template + org-logo resolver, distinct trigger.
|
|
22
4
|
*/
|
|
23
5
|
|
|
24
6
|
import { escapeHtml } from "@teamscala/formatters/escapeHtml";
|
package/src/theme-fonts-link.ts
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Derives the document head's Google Fonts link from the `css_theme` row's
|
|
7
|
-
* `fonts` map — the ONE source for the families a page's variants reference
|
|
8
|
-
* (`the-theme-decides-colour-not-the-component` extends to type). The old
|
|
9
|
-
* envelope hard-coded Inter, so a proposal heading naming a manuscript serif
|
|
10
|
-
* ("Playfair Display") never had its family loaded and fell back to Times
|
|
11
|
-
* (2026-09-02). Now every family the row declares is loaded; a family a
|
|
12
|
-
* variant names that the row does not declare is a row defect, caught at the
|
|
13
|
-
* row's schema boundary.
|
|
14
|
-
*
|
|
15
|
-
* Read from the boot registry cache (captured at service boot, the same
|
|
16
|
-
* semantics as the resource_loader capture) and guarded: a missing or
|
|
17
|
-
* unreadable row degrades to NO fonts link — system fonts render, the page
|
|
18
|
-
* still works. This module must never throw into the document builder.
|
|
19
4
|
*/
|
|
20
5
|
|
|
21
6
|
import { getEntryConfig } from "@teamscala/db/registry/accessors";
|
|
@@ -1,32 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The fn.transfers.presignedUpload procedure body (api_route
|
|
7
|
-
* `fn-transfers-presigned-upload`, authed: input + context). It replaces the
|
|
8
|
-
* FormData POST /api/transfers/upload mount for every UI caller: the browser
|
|
9
|
-
* asks for a SIGNED PUT URL for one key, uploads the bytes straight to the
|
|
10
|
-
* store, and persists the SERVABLE key — so the file never crosses the
|
|
11
|
-
* service and the call is one JSON request instead of a multipart body.
|
|
12
|
-
*
|
|
13
|
-
* The policy the mount carried lives here, and is the SAME policy: the
|
|
14
|
-
* MAX_FILE_SIZE / ALLOWED_TYPES refusals (with their 413/415 statuses intact
|
|
15
|
-
* through the ORPC codes below), the USER-scoped key scheme
|
|
16
|
-
* (`users/<userId>/<dir>/<dir-singular>-<ts>.<ext>`), the unknown-dir
|
|
17
|
-
* fallback to `uploads`, and the servable value returned as `url` with
|
|
18
|
-
* `thumbUrl` mirroring it (the stored value is the servable value —
|
|
19
|
-
* single-logo-pathway, so display code reads the column with no rewriting).
|
|
20
|
-
* The key is derived from `context.user.id`, never from input: a caller
|
|
21
|
-
* cannot mint a URL under another user's prefix.
|
|
22
|
-
*
|
|
23
|
-
* HONEST RESIDUAL: the size refusal reads the DECLARED fileSize, because the
|
|
24
|
-
* bytes no longer pass through here — the store enforces the signature's type
|
|
25
|
-
* and expiry, not a byte count. The mount's Content-Length check was equally
|
|
26
|
-
* a declared-value check (an attacker sent the bytes either way); what
|
|
27
|
-
* changes is that a lying client is now bounded by the URL's TTL instead of a
|
|
28
|
-
* boot-level body limit. A real byte bound belongs to the bucket, not to this
|
|
29
|
-
* handler.
|
|
3
|
+
* @status handwritten
|
|
30
4
|
*/
|
|
31
5
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
32
6
|
import { getPresignedUrl } from "@teamscala/object-storage/create/presigned-url";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Phase 2 of the two-phase boot: the full app. Adds the expensive operations
|
|
7
|
-
* deferred from phase 1 (two-phase-minimal-app.ts) — client asset building
|
|
8
|
-
* (Vite build), pg-boss, the readiness/page-handler flip — and is swapped
|
|
9
|
-
* in atomically once complete.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
/**
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Phase 1 of the two-phase boot: the minimal tool-only app (ORPC, data
|
|
7
|
-
* routes, auth gates) that serves immediately (<10s); phase 2
|
|
8
|
-
* (two-phase-full-app.ts) builds the full app in the background and swaps
|
|
9
|
-
* it in atomically — eliminating the ~25s boot outage window.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
/**
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Phase-2 readiness state box for the two-phase boot: the real page handler +
|
|
7
|
-
* readiness flags (private lets), the writers/flippers, the `/health` endpoint
|
|
8
|
-
* that gates on them, and the phase-1 page handler that delegates to the real
|
|
9
|
-
* handler once phase 2 completes. Every reader goes through these exports.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
/**
|
package/src/types.ts
CHANGED
package/src/ui-service.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Boots a `kind: "ui"` service. Reads the service_install registry row and
|
|
7
|
-
* orchestrates a fixed pipeline of single-purpose stages onto one Hono app
|
|
8
|
-
* served by @teamscala/service-runtime/serve (one Bun.serve in dev and prod).
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
/**
|
package/src/ws-boot-self-test.ts
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Post-listen WebSocket self-test. Runs after serveStart binds the port.
|
|
7
|
-
* For each declared websocket_routes path, opens a synthetic WebSocket
|
|
8
|
-
* connection and asserts 101 upgrade. Non-101 → BootFatalError.
|
|
9
|
-
*
|
|
10
|
-
* This catches the class of bugs where:
|
|
11
|
-
* - WS route registered after ORPC catch-all (order-dependent 404)
|
|
12
|
-
* - fetchWithPeerPort clones the Request on WS upgrade (breaks Bun.upgrade identity)
|
|
13
|
-
* - websocket_routes config row is stale / missing a handler
|
|
14
|
-
*
|
|
15
|
-
* Runs synchronously after serveStart so the boot aborts before the service
|
|
16
|
-
* reports healthy.
|
|
17
4
|
*/
|
|
18
5
|
|
|
19
6
|
import type { ServiceInstallConfig } from "./load-service-install-row";
|