@teamscala/service-boot 0.23.523 → 0.23.525

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/package.json +1 -9
  2. package/src/agreement-gate/agreement-lookup.ts +0 -7
  3. package/src/agreement-gate/authorized-emails.ts +0 -14
  4. package/src/agreement-gate/placeholder-strip.ts +0 -10
  5. package/src/agreement-gate/resolution-caches.ts +0 -5
  6. package/src/agreement-gate/settings.ts +0 -22
  7. package/src/agreement-gate/signing-session.ts +0 -8
  8. package/src/agreement-gate/signing-shell.ts +0 -9
  9. package/src/agreement-gate.ts +0 -52
  10. package/src/apply-logger-config.ts +1 -9
  11. package/src/asset-cache-key.ts +1 -5
  12. package/src/asset-optimize.ts +1 -7
  13. package/src/asset-proxy.ts +1 -10
  14. package/src/asset-social-card.ts +1 -9
  15. package/src/attach-agreement-magic.ts +0 -14
  16. package/src/attach-portal-members.ts +0 -17
  17. package/src/attach-proposal-accept.ts +1 -21
  18. package/src/attach-proposal-magic.ts +1 -13
  19. package/src/attach-transfers-upload.ts +0 -21
  20. package/src/auth/sign-out.ts +1 -44
  21. package/src/auth-gate/public-paths.ts +1 -96
  22. package/src/auth-gate.ts +1 -41
  23. package/src/bind-page-handler.ts +1 -83
  24. package/src/build-client-assets/build-lock.ts +1 -11
  25. package/src/build-client-assets/hashed-output.ts +1 -11
  26. package/src/build-client-assets/hydrate-bundle.ts +1 -137
  27. package/src/build-client-assets/manifest.ts +1 -30
  28. package/src/build-client-assets/source-candidates.ts +0 -51
  29. package/src/build-client-assets/sourcemaps-upload.ts +1 -52
  30. package/src/build-client-assets/styles-build.ts +1 -71
  31. package/src/build-client-assets.ts +1 -30
  32. package/src/capability-slug.ts +1 -45
  33. package/src/compose-page-title.ts +1 -34
  34. package/src/content-gate/access.ts +0 -34
  35. package/src/content-gate/catalog.ts +0 -52
  36. package/src/content-gate/config-types.ts +3 -90
  37. package/src/content-gate/gate-entry-clears.ts +0 -18
  38. package/src/content-gate/gate.ts +0 -27
  39. package/src/content-gate/parse-cookie.ts +0 -4
  40. package/src/content-page-floor.ts +0 -28
  41. package/src/csrf-protection.ts +1 -28
  42. package/src/error-page-logo.ts +2 -35
  43. package/src/expand-roster.ts +1 -25
  44. package/src/gate-ttls.ts +0 -12
  45. package/src/gpt-gate.ts +1 -19
  46. package/src/hono-error-handler.ts +0 -19
  47. package/src/install-harness.ts +1 -12
  48. package/src/install-logging.ts +1 -47
  49. package/src/load-page-manifest.ts +1 -29
  50. package/src/load-service-install-row.ts +42 -10
  51. package/src/module-not-found.ts +0 -17
  52. package/src/mount-orpc-router.ts +1 -94
  53. package/src/mount-server-packages.ts +1 -37
  54. package/src/mount.ts +1 -4
  55. package/src/mutable-app-ref.ts +0 -21
  56. package/src/orpc-contract-rows.json +289 -0
  57. package/src/orpc-openapi-document.ts +1 -19
  58. package/src/orpc-openapi-gate.ts +1 -23
  59. package/src/page-route-shell-types.ts +1 -8
  60. package/src/page-server-loader-graph.ts +0 -10
  61. package/src/page-server-orpc/server-client.ts +1 -23
  62. package/src/pg-boss-handler-registry.ts +1 -24
  63. package/src/pg-boss-mount.ts +1 -109
  64. package/src/proposal-authorized-emails.ts +0 -32
  65. package/src/proposal-gate.ts +1 -52
  66. package/src/quiz-gate.ts +1 -31
  67. package/src/read-html-template.ts +1 -49
  68. package/src/readiness-gate.ts +0 -8
  69. package/src/register-api-routes.ts +1 -11
  70. package/src/register-crash-handlers.ts +1 -6
  71. package/src/register-page-routes.ts +1 -12
  72. package/src/register-page-server-client.ts +1 -66
  73. package/src/register-registry-page-route/ai-chat-prefetch.ts +1 -24
  74. package/src/register-registry-page-route/apply-page-branding.ts +1 -100
  75. package/src/register-registry-page-route/captured-loader-config.ts +1 -12
  76. package/src/register-registry-page-route/graph-cache.ts +1 -38
  77. package/src/register-registry-page-route/live-loader-config.ts +1 -22
  78. package/src/register-registry-page-route/meta-title.ts +1 -36
  79. package/src/register-registry-page-route/page-data-shared.ts +1 -18
  80. package/src/register-registry-page-route/prefetch-org-id.ts +1 -24
  81. package/src/register-registry-page-route/proposal-approval-email-token.ts +1 -22
  82. package/src/register-registry-page-route/proposal-brand-stamping.ts +1 -31
  83. package/src/register-registry-page-route/public-shell-page-data.ts +1 -34
  84. package/src/register-registry-page-route/region-slots-page-data.ts +1 -8
  85. package/src/register-registry-page-route/resolve-page-data.ts +1 -10
  86. package/src/register-registry-page-route/resolve-panel-page.ts +1 -10
  87. package/src/register-registry-page-route/resolve-request-graph.ts +1 -68
  88. package/src/register-registry-page-route/run-content-gates.ts +3 -64
  89. package/src/register-registry-page-route/single-page-data.ts +1 -9
  90. package/src/register-registry-page-route/stamp-session-context.ts +1 -10
  91. package/src/register-registry-page-route/warn-uncovered-blocks.ts +1 -18
  92. package/src/register-registry-page-route.ts +1 -93
  93. package/src/register-static-assets.ts +1 -53
  94. package/src/register-ws-routes.ts +1 -21
  95. package/src/resolve-brand-favicon.ts +0 -22
  96. package/src/resolve-service-port.ts +1 -61
  97. package/src/resolve-theme-mode.ts +1 -30
  98. package/src/resolvers/app-header.ts +1 -20
  99. package/src/resolvers/fetch-org-brand.ts +1 -11
  100. package/src/resolvers/portal-route-paths.ts +1 -29
  101. package/src/resolvers/website-by-host-map.ts +1 -7
  102. package/src/resource-not-found.ts +0 -48
  103. package/src/run-boot-hook-list.ts +1 -13
  104. package/src/run-boot-hooks.ts +1 -19
  105. package/src/scala-tools-currency-watchdog.ts +1 -7
  106. package/src/security-headers.ts +1 -7
  107. package/src/select-service-routes.ts +1 -31
  108. package/src/server-error-page.ts +0 -18
  109. package/src/theme-fonts-link.ts +1 -21
  110. package/src/transfers/presigned-upload.ts +1 -27
  111. package/src/two-phase-full-app.ts +0 -82
  112. package/src/two-phase-minimal-app.ts +1 -111
  113. package/src/two-phase-readiness.ts +0 -6
  114. package/src/types.ts +0 -17
  115. package/src/ui-service.ts +2 -48
  116. package/src/ws-boot-self-test.ts +1 -14
  117. package/src/content-gate/gate-entry-marker.test.ts +0 -111
  118. package/src/content-gate/token.segmentation.test.ts +0 -76
  119. package/src/content-gate/token.test.ts +0 -65
  120. package/src/content-gate/token.ts +0 -219
  121. package/src/is-react-element-type.test.ts +0 -45
  122. package/src/is-react-element-type.ts +0 -20
  123. package/src/page-server-orpc/hub-mcp-client.ts +0 -20
  124. package/src/pg-boss-mount.test.ts +0 -101
  125. package/src/resource-chat-inject.page-assistant.test.ts +0 -145
  126. package/src/resource-chat-inject.test.ts +0 -240
  127. package/src/resource-chat-inject.ts +0 -374
