@teamscala/service-boot 0.23.522 → 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 +15 -9
- 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,35 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Builds the per-service client bundle and stylesheet into `dist/client/`
|
|
7
|
-
* at UI-service boot time. Outputs are content-hashed so deployments
|
|
8
|
-
* naturally cache-bust:
|
|
9
|
-
*
|
|
10
|
-
* src/entry-client.ts ──bun build──> dist/client/hydrate-<hash>.js
|
|
11
|
-
* src/app.css ──tailwindcss──> dist/client/styles-<hash>.css
|
|
12
|
-
* (and) dist/client/asset-manifest.json
|
|
13
|
-
*
|
|
14
|
-
* The HTML template references `{{stylesPath}}` / `{{hydratePath}}` —
|
|
15
|
-
* read-html-template.ts substitutes from the manifest at template-read
|
|
16
|
-
* time. Bytes-level changes → new hash → new URL → browser refetch.
|
|
17
|
-
* Without this, `cache-control: immutable` on a fixed filename means
|
|
18
|
-
* returning visitors keep their stale copy forever.
|
|
19
|
-
*
|
|
20
|
-
* This module is the ORCHESTRATION unit: host-wide lock → parallel
|
|
21
|
-
* hydrate/CSS builds → manifest write (or failure tombstone). The
|
|
22
|
-
* build runs on EVERY boot: the content-fingerprint skip cache and its
|
|
23
|
-
* closure-walking hashers (input-fingerprint.ts, input-hashing.ts) are
|
|
24
|
-
* deleted. A skip is the only path that can serve a stale bundle, and
|
|
25
|
-
* it has done so twice (the mtime skip that baked stale bundles until
|
|
26
|
-
* the fingerprint replaced it, 2026-06-14) — no skip is the one shape
|
|
27
|
-
* that cannot skip wrongly, at the cost of a bounded per-boot build
|
|
28
|
-
* serialised by the host-wide lock below. The individual concerns live
|
|
29
|
-
* as siblings under ./build-client-assets/
|
|
30
|
-
* (source-candidates, manifest, hashed-output, hydrate-bundle,
|
|
31
|
-
* sourcemaps-upload, styles-build, build-lock)
|
|
32
|
-
* — split when the parent held them all at 962 lines.
|
|
33
4
|
*/
|
|
34
5
|
|
|
35
6
|
// Each build is wrapped in its own catch so a JS bundle failure (e.g. transient cross-package export drift) doesn't take down the service
|
package/src/capability-slug.ts
CHANGED
|
@@ -1,40 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Deployable slug -> capability slug.
|
|
7
|
-
*
|
|
8
|
-
* A DEPLOYABLE is named `<capability>-<language>-<tier>`
|
|
9
|
-
* (`deployables-carry-language-and-tier`, as amended by the operator ruling of
|
|
10
|
-
* 2026-09-08: "so actually the apps are ts, not bun" — the language segment
|
|
11
|
-
* spells the LANGUAGE, so Bun-launched services are `ts`): `scala-projects-ts-dev`
|
|
12
|
-
* and `scala-projects-ts-prod` are two DEPLOYMENTS of one APPLICATION. The
|
|
13
|
-
* ecosystem generator derives that name per lane, so one service arrives here
|
|
14
|
-
* under a DIFFERENT identity in each lane.
|
|
15
|
-
*
|
|
16
|
-
* A `page_route` row describes the APPLICATION url space, which is the same
|
|
17
|
-
* in both lanes, so it carries the CAPABILITY name and there is exactly one
|
|
18
|
-
* row per route rather than one per lane. Joining the two by raw string
|
|
19
|
-
* equality therefore matches at most one lane, and does so SILENTLY:
|
|
20
|
-
* measured 2026-09-11, 8 of 10 dev UI services registered ZERO page routes
|
|
21
|
-
* and served 404 on every path -- including `/sign-in` -- while booting
|
|
22
|
-
* clean and reporting healthy, because the dev lane ran as
|
|
23
|
-
* `scala-projects-bun-dev` while every page_route row said `scala-projects`.
|
|
24
|
-
*
|
|
25
|
-
* Both sides normalize through this one function, so the join is
|
|
26
|
-
* tier-agnostic by construction and a row may name either form.
|
|
27
|
-
*
|
|
28
|
-
* The suffix grammar is a CLOSED SET defined by doctrine and emitted by the
|
|
29
|
-
* generator, so this is a parse of a documented contract rather than a
|
|
30
|
-
* pattern heuristic. A slug that does not end in one is already a capability
|
|
31
|
-
* name and is returned unchanged.
|
|
32
|
-
*
|
|
33
|
-
* `bun` REMAINS IN THE SET AS RETIRED SPELLING TOLERANCE, mirroring the
|
|
34
|
-
* generator side (`ecosystem/deployable_identity.rs capability_of` strips
|
|
35
|
-
* `-ts`, `-bun` and `-rust`): the fleet was renamed under `-bun` and rows or
|
|
36
|
-
* supervised names still carrying it must converge to the same capability
|
|
37
|
-
* rather than fail the join during the migration window.
|
|
38
4
|
*/
|
|
39
5
|
|
|
40
6
|
/** Language segment of a deployable name. `bun` is the retired spelling. */
|
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The single canonical page-title composer. Every SSR page handler routes
|
|
7
|
-
* through this (portal/region-shell, public shell, single-page resource). A
|
|
8
|
-
* page title is DERIVED from data that already exists — the website name +
|
|
9
|
-
* the page/entity name — with `builda_pages.meta_title` as an explicit
|
|
10
|
-
* per-page OVERRIDE only, never the primary source.
|
|
11
|
-
*
|
|
12
|
-
* Mirrors the brand-is-org-owned-full-scheme precedent: a value that should
|
|
13
|
-
* be DERIVED was instead hand-set per row, and when unset it silently fell
|
|
14
|
-
* back to something wrong. For themes the wrong fallback was the generic
|
|
15
|
-
* light palette; for titles it was the bare org name — the `default`
|
|
16
|
-
* page_meta_template `titleTemplate: "{org.name}"` produced "GBG Indo" with
|
|
17
|
-
* no site or page context, and per-row `meta_title` carried stale product
|
|
18
|
-
* literals like "Dashboard | scala Hub". The fix is identical in shape:
|
|
19
|
-
* derive by default, override explicitly.
|
|
20
|
-
*
|
|
21
|
-
* Canonical format: `<website.name>: <subject>` (subject = entity title or
|
|
22
|
-
* page name), site-first so a browser tab reads the surface then the page.
|
|
23
|
-
* A resource surface (forms/quiz/gpt) may supply a curated `templateTitle`
|
|
24
|
-
* (e.g. "{agent.name} — {org.name}"); it wins over the default derivation
|
|
25
|
-
* but NOT over an explicit per-page override. The composer NEVER returns a
|
|
26
|
-
* bare org name — that is the regression this closes, and the
|
|
27
|
-
* `page-title-not-bare-org` guard backstops it at the template layer.
|
|
28
4
|
*/
|
|
29
5
|
|
|
30
6
|
export interface TitleContext {
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The content-gate ACCESS endpoint mounter. One generic POST handler for every
|
|
7
|
-
* gate: a plain <form> posts {<recordIdField>, email}; on a match against the
|
|
8
|
-
* resource's authorized set it sets the signed access cookie and redirects back,
|
|
9
|
-
* so the page loader's gate then releases the content. No bespoke endpoint per
|
|
10
|
-
* type — each gate is one AccessConfig. Mounted BEFORE the auth gate so it is
|
|
11
|
-
* publicly reachable.
|
|
12
4
|
*/
|
|
13
5
|
|
|
14
6
|
import type { Hono } from "hono";
|
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* THE CATALOG OF GATED CONTENT TYPES — the one place a gate is registered.
|
|
7
|
-
*
|
|
8
|
-
* The gate ENGINE (gate.ts) and the access MOUNTER (access.ts) were already
|
|
9
|
-
* generic and config-driven, but the SEQUENCE was not: `register-registry-page-
|
|
10
|
-
* route.ts` named proposalGateConfig + agreementGateConfig + gptGateConfig in a
|
|
11
|
-
* hand-written array, and `two-phase-boot.ts` named the same three AccessConfigs
|
|
12
|
-
* in a second hand-written array. Two lists, one fact, and adding a fourth gated
|
|
13
|
-
* resource meant editing both — the same drift as the assistant, one layer down
|
|
14
|
-
* (`information-exists-in-one-location`).
|
|
15
|
-
*
|
|
16
|
-
* Now both call sites iterate THIS. A gate is registered once, as a pair, and
|
|
17
|
-
* the SSR gate and its public access endpoint cannot disagree about which gates
|
|
18
|
-
* exist — the shape doctrine asks for when behaviour genuinely is code:
|
|
19
|
-
* `business-logic-is-rows-plus-executors`, "extend the executor catalog".
|
|
20
|
-
*
|
|
21
|
-
* WHY A CATALOG AND NOT A REGISTRY ROW. A gate is not data — `verifyToken`,
|
|
22
|
-
* `resolveAuthorizedEmails` and the withholding rules are executable behaviour,
|
|
23
|
-
* which the rule places in a typed executor, never in a row. What belongs in a
|
|
24
|
-
* row is the SELECTION: which gate governs a given page. That is a `page_route`
|
|
25
|
-
* field, and a row can only select from a catalog, so this file is the thing it
|
|
26
|
-
* will select from — not a step on the way to it.
|
|
27
4
|
*/
|
|
28
5
|
|
|
29
6
|
/**
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The config contracts that PROGRAM a content gate. Each gated content type
|
|
7
|
-
* (agreement, proposal, …) is one ContentGateConfig + one AccessConfig — no
|
|
8
|
-
* bespoke gate functions per type; the engine (gate.ts) and the access mounter
|
|
9
|
-
* (access.ts) are the single shared primitive driven by these.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
type Resolved = Record<string, unknown>;
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* REQUEST-SCOPED channel for gate-entry marker clears. A gate that declares an
|
|
7
|
-
* entry marker (config.entryCookieName) consumes it on the render that the
|
|
8
|
-
* access endpoint's own redirect produces; that render's response must clear
|
|
9
|
-
* the marker, or the next load (a refresh) would still carry it and skip the
|
|
10
|
-
* gate. The gate writes the name here; bindPageHandler — which owns the page
|
|
11
|
-
* Response — drains it into Set-Cookie, so the marker lives exactly one view.
|
|
12
|
-
*
|
|
13
|
-
* THE CLEARS COME BACK AS THE CALL'S RETURN VALUE, never from a post-hoc
|
|
14
|
-
* read: the store only exists inside the run() scope, so a reader called after
|
|
15
|
-
* the awaited call has already left it and would see an empty list — which is
|
|
16
|
-
* exactly how the first version of this file shipped a marker that was never
|
|
17
|
-
* cleared (measured live 2026-09-14: the cookie survived the render).
|
|
18
|
-
*
|
|
19
|
-
* AsyncLocalStorage rather than a module-level map: request-scoped by
|
|
20
|
-
* construction, no accumulated process state that a restart would silently
|
|
21
|
-
* reset (in-memory-state-is-not-durable).
|
|
22
4
|
*/
|
|
23
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
6
|
|
package/src/content-gate/gate.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The content-gate ENGINE. Runs in the page loader after the resource graph
|
|
7
|
-
* resolves, BEFORE SSR. Orchestration only — the per-type behaviour (which
|
|
8
|
-
* resource, who's authorized, what to seed, how to withhold) is supplied by a
|
|
9
|
-
* ContentGateConfig, so agreements/proposals/future gates are configs, not new
|
|
10
|
-
* functions. The single place that decides whether gated bytes reach the HTML.
|
|
11
4
|
*/
|
|
12
5
|
|
|
13
6
|
/**
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The single signed, short-lived access token for EVERY content gate (agreement,
|
|
7
|
-
* proposal, and any future gated content). A gate withholds page content
|
|
8
|
-
* server-side until the request carries a valid token for that resource; this is
|
|
9
|
-
* the only thing that releases the content into the SSR, so without it a gated
|
|
10
|
-
* page is never readable/scrapable by a link holder.
|
|
11
|
-
*
|
|
12
|
-
* HMAC-SHA256 (Bun-native) over {c:cookieName, r:resourceId, e:email, x:exp},
|
|
13
|
-
* plus the optional captured identity a lead-capture gate carries (n:name,
|
|
14
|
-
* p:phone) so a returning visitor is fully known at SSR with NO client storage.
|
|
15
|
-
* `cookieName` is bound INTO the payload so a token minted for one gate
|
|
16
|
-
* (e.g. ag_token) can never satisfy another gate's check (e.g. prop_token).
|
|
17
|
-
*
|
|
18
|
-
* Secret resolver, in preference order: AUTH_SECRET → ENCRYPTION_KEY →
|
|
19
|
-
* the tenant DB address via @teamscala/db's named-need resolver (first one
|
|
20
|
-
* ≥16 chars). A gate signs AND verifies inside one
|
|
21
|
-
* service, so it only needs a stable per-deployment secret; the fallback keeps a
|
|
22
|
-
* gate working on a service that was never given AUTH_SECRET (e.g. the former
|
|
23
|
-
* public-only proposals viewer) instead of failing closed and locking out every
|
|
24
|
-
* authorized viewer. Any service that has AUTH_SECRET transparently prefers it.
|
|
25
|
-
* Fail-closed: no usable secret yields null (no token, no access).
|
|
26
4
|
*/
|
|
27
5
|
|
|
28
6
|
import type { GateSegmentation } from "./config-types.ts";
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Pure decision: does a CONTENT route (no resource_type) whose loader resolved
|
|
7
|
-
* a `page` body source warrant the branded not-found floor? Extracted from
|
|
8
|
-
* register-registry-page-route so the rule is unit-testable independent of the
|
|
9
|
-
* Hono/DB/graph machinery that wraps it (the fix's correctness does not depend
|
|
10
|
-
* on the publish — verify-before-done).
|
|
11
|
-
*
|
|
12
|
-
* Three outcomes:
|
|
13
|
-
* - no page row (deleted/unmatched path, e.g. /roadmap) → dead-link 404 floor
|
|
14
|
-
* - page row exists but zero content blocks → empty-page 200 floor
|
|
15
|
-
* - otherwise → null (render normally)
|
|
16
|
-
*
|
|
17
|
-
* The "Vision Toolbar" leak this closes: a content catchall synthesized an empty
|
|
18
|
-
* page for an unmatched path and served a cacheable 200; resource-not-found-is-
|
|
19
|
-
* the-root-floor only fired for resource_type routes, so dead content links fell
|
|
20
|
-
* through. See resource-not-found.ts for the floor + operator ruling 2026-07-21.
|
|
21
4
|
*/
|
|
22
5
|
import { PrimaryResourceMissingError } from "./resource-not-found.ts";
|
|
23
6
|
|
package/src/csrf-protection.ts
CHANGED
|
@@ -1,33 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* csrf-protection.ts — platform CSRF middleware (`security-csrf-protection`).
|
|
7
|
-
*
|
|
8
|
-
* Hono's csrf() with a DERIVED trusted-origin set, never a hand-authored
|
|
9
|
-
* allowlist: every trusted origin comes from ONE source per
|
|
10
|
-
* `offered-is-derived` —
|
|
11
|
-
* - the managed base domain (registry portal-domains) covers the platform's
|
|
12
|
-
* own `*.scala.business` origins, INCLUDING the cross-service form POST
|
|
13
|
-
* redirect chains CSP form-action allowlists (proposals → sign etc.), so
|
|
14
|
-
* the CSRF origin set and the CSP form-action set cannot disagree about
|
|
15
|
-
* what "one of ours" means;
|
|
16
|
-
* - customer custom domains resolve through `isTrustedAuthOrigin`
|
|
17
|
-
* (builda_websites lookup) — the SAME lookup better-auth's trustedOrigins
|
|
18
|
-
* uses in @teamscala/auth, so a new custom domain is trusted everywhere at
|
|
19
|
-
* once or nowhere.
|
|
20
|
-
*
|
|
21
|
-
* hono csrf() arms only on form-element content types (urlencoded, multipart,
|
|
22
|
-
* text/plain) — browsers cannot produce a cross-site application/json request
|
|
23
|
-
* from a form, so the JSON API surface (ORPC) is protected by cookie
|
|
24
|
-
* SameSite=Lax + this middleware's text/plain coverage.
|
|
25
|
-
*
|
|
26
|
-
* Custom-domain lookups are cached per-hostname with a TTL: a POSITIVE result
|
|
27
|
-
* is cached for the full TTL (a domain that stopped being ours keeps working
|
|
28
|
-
* for at most the TTL), a NEGATIVE result for a short window (a brand-new
|
|
29
|
-
* custom domain becomes trusted within seconds). The cache is a request
|
|
30
|
-
* accelerator over a DB fact, never the source of truth.
|
|
31
4
|
*/
|
|
32
5
|
|
|
33
6
|
import { getManagedBaseDomain } from "@teamscala/auth/auth-env-resolution";
|
package/src/error-page-logo.ts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The error/holding page's brand logo: resolve the host org's dark-theme
|
|
7
|
-
* wordmark from its install row and inline it as a data URI. Split from
|
|
8
|
-
* resource-not-found.ts under single-purpose-file (the builder consumes a
|
|
9
|
-
* logoUrl argument; only these resolvers touch storage and the registry).
|
|
10
|
-
*
|
|
11
|
-
* The host org's LOGO (wordmark) for the dark error page — the favicon is a
|
|
12
|
-
* tab glyph, the wrong asset at page scale (operator-observed: a lone "h"
|
|
13
|
-
* where the launcha wordmark belonged). Resolution: the service's own
|
|
14
|
-
* install row names its owner (brand_org); the org's dark-theme logo (a
|
|
15
|
-
* light-coloured mark, correct on the dark page) is preferred, light-theme
|
|
16
|
-
* second. Every step skew-tolerant: any miss → null → the builder renders a
|
|
17
|
-
* logo-less dark page.
|
|
18
|
-
*
|
|
19
|
-
* Returns the logo as an INLINE data URI (not a `/api/assets/` proxy URL) so
|
|
20
|
-
* the branded holding/error page ships the logo IN the initial HTML bytes —
|
|
21
|
-
* no second fetch, no post-paint pop-in, no dependency on the asset proxy the
|
|
22
|
-
* degraded service is failing to serve (incident 2026-07-27: the logo on a
|
|
23
|
-
* DEGRADED render-layer holding page appeared after the page painted). The
|
|
24
|
-
* logo sibling of icons-render-ssr-inline. Size-capped: a pathological
|
|
25
|
-
* (>MAX_INLINE_LOGO_BYTES) logo falls back to null rather than bloating the
|
|
26
|
-
* HTML; the page renders logo-less, never broken.
|
|
27
4
|
*/
|
|
28
5
|
|
|
29
6
|
import { getClient } from "@teamscala/object-storage/create/client/get-client";
|
package/src/expand-roster.ts
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Server-side roster expansion — the executor that handles "render N of block X
|
|
7
|
-
* per data row" so the page never needs a domain-specific panel/list block. A
|
|
8
|
-
* block authored with `props.dataRepeat: "parties"` is a TEMPLATE; before SSR
|
|
9
|
-
* this clones it once per roster item into its parent slot (top-level content or
|
|
10
|
-
* any Container's `content` slot), then drops the marker. Each clone:
|
|
11
|
-
* - sets `role` + `dataKey` to the item's key, so a prop-driven block (the
|
|
12
|
-
* SignatureField cell) finds its own party with no per-clone wiring;
|
|
13
|
-
* - substitutes `{{name}}` / `{{role}}` / `{{company}}` / `{{date}}` /
|
|
14
|
-
* `{{status}}` placeholders in any string prop (so a recipient row is plain
|
|
15
|
-
* `Container` + `Text` primitives — the copy lives on the page, not here).
|
|
16
|
-
* `{{status}}` resolves from the template's own `signedLabel` /
|
|
17
|
-
* `awaitingLabel` props (DB-driven copy), never hardcoded.
|
|
18
|
-
* Generic over any roster; the agreement page passes its parties.
|
|
19
4
|
*/
|
|
20
5
|
|
|
21
6
|
export interface RosterItem {
|
package/src/gate-ttls.ts
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Cached readers for the content-gate access-token TTL config rows
|
|
7
|
-
* (agreement-gate / proposal-gate / gpt-gate / quiz-gate). initGateTtls() runs once at boot
|
|
8
|
-
* (two-phase-boot, before the gates mount) to populate the cache from the
|
|
9
|
-
* registry rows; the sync getters (agTtlMs/propTtlMs/gptTtlMs) return the
|
|
10
|
-
* cached value — or the former hardcoded default before init / on row-miss — so
|
|
11
|
-
* the sync AccessConfig.ttlMs fields + sync hono magic-link handlers read them
|
|
12
|
-
* without async. Replaces the per-file AG_TTL_MS / PROP_TTL_MS / GPT_TTL_MS
|
|
13
|
-
* constants (config-constants: the registry row is the source, dedup'd across
|
|
14
|
-
* each gate + its attach-* magic counterpart so token TTL and cookie maxAge
|
|
15
|
-
* never drift).
|
|
16
4
|
*/
|
|
17
5
|
import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
|
|
18
6
|
import * as v from "valibot";
|
package/src/gpt-gate.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The PUBLIC GPT identity gate, expressed as config for the content-gate
|
|
7
|
-
* primitive (content-gate/). Unlike proposals/agreements this is NOT access
|
|
8
|
-
* control — every email is a valid visitor (lead capture). Its whole job is to
|
|
9
|
-
* make a returning visitor's identity known AT SSR so the GPT body renders the
|
|
10
|
-
* real conversation on first paint instead of server-rendering the capture gate
|
|
11
|
-
* and then swapping to the chat after hydration (the ssr-then-live-handoff +
|
|
12
|
-
* "know the visitor at SSR first paint" violation: building-applications).
|
|
13
|
-
*
|
|
14
|
-
* Flow: the capture form POSTs {gptId, email} to /api/gpt-access (openCapture →
|
|
15
|
-
* any email matches); the access endpoint sets a signed `gpt_token` cookie. On
|
|
16
|
-
* the next request runContentGate verifies that cookie for this agent and seeds
|
|
17
|
-
* resolved.initialVisitor — which the GPT body serverLoader (ssrGptSession) then
|
|
18
|
-
* uses to SSR-restore the conversation, and GptBodyBlock uses to skip the gate.
|
|
19
|
-
* onUnauthorized is a no-op: an un-gated (first-time) visitor sees the capture
|
|
20
|
-
* form the block renders itself when no visitor is known — that is correct, not
|
|
21
|
-
* a flash (they ARE anonymous), so there is nothing to withhold.
|
|
22
4
|
*/
|
|
23
5
|
|
|
24
6
|
import type { AccessConfig, ContentGateConfig } from "./content-gate/config-types.ts";
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The single app.onError factory for every Hono app (UI + backend). Two error
|
|
7
|
-
* classes, deliberately separate:
|
|
8
|
-
* - HTTPException: a DELIBERATE status-bearing throw (e.g. hono bodyLimit's
|
|
9
|
-
* 413). Preserve its status + message verbatim — buildErrorReport only knows
|
|
10
|
-
* 500/503 and would erase a deliberate 413/401/etc.
|
|
11
|
-
* - anything else: an UNEXPECTED error → buildErrorReport (errorId correlation,
|
|
12
|
-
* transient-vs-bug classification, 503 + Retry-After when retryable, else 500).
|
|
13
|
-
*
|
|
14
|
-
* Resolves the apparent tension between `hono-global-error-handler` (HTTPException
|
|
15
|
-
* → status + JSON) and `error-responses-via-error-report` (every error response
|
|
16
|
-
* via buildErrorReport): the latter governs UNEXPECTED errors; HTTPException is
|
|
17
|
-
* the documented deliberate-status carve-out. The handler is a consumer of
|
|
18
|
-
* buildErrorReport, never a second classifier for the unexpected branch.
|
|
19
4
|
*/
|
|
20
5
|
|
|
21
6
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
package/src/install-harness.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Install harness — sandbox-mounts each declared package and captures
|
|
7
|
-
* registry.require() calls via AsyncLocalStorage. Validates all required
|
|
8
|
-
* rows exist before returning. Throws HarnessInstallError with full
|
|
9
|
-
* context on failure. Per Constitution Rule 2 step 3.
|
|
10
|
-
*
|
|
11
|
-
* Marked: HarnessInstallError is the error class
|
|
12
|
-
* validatePackageDependencies (might) throw — they form one canonical
|
|
13
|
-
* "install harness validation" unit (the validator + the failure-mode
|
|
14
|
-
* class it produces).
|
|
15
4
|
*/
|
|
16
5
|
|
|
17
6
|
import {
|
package/src/install-logging.ts
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* One-call install of the entire logging pipeline. After installLogging()
|
|
7
|
-
* runs, every error in the service — process-level, HTTP middleware, Hono
|
|
8
|
-
* handlers, Puck blocks, even third-party packages calling console.* — is
|
|
9
|
-
* captured by @teamscala/logger and flows to Sentry/GlitchTip (prod) or
|
|
10
|
-
* loga (dev) with full context. Application code never imports the logger
|
|
11
|
-
* or wraps anything in try/catch for telemetry purposes.
|
|
12
|
-
*
|
|
13
|
-
* Two phases, two files (single-purpose-file: the guard counts them as two
|
|
14
|
-
* export groups in one file): installLogging (early bootstrap, hardcoded
|
|
15
|
-
* defaults) lives here; applyLoggerConfig (late-stage, DB-driven) lives in
|
|
16
|
-
* apply-logger-config.ts. The service boot pipeline calls them in sequence
|
|
17
|
-
* with the same serviceSlug.
|
|
18
4
|
*/
|
|
19
5
|
|
|
20
6
|
import { ManagedCache } from "@teamscala/cache/cache";
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Is a resolved module export something React can render?
|
|
7
|
-
*
|
|
8
|
-
* `typeof x === "function"` is NOT that question. memo(), forwardRef() and
|
|
9
|
-
* lazy() all return OBJECTS carrying a `$$typeof` symbol, so a shape check on
|
|
10
|
-
* `function` rejects three of React's own idioms as "not a component".
|
|
11
|
-
* Measured 2026-09-01: scala-projects skipped PresenceAvatars for exactly this
|
|
12
|
-
* reason — the block is `memo(function PresenceAvatarsBlock(...))` — and every
|
|
13
|
-
* page rendering it came out blank with no error beyond one boot WARN.
|
|
14
4
|
*/
|
|
15
5
|
|
|
16
6
|
export function isReactElementType(value: unknown): boolean {
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Loads the service's GENERATED `src/page-manifest.ts` — the codegen output of
|
|
7
|
-
* the `page-manifest` transform, emitted from `block_schema` rows. That file is
|
|
8
|
-
* the single artefact describing which Puck components this service renders.
|
|
9
|
-
*
|
|
10
|
-
* This REPLACES `discover-blocks.ts`, a hand-written runtime scan that queried
|
|
11
|
-
* block_schema at boot and dynamic-imported each row. Deleting it is the point.
|
|
12
|
-
* The scan was introduced to survive one dead row, and it bought that by moving
|
|
13
|
-
* a build-time, deterministic, generated fact into a runtime path with four
|
|
14
|
-
* independent ways to silently resolve nothing: a registry read that answers
|
|
15
|
-
* with an empty array instead of failing, an install probe that reports "not
|
|
16
|
-
* installed" for installed packages, a `node_modules` lookup relative to
|
|
17
|
-
* `process.cwd()`, and a per-block try/catch that swallows each failure into a
|
|
18
|
-
* string nobody reads when the boot dies first. Measured 2026-09-03: 91 active
|
|
19
|
-
* block_schema rows, a generated manifest carrying 63 blocks sitting on disk,
|
|
20
|
-
* and the scan loading ZERO — for hours, across every UI service.
|
|
21
|
-
*
|
|
22
|
-
* Generation is the platform's answer to exactly this (`one-generator-pattern`,
|
|
23
|
-
* `business-logic-is-rows-plus-executors`): a bad row fails codegen loudly, at
|
|
24
|
-
* build time, with the file in front of you. It cannot present as a service
|
|
25
|
-
* that boots and renders nothing.
|
|
26
4
|
*/
|
|
27
5
|
|
|
28
6
|
import { join } from "node:path";
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Loads the service_install registry row for a given service slug.
|
|
7
|
-
* Uses registry.require() per Constitution Rule 11.
|
|
8
|
-
* Schema is owned by @teamscala/db-validation (Rule 7).
|
|
9
4
|
*/
|
|
10
5
|
|
|
11
6
|
import { getCache } from "@teamscala/db/registry/loader/cache/get-cache";
|
package/src/module-not-found.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Classifies a failed dynamic import as "this candidate is absent" vs a real
|
|
7
|
-
* error, for the server_package mount candidate chain. Extracted from
|
|
8
|
-
* mount-server-packages so the classification is testable: as a closure it
|
|
9
|
-
* could not be exercised, and it shipped wrong.
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
/** `@scope/name/sub/path.ts` -> `@scope/name`; `name/sub` -> `name`. */
|
package/src/mount-orpc-router.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Mounts the composed ORPC app router (generated Prisma CRUD + custom fn routers)
|
|
7
|
-
* at /api/rpc/* over HTTP via @orpc/server/fetch's RPCHandler.
|
|
8
|
-
* Uses getAppRouter() from @teamscala/orpc/app-router which merges generated
|
|
9
|
-
* routers plus the row-composed fn namespace (orpc-bundle from api_route rows).
|
|
10
4
|
*/
|
|
11
5
|
|
|
12
6
|
import { join } from "node:path";
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
2
|
+
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Walks `service_install.server_packages[]`, dynamic-imports each, and calls
|
|
7
|
-
* its `mount(ctx)` function. Server packages register handlers / boot hooks /
|
|
8
|
-
* scheduled jobs / WS routes via the MountContext exposed in ctx; the
|
|
9
|
-
* resulting MountRegistry is consumed by register-api-routes /
|
|
10
|
-
* register-ws-routes / run-boot-hook-list / start-scheduled-jobs.
|
|
11
4
|
*/
|
|
12
5
|
|
|
13
6
|
/**
|
package/src/mount.ts
CHANGED
package/src/mutable-app-ref.ts
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
3
|
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* Mutable app reference for two-phase boot. Allows swapping the Hono app
|
|
7
|
-
* atomically after building the full app in the background. The initial app
|
|
8
|
-
* is a minimal tool-only surface (ORPC, data routes) that starts serving
|
|
9
|
-
* immediately; the full app (pages, hydration) is built off-to-the-side and
|
|
10
|
-
* swapped in once complete.
|
|
11
|
-
*
|
|
12
|
-
* Thread-safety: Bun is single-threaded, so a simple module-level variable
|
|
13
|
-
* is sufficient. Atomics are not needed.
|
|
14
4
|
*/
|
|
15
5
|
|
|
16
6
|
import type { Hono } from "hono";
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* THE DOCUMENT'S OWN FACTS, DERIVED WHERE THEY ARE KNOWN: the service
|
|
7
|
-
* names itself, and the request knows where it arrived. Deriving rather
|
|
8
|
-
* than configuring is what makes ONE document correct for the internal
|
|
9
|
-
* address the MCP gateway uses and for a public host at the same time
|
|
10
|
-
* (`information-exists-in-one-location` — the request is the only place
|
|
11
|
-
* the reachable address exists).
|
|
12
|
-
*
|
|
13
|
-
* The `/api/rpc` prefix is part of the answer, not an accident of the
|
|
14
|
-
* mount: it is the prefix the POST handler serves under, so a client that
|
|
15
|
-
* reads the document can call what the document describes.
|
|
3
|
+
* @status handwritten
|
|
16
4
|
*/
|
|
17
5
|
|
|
18
6
|
/**
|