@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.
- package/package.json +1 -9
- package/src/agreement-gate/agreement-lookup.ts +0 -7
- package/src/agreement-gate/authorized-emails.ts +0 -14
- package/src/agreement-gate/placeholder-strip.ts +0 -10
- package/src/agreement-gate/resolution-caches.ts +0 -5
- package/src/agreement-gate/settings.ts +0 -22
- package/src/agreement-gate/signing-session.ts +0 -8
- package/src/agreement-gate/signing-shell.ts +0 -9
- package/src/agreement-gate.ts +0 -52
- 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 +1 -21
- package/src/attach-proposal-magic.ts +1 -13
- package/src/attach-transfers-upload.ts +0 -21
- package/src/auth/sign-out.ts +1 -44
- package/src/auth-gate/public-paths.ts +1 -96
- package/src/auth-gate.ts +1 -41
- package/src/bind-page-handler.ts +1 -83
- 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 -137
- package/src/build-client-assets/manifest.ts +1 -30
- package/src/build-client-assets/source-candidates.ts +0 -51
- package/src/build-client-assets/sourcemaps-upload.ts +1 -52
- package/src/build-client-assets/styles-build.ts +1 -71
- package/src/build-client-assets.ts +1 -30
- package/src/capability-slug.ts +1 -45
- package/src/compose-page-title.ts +1 -34
- package/src/content-gate/access.ts +0 -34
- package/src/content-gate/catalog.ts +0 -52
- package/src/content-gate/config-types.ts +3 -90
- package/src/content-gate/gate-entry-clears.ts +0 -18
- package/src/content-gate/gate.ts +0 -27
- package/src/content-gate/parse-cookie.ts +0 -4
- package/src/content-page-floor.ts +0 -28
- package/src/csrf-protection.ts +1 -28
- package/src/error-page-logo.ts +2 -35
- package/src/expand-roster.ts +1 -25
- package/src/gate-ttls.ts +0 -12
- package/src/gpt-gate.ts +1 -19
- package/src/hono-error-handler.ts +0 -19
- package/src/install-harness.ts +1 -12
- package/src/install-logging.ts +1 -47
- package/src/load-page-manifest.ts +1 -29
- package/src/load-service-install-row.ts +42 -10
- package/src/module-not-found.ts +0 -17
- package/src/mount-orpc-router.ts +1 -94
- package/src/mount-server-packages.ts +1 -37
- package/src/mount.ts +1 -4
- package/src/mutable-app-ref.ts +0 -21
- package/src/orpc-contract-rows.json +289 -0
- package/src/orpc-openapi-document.ts +1 -19
- package/src/orpc-openapi-gate.ts +1 -23
- package/src/page-route-shell-types.ts +1 -8
- package/src/page-server-loader-graph.ts +0 -10
- package/src/page-server-orpc/server-client.ts +1 -23
- package/src/pg-boss-handler-registry.ts +1 -24
- package/src/pg-boss-mount.ts +1 -109
- package/src/proposal-authorized-emails.ts +0 -32
- package/src/proposal-gate.ts +1 -52
- package/src/quiz-gate.ts +1 -31
- 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 -66
- package/src/register-registry-page-route/ai-chat-prefetch.ts +1 -24
- package/src/register-registry-page-route/apply-page-branding.ts +1 -100
- package/src/register-registry-page-route/captured-loader-config.ts +1 -12
- package/src/register-registry-page-route/graph-cache.ts +1 -38
- package/src/register-registry-page-route/live-loader-config.ts +1 -22
- package/src/register-registry-page-route/meta-title.ts +1 -36
- package/src/register-registry-page-route/page-data-shared.ts +1 -18
- package/src/register-registry-page-route/prefetch-org-id.ts +1 -24
- package/src/register-registry-page-route/proposal-approval-email-token.ts +1 -22
- package/src/register-registry-page-route/proposal-brand-stamping.ts +1 -31
- package/src/register-registry-page-route/public-shell-page-data.ts +1 -34
- 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 -68
- package/src/register-registry-page-route/run-content-gates.ts +3 -64
- 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 -18
- package/src/register-registry-page-route.ts +1 -93
- package/src/register-static-assets.ts +1 -53
- package/src/register-ws-routes.ts +1 -21
- package/src/resolve-brand-favicon.ts +0 -22
- package/src/resolve-service-port.ts +1 -61
- package/src/resolve-theme-mode.ts +1 -30
- package/src/resolvers/app-header.ts +1 -20
- package/src/resolvers/fetch-org-brand.ts +1 -11
- package/src/resolvers/portal-route-paths.ts +1 -29
- package/src/resolvers/website-by-host-map.ts +1 -7
- package/src/resource-not-found.ts +0 -48
- 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 -31
- package/src/server-error-page.ts +0 -18
- package/src/theme-fonts-link.ts +1 -21
- package/src/transfers/presigned-upload.ts +1 -27
- package/src/two-phase-full-app.ts +0 -82
- package/src/two-phase-minimal-app.ts +1 -111
- package/src/two-phase-readiness.ts +0 -6
- package/src/types.ts +0 -17
- package/src/ui-service.ts +2 -48
- package/src/ws-boot-self-test.ts +1 -14
- package/src/content-gate/gate-entry-marker.test.ts +0 -111
- package/src/content-gate/token.segmentation.test.ts +0 -76
- package/src/content-gate/token.test.ts +0 -65
- package/src/content-gate/token.ts +0 -219
- package/src/is-react-element-type.test.ts +0 -45
- package/src/is-react-element-type.ts +0 -20
- package/src/page-server-orpc/hub-mcp-client.ts +0 -20
- package/src/pg-boss-mount.test.ts +0 -101
- package/src/resource-chat-inject.page-assistant.test.ts +0 -145
- package/src/resource-chat-inject.test.ts +0 -240
- 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.
|
|
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
|
/**
|
package/src/agreement-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 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
|
|
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";
|
package/src/asset-cache-key.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
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"]);
|
package/src/asset-optimize.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
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";
|
package/src/asset-proxy.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
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";
|
package/src/asset-social-card.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @system
|
|
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 "
|
|
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 "
|
|
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";
|
package/src/auth/sign-out.ts
CHANGED
|
@@ -1,54 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @system service-boot
|
|
3
|
-
* @status handwritten
|
|
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
|
|