package/package.json CHANGED
@@ -118,8 +118,6 @@
118
118
  "./content-gate/gate-entry-clears.ts": "./src/content-gate/gate-entry-clears.ts",
119
119
  "./content-gate/parse-cookie": "./src/content-gate/parse-cookie.ts",
120
120
  "./content-gate/parse-cookie.ts": "./src/content-gate/parse-cookie.ts",
121
- "./content-gate/token": "./src/content-gate/token.ts",
122
- "./content-gate/token.ts": "./src/content-gate/token.ts",
123
121
  "./content-page-floor": "./src/content-page-floor.ts",
124
122
  "./content-page-floor.ts": "./src/content-page-floor.ts",
125
123
  "./csrf-protection": "./src/csrf-protection.ts",
@@ -138,8 +136,6 @@
138
136
  "./install-harness.ts": "./src/install-harness.ts",
139
137
  "./install-logging": "./src/install-logging.ts",
140
138
  "./install-logging.ts": "./src/install-logging.ts",
141
- "./is-react-element-type": "./src/is-react-element-type.ts",
142
- "./is-react-element-type.ts": "./src/is-react-element-type.ts",
143
139
  "./load-page-manifest": "./src/load-page-manifest.ts",
144
140
  "./load-page-manifest.ts": "./src/load-page-manifest.ts",
145
141
  "./load-service-install-row": "./src/load-service-install-row.ts",
@@ -162,8 +158,6 @@
162
158
  "./page-route-shell-types.ts": "./src/page-route-shell-types.ts",
163
159
  "./page-server-loader-graph": "./src/page-server-loader-graph.ts",
164
160
  "./page-server-loader-graph.ts": "./src/page-server-loader-graph.ts",
165
- "./page-server-orpc/hub-mcp-client": "./src/page-server-orpc/hub-mcp-client.ts",
166
- "./page-server-orpc/hub-mcp-client.ts": "./src/page-server-orpc/hub-mcp-client.ts",
167
161
  "./page-server-orpc/server-client": "./src/page-server-orpc/server-client.ts",
168
162
  "./page-server-orpc/server-client.ts": "./src/page-server-orpc/server-client.ts",
169
163
  "./pg-boss-handler-registry": "./src/pg-boss-handler-registry.ts",
@@ -246,8 +240,6 @@
246
240
  "./resolvers/portal-route-paths.ts": "./src/resolvers/portal-route-paths.ts",
247
241
  "./resolvers/website-by-host-map": "./src/resolvers/website-by-host-map.ts",
248
242
  "./resolvers/website-by-host-map.ts": "./src/resolvers/website-by-host-map.ts",
249
- "./resource-chat-inject": "./src/resource-chat-inject.ts",
250
- "./resource-chat-inject.ts": "./src/resource-chat-inject.ts",
251
243
  "./resource-not-found": "./src/resource-not-found.ts",
252
244
  "./resource-not-found.ts": "./src/resource-not-found.ts",
253
245
  "./run-boot-hook-list": "./src/run-boot-hook-list.ts",
@@ -302,5 +294,5 @@
302
294
  "repository": "https://github.com/teamgbg/service-boot",
303
295
  "sideEffects": false,
304
296
  "type": "module",
305
- "version": "0.23.523"
297
+ "version": "0.23.525"
306
298
  }
@@ -1,13 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Agreement lookup: typed first-match query over the agreements model via
7
- * the page-server ORPC client.
8
- *
9
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
10
- * paths adjusted only.
11
4
  */
12
5
 
13
6
  import { getServerClient } from "../page-server-orpc/server-client.ts";
@@ -1,13 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The agreement's authorized email sets: the signer roster (who may sign) and
7
- * the roster ∪ owning/party-org members (who may view past the gate).
8
- *
9
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
10
- * paths adjusted only.
11
4
  */
12
5
 
13
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -15,7 +8,6 @@ import { parseAgreementParties } from "@teamscala/document-signing/agreement-par
15
8
  import { findFirstAgreement } from "./agreement-lookup.ts";
16
9
  import { resolveAgreementViewerDirectory } from "./resolution-caches.ts";
17
10
 
18
- /** The agreement's authorized email set (its signer roster) for the access endpoint. */
19
11
  export async function resolveAgreementSignerEmails(
20
12
  agreementId: string,
21
13
  ): Promise<Set<string>> {
@@ -47,12 +39,6 @@ export async function resolveAgreementSignerEmails(
47
39
  }
48
40
  }
49
41
 
50
- /**
51
- * Every email allowed PAST the gate: the signer roster (who may sign) UNION every
52
- * member of the owning org (who may VIEW). Signing authority stays roster-only —
53
- * buildSigningSession gives a non-party org member a view-only session. This is
54
- * what the access endpoint and session-authorize check.
55
- */
56
42
  export async function resolveAgreementAuthorizedEmails(
57
43
  agreementId: string,
58
44
  ): Promise<Set<string>> {
@@ -1,19 +1,10 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The withholding transform: drop blocks whose own text still carries an
7
- * {{agreement.*}} token, recursively, so unauthed viewers never see raw
8
- * placeholders.
9
- *
10
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
11
- * paths adjusted only.
12
4
  */
13
5
 
14
6
  import type { ContentBlock } from "./signing-session.ts";
15
7
 
16
- /** True if any of the block's OWN string props carries an {{agreement.*}} token. */
17
8
  function blockHasAgreementPlaceholder(block: ContentBlock): boolean {
18
9
  const props = block.props;
19
10
  if (!props) return false;
@@ -23,7 +14,6 @@ function blockHasAgreementPlaceholder(block: ContentBlock): boolean {
23
14
  return false;
24
15
  }
25
16
 
26
- /** Drop blocks whose own text still holds an {{agreement.*}} token, recursively. */
27
17
  export function stripAgreementPlaceholderBlocks(content: ContentBlock[]): ContentBlock[] {
28
18
  const out: ContentBlock[] = [];
29
19
  for (const block of content) {
@@ -1,11 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The agreement gate's resolution caches: the page→agreement association
7
- * cache, the owning/party org member-directory caches, and the event-driven
8
- * invalidation (LISTEN registry_config_changed) that clears them.
9
4
  */
10
5
 
11
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,13 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * DB-driven display + behaviour settings for the AGREEMENT content gate
7
- * (config/content-gate-agreement), plus its cookie name.
8
- *
9
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
10
- * paths adjusted only.
11
4
  */
12
5
 
13
6
  import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
@@ -15,13 +8,6 @@ import * as v from "valibot";
15
8
 
16
9
  export const AG_COOKIE = "ag_token";
17
10
 
18
- /**
19
- * Agreement gate display + behaviour, DB-driven (config/content-gate-agreement),
20
- * never hardcoded — mirrors proposal-gate's loadProposalGateSettings. requireOtp
21
- * is the per-gate OTP switch (agreements default ON: a signer verifies identity
22
- * before signing). title is the fallback used when no agreement name resolves. A
23
- * missing/invalid row falls back to these defaults so the gate UI ALWAYS renders.
24
- */
25
11
  export const AGREEMENT_GATE_DEFAULTS = {
26
12
  title: "Private document",
27
13
  subtitle: "Verify your email to view and sign this document.",
@@ -36,16 +22,8 @@ const AgreementGateSettingsSchema = v.object({
36
22
  requireOtp: v.optional(v.boolean(), AGREEMENT_GATE_DEFAULTS.requireOtp),
37
23
  });
38
24
 
39
- /**
40
- * The settings type is the SCHEMA'S output, never a hand mirror
41
- * (`autosave-single-surface` rule 2): the interface re-spelled the schema one
42
- * block away and drifts the first time the schema grows a field.
43
- */
44
25
  export type AgreementGateSettings = v.InferOutput<typeof AgreementGateSettingsSchema>;
45
26
 
46
- /** Gate copy/behaviour, or null when the config row is absent/broken. Null is
47
- * the absent signal — callers default explicitly so a missing row neither 500s
48
- * the page nor masks as a populated value (protective-layers-fail-explicitly). */
49
27
  export async function loadAgreementGateSettings(): Promise<AgreementGateSettings | null> {
50
28
  try {
51
29
  return await loadRegistryConfig(
@@ -1,14 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The verified signer's signing session: gate-block discovery across regions,
7
- * roster/party session construction (signer vs view-only org member), and
8
- * seeding of the session into every SignatureField.
9
- *
10
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
11
- * paths adjusted only.
12
4
  */
13
5
 
14
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,15 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * New-flow signing-shell injection (no pre-placed gate required): agreement
7
- * meta stashing, body-content discovery, the SignIn gate block builder, and
8
- * the DB-driven signing shell (config/agreement-signing-shell) with its
9
- * hardcoded fallback.
10
- *
11
- * Split out of agreement-gate.ts (909 lines) — byte-for-byte move; import
12
- * paths adjusted only.
13
4
  */
14
5
 
15
6
  /**
@@ -1,24 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The AGREEMENT content gate, expressed as config for the content-gate primitive
7
- * (content-gate/). The agreement-specific bits: which resource (the placed
8
- * SignIn gate block's id), who's authorized (the signer roster), what to seed
9
- * (the verified signer's signing session into every SignatureField), and how to
10
- * withhold (strip the gate region to gate-only + strip {{agreement.*}} chrome).
11
- * The engine handles cookie/token/verify/orchestration.
12
- *
13
- * The signing logic (buildSigningSession, seedSigningBlocks, roster expansion,
14
- * placeholder strip) is preserved verbatim from the former gate-agreement-
15
- * content.ts — only the cookie/token orchestration moved into the shared engine.
16
- *
17
- * This module holds the two exported configs (agreementGateConfig +
18
- * agreementAccessConfig). The machinery lives as siblings under
19
- * ./agreement-gate/ (settings, agreement-lookup, signing-session,
20
- * placeholder-strip, signing-shell, authorized-emails) — split when the
21
- * parent held them all at 909 lines.
22
4
  */
23
5
 
24
6
  import type { AccessConfig, ContentGateConfig } from "./content-gate/config-types.ts";
@@ -27,7 +9,6 @@ import {
27
9
  verifyAgreementAccessToken,
28
10
  } from "@teamscala/document-signing/agreement-access-token";
29
11
  import { expandRosterTemplates } from "./expand-roster.ts";
30
- import { injectResourceChatLauncher } from "./resource-chat-inject.ts";
31
12
  import { agTtlMs } from "./gate-ttls.ts";
32
13
  import { getAppLogger } from "@teamscala/logger/app-loggers";
33
14
  import { AG_COOKIE, AGREEMENT_GATE_DEFAULTS, loadAgreementGateSettings } from "./agreement-gate/settings.ts";
@@ -54,7 +35,6 @@ import { resolveAgreementAuthorizedEmails } from "./agreement-gate/authorized-em
54
35
  export const agreementGateConfig: ContentGateConfig = {
55
36
  cookieName: AG_COOKIE,
56
37
  async getResourceId(resolved) {
57
- // Old-flow pages: a pre-placed SignIn gate block carries the UUID.
58
38
  const gate = findAgreementGate(resolved);
59
39
  if (gate.agreementUuid) {
60
40
  const gateRegion = gate.gateRegions[0];
@@ -65,7 +45,6 @@ export const agreementGateConfig: ContentGateConfig = {
65
45
  return gate.agreementUuid;
66
46
  }
67
47
 
68
- // New-flow pages: resolve UUID from the page→agreement association.
69
48
  const pageId = (resolved.page as { id?: string } | undefined)?.id;
70
49
  if (!pageId) return "";
71
50
 
@@ -88,7 +67,6 @@ export const agreementGateConfig: ContentGateConfig = {
88
67
  stashAgreementMeta(resolved, agreement.id, agreement.name);
89
68
  return agreement.id;
90
69
  }
91
- // Cache negative so non-agreement pages never re-query.
92
70
  pageAgreementCache.set(pageId, { id: "", name: "" });
93
71
  return "";
94
72
  } catch (err) {
@@ -98,10 +76,6 @@ export const agreementGateConfig: ContentGateConfig = {
98
76
  return "";
99
77
  }
100
78
  },
101
- // Baked-in unified auth (engine default): a logged-in viewer whose session
102
- // email is in the authorized set (signer roster ∪ owning/party-org members)
103
- // is released with no per-gate OTP — session verify is local, works on
104
- // Railway. Just exposing the resolver opts into the platform-wide behaviour.
105
79
  resolveAuthorizedEmails: resolveAgreementAuthorizedEmails,
106
80
  verifyToken(token, resourceId, nowMs) {
107
81
  const email = verifyAgreementAccessToken(token, resourceId, nowMs);
@@ -110,7 +84,6 @@ export const agreementGateConfig: ContentGateConfig = {
110
84
  async onAuthorized(resolved, agreementUuid, { email }) {
111
85
  const session = await buildSigningSession(agreementUuid, email);
112
86
  if (!session) {
113
- // Session build failed — withhold content as if unauthorized.
114
87
  if (!hasPrePlacedGate(resolved)) {
115
88
  const content = getBodyContentArray(resolved);
116
89
  if (content) {
@@ -135,9 +108,6 @@ export const agreementGateConfig: ContentGateConfig = {
135
108
  })
136
109
  : "";
137
110
 
138
- // New-flow pages: inject the signing shell BEFORE roster expansion so
139
- // the SignatureField template is expanded per party. Old-flow pages
140
- // already have pre-placed blocks — skip injection.
141
111
  if (!hasPrePlacedGate(resolved)) {
142
112
  const content = getBodyContentArray(resolved);
143
113
  if (content) {
@@ -148,37 +118,18 @@ export const agreementGateConfig: ContentGateConfig = {
148
118
  }
149
119
  }
150
120
 
151
- // Expand roster templates + bind agreement-meta placeholders (both flows).
152
121
  expandRosterTemplates(resolved, session.parties, {
153
122
  name: session.agreementName,
154
123
  ref: session.agreementRef,
155
124
  org: session.orgName ?? "",
156
125
  signer: session.authedName,
157
- // Role-aware verb for the header status line. authedRole is a party key
158
- // for a designated signer, or the literal "viewer" for a view-only
159
- // recipient (an org member who isn't on the signer roster). So the
160
- // header reads "Viewing as <name>" for a viewer, "Signing as <name>"
161
- // for a signer — never implying a viewer can sign.
162
126
  signerVerb: session.authedRole === "viewer" ? "Viewing" : "Signing",
163
127
  signedCount: String(signedCount),
164
128
  total: String(session.parties.length),
165
129
  completedDate,
166
130
  });
167
- // Seed the verified session into every SignatureField (both flows).
168
131
  seedSigningBlocks(resolved, session);
169
132
 
170
- // Floating "Ask about this agreement" GPT bubble — the generic
171
- // ResourceChatLauncher (one-ai-chat-system), strictly behind the email
172
- // gate. The agreement's clause text becomes the chat's runtime context.
173
- await injectResourceChatLauncher(resolved, email, {
174
- resourceKey: "agreement",
175
- agentConfigSlug: "agreement-assistant-agent",
176
- organisationId: session.organisationId ?? undefined,
177
- launchLabel: "Ask about this agreement",
178
- placeholder: "Ask about this agreement…",
179
- });
180
-
181
- // Old-flow only: drop the pre-placed gate block (new flow has none).
182
133
  if (hasPrePlacedGate(resolved)) {
183
134
  const { gateRegions } = findAgreementGate(resolved);
184
135
  for (const { pd, content, gateIdx } of gateRegions) {
@@ -188,13 +139,11 @@ export const agreementGateConfig: ContentGateConfig = {
188
139
  },
189
140
  async onUnauthorized(resolved, agreementUuid) {
190
141
  if (hasPrePlacedGate(resolved)) {
191
- // Old flow: strip region to gate-only.
192
142
  const { gateRegions } = findAgreementGate(resolved);
193
143
  for (const { pd, content, gateIdx } of gateRegions) {
194
144
  pd.content = [content[gateIdx]];
195
145
  }
196
146
  } else {
197
- // New flow: inject gate, withhold all body content.
198
147
  const meta = resolved[AGREEMENT_META_KEY] as
199
148
  | { name?: string }
200
149
  | undefined;
@@ -213,7 +162,6 @@ export const agreementGateConfig: ContentGateConfig = {
213
162
  }
214
163
  }
215
164
 
216
- // Common: strip agreement-placeholder blocks + neutralize page meta.
217
165
  for (const value of Object.values(resolved)) {
218
166
  const pd = (
219
167
  value as { puck_data?: { content?: unknown[] } } | null
@@ -1,14 +1,6 @@
1
1
  /**
2
- * @system server-boot
2
+ * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Late-stage logger configuration — runs AFTER Registry is loaded (the boot
7
- * pipeline's `apply-logger-config` phase), injecting the DB-driven settings
8
- * into the logger primitive. Split from install-logging.ts under
9
- * single-purpose-file: the early bootstrap (hardcoded defaults, primitive
10
- * injection) and this late reconfiguration are two phases, and the guard
11
- * counts them as two export groups in one file.
12
4
  */
13
5
 
14
6
  import { setMinLevel } from "@teamscala/logger/creator";
@@ -1,10 +1,6 @@
1
1
  /**
2
- * @system server-handlers
2
+ * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * ETag derivation for the asset proxy that incorporates the negotiated
7
- * image format so per-Accept variants cache independently.
8
4
  */
9
5
 
10
6
  const SKIP_FORMATS = new Set(["webp", "avif", "gif", "svg", "ico"]);
@@ -1,12 +1,6 @@
1
1
  /**
2
- * @system server-handlers
2
+ * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * On-the-fly image optimisation for the asset proxy. Converts stored
7
- * PNG/JPEG to WebP or AVIF when the browser signals support, using
8
- * @teamscala/image-optimizer's compressImage primitive. Returns null
9
- * on failure so the caller can fall back to the original stream.
10
4
  */
11
5
 
12
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,15 +1,6 @@
1
1
  /**
2
- * @system server-handlers
2
+ * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Same-origin S3 asset proxy with on-the-fly image optimisation.
7
- * Streams objects from the configured S3 bucket, converting PNG/JPEG
8
- * to WebP/AVIF when the browser declares support via Accept header.
9
- * Returns 404 for missing keys, 502 for S3 failures. Never redirects.
10
- * Every non-2xx exit records which stage failed through the app logger:
11
- * a 404 and a 502 are indistinguishable at the socket, so the fault plane
12
- * (credential resolution vs store side) is only observable in the log.
13
4
  */
14
5
 
15
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,14 +1,6 @@
1
1
  /**
2
- * @system server-handlers
2
+ * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Dynamic-import wrapper around image-optimizer's in-process sharp
7
- * social-card op. Mirrors asset-optimize.ts's optional-dependency shape:
8
- * services without image-optimizer installed transparently get null and
9
- * fall back to the raw asset stream. In-process (not HTTP) because the
10
- * og:image transform is a cold path per shared link — no auth handshake,
11
- * no per-consumer service config.
12
4
  */
13
5
 
14
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,20 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The agreement MAGIC-LINK endpoint. A recipient who clicks an emailed
7
- * "Review & sign" link — which carries a pre-authorised, signed ag_token for
8
- * THEIR party — lands here: the token is verified for the agreement, the
9
- * ag_token cookie is set on the shared parent domain, and the browser is
10
- * 302-redirected straight into the signing page. No email-entry gate, no OTP —
11
- * "came from our email" IS the proof of identity, exactly like the
12
- * proposal-accept hand-off (attach-proposal-accept.ts).
13
- *
14
- * The token is the same HMAC ag_token the gate verifies (30-min TTL, scoped to
15
- * {agreementId, email}); an invalid/expired/forged token sets no cookie and the
16
- * destination page simply shows the normal email gate. GET so it is a plain
17
- * clickable link in an email.
18
4
  */
19
5
 
20
6
  import type { Hono } from "hono";
@@ -1,23 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Portal-secured member directory endpoint. Resolves members of an org via a
7
- * parameterized raw join over better_auth_member + "user" — NOT the typed
8
- * `member` ORPC model, which is deliberately omitted from the portal's
9
- * service_schema (security: the raw table is not exposed as a typed CRUD
10
- * surface). Mirrors the agreement-gate.ts:109-150 precedent.
11
- *
12
- * The portal's auth middleware (better-auth-cookie) gates the endpoint — only
13
- * authenticated users can call it. The orgId comes from the caller's context
14
- * (the portal resolves it from the session + passes it).
15
- *
16
- * Endpoint: GET /api/portal/members?orgId=<uuid>
17
- * - orgId omitted: super-admin all-orgs mode (returns every member across
18
- * all orgs, strictly gated to super-admin via getSession; used by the
19
- * "View As" impersonation dropdown).
20
- * Response: { members: Array<{ id, user_id, organization_id, role, email, name, image }> }
21
4
  */
22
5
 
23
6
  import type { Hono } from "hono";
@@ -1,33 +1,13 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The proposal ACCEPT endpoint. A viewer who has passed the proposal gate (holds
7
- * a valid prop_token) POSTs {proposalId} here to accept; the server generates a
8
- * signing document from the proposal (createAgreementFromProposal) and
9
- * 302-redirects them straight into signing it on the sign site. This replaces
10
- * the old mailto CTA. Mounted BEFORE the auth gate so it is publicly reachable —
11
- * the prop_token (not a login) is the proof of identity, exactly like the
12
- * content-gate access endpoint.
13
- *
14
- * Single sign-on across the journey: the viewer already proved their email at
15
- * the proposal gate (prop_token), so we MUST NOT make them re-enter it on the
16
- * sign site. Before redirecting, we mint the agreement's ag_token for that same
17
- * verified email and set it on the SHARED parent domain (Domain=.scala.business)
18
- * so the browser carries it from proposals.scala.business to sign.scala.business
19
- * — the agreement gate then auto-reveals + seeds the signing session with zero
20
- * extra prompts. This requires both hosts to resolve the SAME token secret
21
- * (AUTH_SECRET→ENCRYPTION_KEY→DATABASE_URL): scala-sign must carry ENCRYPTION_KEY
22
- * (also required to decrypt the org's Stripe key on full sign), so both hosts
23
- * land on ENCRYPTION_KEY and the carried token verifies.
24
4
  */
25
5
 
26
6
  import type { Hono } from "hono";
27
7
  import { getAppLogger } from "@teamscala/logger/app-loggers";
28
8
  import { signAgreementAccessToken } from "@teamscala/document-signing/agreement-access-token";
29
9
  import { parseCookie } from "./content-gate/parse-cookie.ts";
30
- import { verifyAccessToken } from "./content-gate/token.ts";
10
+ import { verifyAccessToken } from "@teamscala/agreement-generation/content-gate-token";
31
11
  import { agTtlMs } from "./gate-ttls.ts";
32
12
  import { createAgreementFromProposal } from "@teamscala/agreement-generation/create-agreement-from-proposal";
33
13
  import { getCookieDomain } from "@teamscala/db/registry/cookie-domain";
@@ -1,23 +1,11 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * The proposal MAGIC-LINK endpoint — the proposal twin of attach-agreement-magic.
7
- * A recipient who clicks an emailed "View your proposal" link (carrying a signed,
8
- * per-recipient prop_token) lands here: the token is verified for the proposal,
9
- * the prop_token cookie is set, and the browser is 302-redirected straight into
10
- * the proposal page. No email-entry gate, no OTP. An invalid/expired token sets
11
- * no cookie and the page falls through to the normal email gate.
12
- *
13
- * The prop_token is host-scoped (proposals are served on the proposals host only),
14
- * unlike the agreement ag_token which spans .scala.business for the proposal→sign
15
- * hand-off. GET so it is a plain clickable link in an email.
16
4
  */
17
5
 
18
6
  import type { Hono } from "hono";
19
7
  import { getAppLogger } from "@teamscala/logger/app-loggers";
20
- import { verifyAccessToken } from "./content-gate/token.ts";
8
+ import { verifyAccessToken } from "@teamscala/agreement-generation/content-gate-token";
21
9
  import { propTtlMs } from "./gate-ttls.ts";
22
10
 
23
11
  const PROP_COOKIE = "prop_token";
@@ -1,27 +1,6 @@
1
1
  /**
2
2
  * @system service-boot
3
3
  * @status handwritten
4
- * @edit edit directly
5
- *
6
- * Authenticated asset upload — POST /api/transfers/upload. Restores the
7
- * handler deleted with @teamscala/server-files (commit ca5643ef); avatar/logo
8
- * upload had 404'd on every service since. Writes to S3 via
9
- * @teamscala/object-storage/create/write-ops and returns the SERVABLE URL
10
- * (`/api/assets/<key>`) the client persists into avatar_url/logo_url — the
11
- * stored value is the servable value (single-logo-pathway), so display code
12
- * reads the column with no rewriting. Self-authenticates (getSession) so it
13
- * mounts BEFORE the auth gate.
14
- *
15
- * Key scheme is USER-scoped (`users/<userId>/<type>/<file>-<ts>.<ext>`) per
16
- * the operator ruling: a profile picture belongs to the individual, not any
17
- * org (the prior handler's org-scoped `<orgId>/avatars/...` encoded the
18
- * uploading org into a personal asset). Spec + history:
19
- * `reference/asset-upload-system.md`.
20
- *
21
- * `thumbUrl` mirrors `url` — the full image is served for both; a separate
22
- * generated thumbnail is a future refinement (the prior handler generated
23
- * one, but the image-resize dependency is not wired here yet and a missing
24
- * thumb must not block the upload restoring).
25
4
  */
26
5
  import type { Hono } from "hono";
27
6
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -1,54 +1,11 @@
1
1
  /**
2
2
  * @system service-boot
3
- * @status handwritten — none derivable: the api_route row (fn-auth-sign-out) declares the path, auth and input, not a body; host-bus-handler-contracts emits the client node + wiring FROM the row, never a procedure body; the sign-out mechanism is BetterAuth's `auth.api.signOut` on the app's own session + the /api/auth/* → scala-otp forward that auth-gate already performs (the body composes those two, it is not generated from either).
4
- * @edit edit directly
5
- *
6
- * The fn.auth.signOut procedure body (api_route `fn-auth-sign-out`, authed:
7
- * context). It is the host-side sign-out the Button logout action calls
8
- * through the generated ORPC client node, replacing the raw browser POST to
9
- * /api/auth/sign-out. It takes no input — sign-out has no fields — so the
10
- * generated node is called as `orpcClient.fn.auth.signOut()`, the same no-arg
11
- * shape as fn.chat.health.
12
- *
13
- * WHY A PROCEDURE, NOT THE RAW FETCH. The old client called
14
- * `/api/auth/sign-out` directly. That path is only served when the host is
15
- * running the auth gate's `/api/auth/*` forward or a local BetterAuth handler,
16
- * and it is a bare string path the client hard-codes — a second, unnamed
17
- * statement of "where sign-out lives". Declaring it as an api_route procedure
18
- * gives it the one home (the registry row) the rest of the auth surface uses,
19
- * and the generated node carries the path.
20
- *
21
- * WHAT IT DOES, IN ORDER, so server-side behaviour is UNCHANGED from the
22
- * forwarded POST:
23
- * 1. Revoke the caller's session through the SAME BetterAuth instance the
24
- * session-read path uses (getAuth() — the bridge auth-gate installs, so
25
- * it validates through the identical factory). This deletes the session
26
- * row and returns the Set-Cookie headers that clear it.
27
- * 2. When SCALA_OTP_URL is set — centralised auth, per otp-centralised-via-
28
- * otp-service — forward the same POST to scala-otp's /api/auth/sign-out
29
- * with the caller's cookie, so the central service clears the shared
30
- * Domain=.scala.business session too. The local revoke alone would leave
31
- * the central session alive. The caller's cookie is carried forward and
32
- * scala-otp's response status is checked, so a central refusal is reported
33
- * rather than swallowed — the same POST the /api/auth/* forward in
34
- * auth-gate performs.
35
- *
36
- * HONEST RESIDUAL: the ORPC response is JSON, so the Set-Cookie headers
37
- * BetterAuth returns on its Response cannot ride back to the browser through
38
- * this procedure. The session is revoked in the database on both sides, and
39
- * the caller redirects to the sign-in path, so the browser presents a
40
- * revoked-and-deleted session token on its next request and the auth gate
41
- * rejects it — the same observable outcome as the forwarded POST, whose
42
- * cleared cookie was an optimisation. Clearing the cookie explicitly is the
43
- * caller's business (the raw /api/auth/clear-stale-cookies route), not this
44
- * procedure's.
3
+ * @status handwritten
45
4
  */
46
5
  import { getAppLogger } from "@teamscala/logger/app-loggers";
47
6
 
48
7
  export interface SignOutResult {
49
- /** True once the local session is revoked (and the central one, when centralised). */
50
8
  ok: boolean;
51
- /** True when the sign-out was forwarded to scala-otp. */
52
9
  forwarded: boolean;
53
10
  }
54
11