@powerhousedao/switchboard 6.2.3-dev.33 → 6.2.3-dev.35
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/.tsbuild/test/boot-mcp-enabled.test.d.ts +2 -0
- package/.tsbuild/test/boot-mcp-enabled.test.d.ts.map +1 -0
- package/.tsbuild/test/boot-privacy.test.d.ts +2 -0
- package/.tsbuild/test/boot-privacy.test.d.ts.map +1 -0
- package/.tsbuild/test/mcp-flag.test.d.ts +2 -0
- package/.tsbuild/test/mcp-flag.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +50 -0
- package/README.md +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{server-DAgJkmvH.mjs → server-CXAp5TOI.mjs} +149 -34
- package/dist/server-CXAp5TOI.mjs.map +1 -0
- package/dist/server.d.mts +22 -1
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/package.json +16 -15
- package/test/attachment-reference-read-model.test.ts +6 -1
- package/test/boot-mcp-enabled.test.ts +121 -0
- package/test/boot-privacy.test.ts +243 -0
- package/test/boot-unsupported-stored-documents.test.ts +9 -6
- package/test/fatal-shutdown.test.ts +31 -1
- package/test/mcp-flag.test.ts +56 -0
- package/test/workflow-runtime.test.ts +14 -1
- package/tsconfig.json +3 -0
- package/dist/server-DAgJkmvH.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
## 6.2.3-dev.35 (2026-10-01)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- ⚠️ **registry:** serve every replica from Postgres and S3, processing publishes in a worker ([0d25d5fe35](https://github.com/powerhouse-inc/powerhouse/commit/0d25d5fe35))
|
|
6
|
+
- **switchboard:** configure the erasure purge timeout ([354375ea61](https://github.com/powerhouse-inc/powerhouse/commit/354375ea61))
|
|
7
|
+
- **switchboard:** run the privacy add-on behind PH_PRIVACY_ENABLED ([38c9a43856](https://github.com/powerhouse-inc/powerhouse/commit/38c9a43856))
|
|
8
|
+
- **reactor-privacy:** erase requested documents on a serial schedule ([f4f31289a1](https://github.com/powerhouse-inc/powerhouse/commit/f4f31289a1))
|
|
9
|
+
- **reactor-privacy:** serve disclosure and erasure to supreme admins only ([7a6b5232cb](https://github.com/powerhouse-inc/powerhouse/commit/7a6b5232cb))
|
|
10
|
+
- **reactor-privacy:** scaffold the package and its migration ledger ([152ecff77b](https://github.com/powerhouse-inc/powerhouse/commit/152ecff77b))
|
|
11
|
+
- **reactor-drive:** erase a purged document's nodes in NodeProcessor ([5a608e73bf](https://github.com/powerhouse-inc/powerhouse/commit/5a608e73bf))
|
|
12
|
+
- **reactor-attachments:** delete a purged document's attachment references ([4121c83347](https://github.com/powerhouse-inc/powerhouse/commit/4121c83347))
|
|
13
|
+
|
|
14
|
+
### 🩹 Fixes
|
|
15
|
+
|
|
16
|
+
- **switchboard:** let workflow steps read attachments as they read documents ([b8c861360b](https://github.com/powerhouse-inc/powerhouse/commit/b8c861360b))
|
|
17
|
+
- **switchboard:** shut down when a PGlite store cannot flush ([42ea6fd893](https://github.com/powerhouse-inc/powerhouse/commit/42ea6fd893))
|
|
18
|
+
- **reactor:** a drive's purge does not require a live former member ([e8266b1035](https://github.com/powerhouse-inc/powerhouse/commit/e8266b1035))
|
|
19
|
+
- **reactor-privacy:** warn and record lastError while a leaked purge blocks dispatch ([cd6fe4f022](https://github.com/powerhouse-inc/powerhouse/commit/cd6fe4f022))
|
|
20
|
+
- **reactor-privacy:** reopen every failed request with no failed item each tick ([d9c19c3837](https://github.com/powerhouse-inc/powerhouse/commit/d9c19c3837))
|
|
21
|
+
- **reactor-api:** ignore polled refusals of an unknown kind ([aac4a3e752](https://github.com/powerhouse-inc/powerhouse/commit/aac4a3e752))
|
|
22
|
+
- **reactor-api:** fail a poll with a recoverable code when a refusal is not recorded ([3ab1c92f7b](https://github.com/powerhouse-inc/powerhouse/commit/3ab1c92f7b))
|
|
23
|
+
- **reactor:** keep a polled marker refusal only when the marker was owed ([07cae595da](https://github.com/powerhouse-inc/powerhouse/commit/07cae595da))
|
|
24
|
+
- **reactor:** persist a purge refusal only from a MARKER_REFUSED dead letter ([ef2c3f4729](https://github.com/powerhouse-inc/powerhouse/commit/ef2c3f4729))
|
|
25
|
+
|
|
26
|
+
### 🔥 Performance
|
|
27
|
+
|
|
28
|
+
- **reactor-privacy:** dispatch the next purge without rescanning the backlog ([5de3703ebf](https://github.com/powerhouse-inc/powerhouse/commit/5de3703ebf))
|
|
29
|
+
|
|
30
|
+
### ⚠️ Breaking Changes
|
|
31
|
+
|
|
32
|
+
- **registry:** serve every replica from Postgres and S3, processing publishes in a worker ([0d25d5fe35](https://github.com/powerhouse-inc/powerhouse/commit/0d25d5fe35))
|
|
33
|
+
/packages and /pieces return pages ({items,total,limit,offset,hasMore}) instead of full arrays, and piece bundles move to /-/pieces/bundled/<name>/<version>.tgz.
|
|
34
|
+
|
|
35
|
+
### ❤️ Thank You
|
|
36
|
+
|
|
37
|
+
- acaldas
|
|
38
|
+
- Benjamin Jordan
|
|
39
|
+
- Claude Opus 5.5
|
|
40
|
+
|
|
41
|
+
## 6.2.3-dev.34 (2026-09-30)
|
|
42
|
+
|
|
43
|
+
### 🚀 Features
|
|
44
|
+
|
|
45
|
+
- **switchboard:** let MCP_ENABLED=false keep the MCP server unmounted ([#3146](https://github.com/powerhouse-inc/powerhouse/pull/3146))
|
|
46
|
+
|
|
47
|
+
### ❤️ Thank You
|
|
48
|
+
|
|
49
|
+
- Guillermo Puente Sandoval @gpuente
|
|
50
|
+
|
|
1
51
|
## 6.2.3-dev.33 (2026-09-30)
|
|
2
52
|
|
|
3
53
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -110,6 +110,7 @@ pnpm add -g @powerhousedao/switchboard
|
|
|
110
110
|
| `REDIS_TLS_URL` | Redis TLS connection URL | - |
|
|
111
111
|
| `PYROSCOPE_SERVER_ADDRESS` | Pyroscope server address | - |
|
|
112
112
|
| `FEATURE_REACTORV2_ENABLED` | Enable Reactor v2 subgraph feature | `false` |
|
|
113
|
+
| `MCP_ENABLED` | `false` keeps `/mcp` unmounted | `true` |
|
|
113
114
|
|
|
114
115
|
See [Observability](#observability) below for Sentry and OpenTelemetry variables.
|
|
115
116
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="691165f8-a729-5658-bf22-e7b2440e6156")}catch(e){}}();
|
|
4
|
-
import { i as StoredDocumentsRefusedError, r as startSwitchboard, s as parseForcePgVersion } from "./server-
|
|
4
|
+
import { i as StoredDocumentsRefusedError, r as startSwitchboard, s as parseForcePgVersion } from "./server-CXAp5TOI.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
7
7
|
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bf12fafa-a066-5f83-98b1-3dddd60afc6a")}catch(e){}}();
|
|
3
3
|
import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-uaBlzyjS.mjs";
|
|
4
4
|
import { createRequire, register } from "node:module";
|
|
5
5
|
import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
|
|
@@ -11,7 +11,7 @@ import { existsSync, promises, realpathSync } from "node:fs";
|
|
|
11
11
|
import path, { resolve } from "node:path";
|
|
12
12
|
import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
|
|
13
13
|
import { ChannelScheme, DriveCollectionId, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, UnsupportedStoredProtocolError, createHybridProjectionCoordinatorFactory, instrumentPgPool, parseDriveUrl, supportsLiveReadModelRegistration } from "@powerhousedao/reactor";
|
|
14
|
-
import { AuthorizationPolicy, BaseSubgraph, ForbiddenError, HttpPackageLoader, ImportPackageLoader, PGLITE_UTC_PARSERS, PackageManagementService, PackagesSubgraph, callerSubject, createCanonicalDocumentIdResolver, getUniqueDocumentModels, initializeAndStartAPI, resolveRenownConfig } from "@powerhousedao/reactor-api";
|
|
14
|
+
import { AuthorizationPolicy, BaseSubgraph, ForbiddenError, HttpPackageLoader, ImportPackageLoader, PGLITE_UTC_PARSERS, PackageManagementService, PackagesSubgraph, PrivacyPermissionAdapter, callerSubject, createCanonicalDocumentIdResolver, getUniqueDocumentModels, initializeAndStartAPI, resolveRenownConfig } from "@powerhousedao/reactor-api";
|
|
15
15
|
import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
|
|
16
16
|
import { AttachmentAlreadyExists, AttachmentNotFound, AttachmentPending, AttachmentReferenceReadModel, AttachmentSchemaCompiler, HashMismatch, InvalidAttachmentRef, ReservationNotFound, SizeMismatch, UploadTooLarge, createRef, createRemoteAttachmentService, parseAttachmentDownloadTarget, parseRef } from "@powerhousedao/reactor-attachments";
|
|
17
17
|
import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
|
|
@@ -23,6 +23,7 @@ import path$1 from "path";
|
|
|
23
23
|
import { Pool } from "pg";
|
|
24
24
|
import { Readable } from "node:stream";
|
|
25
25
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
26
|
+
import { DisclosureService, MIN_DEPLOYMENT_SECRET_BYTES, createModuleErasure, createPrivacySubgraph, registerSubjectDocumentsReadModel } from "@powerhousedao/reactor-privacy";
|
|
26
27
|
import { ReactorGroupV1 } from "@powerhousedao/reactor-group";
|
|
27
28
|
import { driveDocumentModelModule } from "@powerhousedao/shared/document-drive";
|
|
28
29
|
import { DEFAULT_SIGNATURE_POLICY, isSignaturePolicy } from "@powerhousedao/shared/document-model";
|
|
@@ -790,7 +791,7 @@ async function registerAttachmentReferenceReadModelOnModule(clientModule, attach
|
|
|
790
791
|
//#endregion
|
|
791
792
|
//#region src/fatal-shutdown.mts
|
|
792
793
|
const FORCED_EXIT_MS = 15e3;
|
|
793
|
-
const
|
|
794
|
+
const fatalHandlers = /* @__PURE__ */ new WeakMap();
|
|
794
795
|
/**
|
|
795
796
|
* Sends an uncaught exception or unhandled rejection through the SIGTERM
|
|
796
797
|
* shutdown that `withSignalHandlers()` installs, so the reactor and read-model
|
|
@@ -803,8 +804,7 @@ const installed = /* @__PURE__ */ new WeakSet();
|
|
|
803
804
|
* listener deliberately keeps the process running.
|
|
804
805
|
*/
|
|
805
806
|
function installFatalErrorShutdown(logger, proc = process) {
|
|
806
|
-
if (
|
|
807
|
-
installed.add(proc);
|
|
807
|
+
if (fatalHandlers.has(proc)) return;
|
|
808
808
|
const realExit = proc.exit.bind(proc);
|
|
809
809
|
let shuttingDown = false;
|
|
810
810
|
const onFatal = (kind, err) => {
|
|
@@ -818,12 +818,97 @@ function installFatalErrorShutdown(logger, proc = process) {
|
|
|
818
818
|
}, FORCED_EXIT_MS).unref();
|
|
819
819
|
proc.kill(proc.pid, "SIGTERM");
|
|
820
820
|
};
|
|
821
|
+
fatalHandlers.set(proc, onFatal);
|
|
821
822
|
proc.on("uncaughtException", (err) => onFatal("Uncaught exception", err));
|
|
822
823
|
proc.on("unhandledRejection", (reason) => {
|
|
823
824
|
if (proc.listenerCount("unhandledRejection") > 1) return;
|
|
824
825
|
onFatal("Unhandled rejection", reason);
|
|
825
826
|
});
|
|
826
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* Routes a fatal condition the caller detected through the same shutdown as an
|
|
830
|
+
* uncaught error. Returns false, and does nothing, when
|
|
831
|
+
* `installFatalErrorShutdown` has not run for `proc`.
|
|
832
|
+
*/
|
|
833
|
+
function triggerFatalShutdown(kind, err, proc = process) {
|
|
834
|
+
const onFatal = fatalHandlers.get(proc);
|
|
835
|
+
if (!onFatal) return false;
|
|
836
|
+
onFatal(kind, err);
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region src/privacy.mts
|
|
841
|
+
const DAY_MS = 864e5;
|
|
842
|
+
/** Enabling privacy on a switchboard that cannot run it safely. */
|
|
843
|
+
var PrivacyBootError = class extends Error {
|
|
844
|
+
constructor(message) {
|
|
845
|
+
super(message);
|
|
846
|
+
this.name = "PrivacyBootError";
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
function positive(env, name, unitMs) {
|
|
850
|
+
const raw = env[name];
|
|
851
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
852
|
+
const value = Number(raw);
|
|
853
|
+
if (!Number.isFinite(value) || value <= 0) throw new PrivacyBootError(`${name} must be a positive number, got "${raw}"`);
|
|
854
|
+
return value * unitMs;
|
|
855
|
+
}
|
|
856
|
+
function resolvePrivacy(options, env) {
|
|
857
|
+
if (!(options?.enabled ?? env.PH_PRIVACY_ENABLED === "true")) return { enabled: false };
|
|
858
|
+
const deploymentSecret = options?.deploymentSecret ?? env.PH_PRIVACY_DEPLOYMENT_SECRET;
|
|
859
|
+
if (!deploymentSecret || Buffer.byteLength(deploymentSecret, "utf8") < MIN_DEPLOYMENT_SECRET_BYTES) throw new PrivacyBootError(`PH_PRIVACY_ENABLED needs PH_PRIVACY_DEPLOYMENT_SECRET of at least ${MIN_DEPLOYMENT_SECRET_BYTES} bytes`);
|
|
860
|
+
return {
|
|
861
|
+
enabled: true,
|
|
862
|
+
deploymentSecret,
|
|
863
|
+
intervalMs: positive(env, "PH_PRIVACY_INTERVAL_MS", 1),
|
|
864
|
+
deadlineMs: positive(env, "PH_PRIVACY_DEADLINE_DAYS", DAY_MS),
|
|
865
|
+
markerGraceMs: positive(env, "PH_PRIVACY_MARKER_GRACE_DAYS", DAY_MS),
|
|
866
|
+
purgeTimeoutMs: positive(env, "PH_PRIVACY_PURGE_TIMEOUT_MINUTES", 6e4)
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
/** Subject index, erasure scheduler and admin subgraph on a built reactor. */
|
|
870
|
+
async function startPrivacy(deps) {
|
|
871
|
+
const { config, reactorModule, authorizationService } = deps;
|
|
872
|
+
if (authorizationService.config.policy === AuthorizationPolicy.OPEN) throw new PrivacyBootError("PH_PRIVACY_ENABLED is refused under the OPEN authorization policy: every caller, anonymous included, would administer erasure. Enable authentication (AUTH_ENABLED=true with ADMINS) first.");
|
|
873
|
+
if (!reactorModule) throw new PrivacyBootError("PH_PRIVACY_ENABLED needs an in-process reactor module");
|
|
874
|
+
if (!deps.signer?.app?.key) throw new PrivacyBootError("PH_PRIVACY_ENABLED needs a reactor signer (a Renown identity): every receiver refuses an unsigned purge marker");
|
|
875
|
+
const permissions = deps.documentPermissionService ? new PrivacyPermissionAdapter(deps.documentPermissionService) : void 0;
|
|
876
|
+
const { service, scheduler } = createModuleErasure(reactorModule, {
|
|
877
|
+
deploymentSecret: config.deploymentSecret,
|
|
878
|
+
signer: deps.signer,
|
|
879
|
+
permissions,
|
|
880
|
+
intervalMs: config.intervalMs,
|
|
881
|
+
deadlineMs: config.deadlineMs,
|
|
882
|
+
markerGraceMs: config.markerGraceMs,
|
|
883
|
+
purgeTimeoutMs: config.purgeTimeoutMs,
|
|
884
|
+
logger: deps.logger
|
|
885
|
+
});
|
|
886
|
+
const disclosure = new DisclosureService(reactorModule.database, config.deploymentSecret, permissions);
|
|
887
|
+
let subgraph;
|
|
888
|
+
try {
|
|
889
|
+
subgraph = createPrivacySubgraph({
|
|
890
|
+
authorizationService,
|
|
891
|
+
erasure: service,
|
|
892
|
+
disclosure
|
|
893
|
+
});
|
|
894
|
+
} catch (error) {
|
|
895
|
+
throw new PrivacyBootError(error instanceof Error ? error.message : String(error));
|
|
896
|
+
}
|
|
897
|
+
await registerSubjectDocumentsReadModel(reactorModule, { deploymentSecret: config.deploymentSecret });
|
|
898
|
+
await deps.graphqlManager.registerSubgraphInstance({
|
|
899
|
+
...subgraph,
|
|
900
|
+
path: deps.graphqlManager.getBasePath(),
|
|
901
|
+
reactorClient: deps.reactorClient,
|
|
902
|
+
relationalDb: void 0
|
|
903
|
+
}, "graphql", false);
|
|
904
|
+
scheduler.start();
|
|
905
|
+
deps.logger.info("Privacy add-on started: subject index, erasure scheduler");
|
|
906
|
+
return {
|
|
907
|
+
erasure: service,
|
|
908
|
+
scheduler,
|
|
909
|
+
stop: () => scheduler.stop()
|
|
910
|
+
};
|
|
911
|
+
}
|
|
827
912
|
//#endregion
|
|
828
913
|
//#region src/builder-defaults.mts
|
|
829
914
|
/**
|
|
@@ -1112,6 +1197,18 @@ async function initFeatureFlags() {
|
|
|
1112
1197
|
return OpenFeature.getClient();
|
|
1113
1198
|
}
|
|
1114
1199
|
//#endregion
|
|
1200
|
+
//#region src/mcp-flag.mts
|
|
1201
|
+
/** The env var and OpenFeature flag key that can keep the MCP server unmounted. */
|
|
1202
|
+
const MCP_ENABLED = "MCP_ENABLED";
|
|
1203
|
+
/** Whether to mount the MCP server at `/mcp`. On unless the host passes
|
|
1204
|
+
* `mcp: false` or MCP_ENABLED is "false": the env var can turn a host's MCP
|
|
1205
|
+
* off, never on, so a deployment that serves no MCP client can close the
|
|
1206
|
+
* endpoint without the bin growing a flag of its own. */
|
|
1207
|
+
async function resolveMcpEnabled({ featureFlags, option }) {
|
|
1208
|
+
if (option === false) return false;
|
|
1209
|
+
return featureFlags.getBooleanValue(MCP_ENABLED, true);
|
|
1210
|
+
}
|
|
1211
|
+
//#endregion
|
|
1115
1212
|
//#region src/workflow/oauth-callback.ts
|
|
1116
1213
|
const OAUTH_CALLBACK_PATH = "oauth/callback";
|
|
1117
1214
|
function escapeHtml(text) {
|
|
@@ -1918,28 +2015,14 @@ function writeAssertion(authorizationService, reactorClient) {
|
|
|
1918
2015
|
if (!await authorizationService.canWrite(documentId, ctx.user?.address)) throw new ForbiddenError("to write this document");
|
|
1919
2016
|
};
|
|
1920
2017
|
}
|
|
1921
|
-
/**
|
|
1922
|
-
* carries no caller
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
let parsed;
|
|
1930
|
-
try {
|
|
1931
|
-
parsed = parseRef(ref);
|
|
1932
|
-
} catch {
|
|
1933
|
-
return false;
|
|
1934
|
-
}
|
|
1935
|
-
if (parsed.version !== 1) return false;
|
|
1936
|
-
const canonicalRef = createRef(parsed.hash.toLowerCase());
|
|
1937
|
-
try {
|
|
1938
|
-
return await references.hasReference(await resolveCanonical(documentId), canonicalRef);
|
|
1939
|
-
} catch {
|
|
1940
|
-
return false;
|
|
1941
|
-
}
|
|
1942
|
-
};
|
|
2018
|
+
/** A step reads any well-formed attachment, as it reads any document: a run
|
|
2019
|
+
* carries no caller to check against. */
|
|
2020
|
+
function canReadAttachmentRef(_documentId, ref) {
|
|
2021
|
+
try {
|
|
2022
|
+
return Promise.resolve(parseRef(ref).version === 1);
|
|
2023
|
+
} catch {
|
|
2024
|
+
return Promise.resolve(false);
|
|
2025
|
+
}
|
|
1943
2026
|
}
|
|
1944
2027
|
async function registerWorkflowTriggersReadModel(engine, runtime, clientModule) {
|
|
1945
2028
|
const reactorModule = clientModule?.reactorModule;
|
|
@@ -1983,7 +2066,7 @@ async function composeWorkflowRuntime(deps) {
|
|
|
1983
2066
|
assertCanWrite: writeAssertion(deps.authorizationService, deps.reactorClient),
|
|
1984
2067
|
webhooks: deps.webhooks,
|
|
1985
2068
|
attachments: deps.attachments,
|
|
1986
|
-
canReadAttachmentRef
|
|
2069
|
+
canReadAttachmentRef,
|
|
1987
2070
|
logger: deps.logger
|
|
1988
2071
|
});
|
|
1989
2072
|
const triggers = await registerWorkflowTriggersReadModel(engine, runtime, deps.clientModule);
|
|
@@ -2320,6 +2403,9 @@ const PGLITE_FLUSH_INTERVAL_MS = (() => {
|
|
|
2320
2403
|
const parsed = Number(raw);
|
|
2321
2404
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 100;
|
|
2322
2405
|
})();
|
|
2406
|
+
function onPgliteFlushError(err) {
|
|
2407
|
+
triggerFatalShutdown("PGlite snapshot flush failed", err);
|
|
2408
|
+
}
|
|
2323
2409
|
const PGLITE_IN_MEMORY = process.env.PH_PGLITE_IN_MEMORY === "1";
|
|
2324
2410
|
/**
|
|
2325
2411
|
* Attempt to bind a throwaway TCP server to the given port. Resolves true if
|
|
@@ -2384,7 +2470,8 @@ async function createReactorKysely(opts) {
|
|
|
2384
2470
|
const { PGlite } = await loadPGliteModule(reactorPgliteMajor);
|
|
2385
2471
|
const pglite = inMemory ? new PGlite() : new PGlite({ fs: new AtomicNodeFs(reactorPgliteDir, {
|
|
2386
2472
|
logger,
|
|
2387
|
-
flushIntervalMs
|
|
2473
|
+
flushIntervalMs,
|
|
2474
|
+
onFlushError: onPgliteFlushError
|
|
2388
2475
|
}) });
|
|
2389
2476
|
logger.info(inMemory ? `Using in-memory PGlite (PG${reactorPgliteMajor}) for reactor storage [PH_PGLITE_IN_MEMORY=1]` : `Using PGlite (PG${reactorPgliteMajor}) for reactor storage at ${reactorPgliteDir}`);
|
|
2390
2477
|
return {
|
|
@@ -2507,6 +2594,7 @@ async function initServer(serverPort, options, renown, renownConfig) {
|
|
|
2507
2594
|
process.kill(process.pid, "SIGTERM");
|
|
2508
2595
|
};
|
|
2509
2596
|
const workflowsEnabled = options.workflows?.enabled === true;
|
|
2597
|
+
const privacyConfig = resolvePrivacy(options.privacy, process.env);
|
|
2510
2598
|
if (workflowsEnabled) {
|
|
2511
2599
|
await assertWorkflowPackageLoadable();
|
|
2512
2600
|
if (!packages.includes("@powerhousedao/workflow")) packages.push(WORKFLOW_PACKAGE_NAME);
|
|
@@ -2686,7 +2774,8 @@ async function initServer(serverPort, options, renown, renownConfig) {
|
|
|
2686
2774
|
pgliteFactory = PGLITE_IN_MEMORY ? () => new ReadModelPGlite({ parsers: PGLITE_UTC_PARSERS }) : (connectionString) => new ReadModelPGlite({
|
|
2687
2775
|
fs: new AtomicNodeFs(connectionString ?? readModelPgliteDir, {
|
|
2688
2776
|
logger,
|
|
2689
|
-
flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS
|
|
2777
|
+
flushIntervalMs: PGLITE_FLUSH_INTERVAL_MS,
|
|
2778
|
+
onFlushError: onPgliteFlushError
|
|
2690
2779
|
}),
|
|
2691
2780
|
parsers: PGLITE_UTC_PARSERS
|
|
2692
2781
|
});
|
|
@@ -2731,8 +2820,6 @@ async function initServer(serverPort, options, renown, renownConfig) {
|
|
|
2731
2820
|
relationalDb: api.relationalDb,
|
|
2732
2821
|
secretsKeyFile: readModelPgliteDir === null ? false : void 0,
|
|
2733
2822
|
attachments: createAttachmentClient(api.attachments.service),
|
|
2734
|
-
attachmentReferences: api.attachmentReferenceIndex.store,
|
|
2735
|
-
attachmentReferenceProjection: api.attachmentReferenceProjection,
|
|
2736
2823
|
webhooks: api.httpRoutes.scopeFor(WORKFLOW_PACKAGE_NAME).webhooks,
|
|
2737
2824
|
http: api.httpRoutes.scopeFor(WORKFLOW_PACKAGE_NAME),
|
|
2738
2825
|
authorizationService: api.authorizationService,
|
|
@@ -2757,7 +2844,28 @@ async function initServer(serverPort, options, renown, renownConfig) {
|
|
|
2757
2844
|
await workflows.start();
|
|
2758
2845
|
logger.info("Workflow runtime started");
|
|
2759
2846
|
}
|
|
2847
|
+
let privacy;
|
|
2848
|
+
if (privacyConfig.enabled) {
|
|
2849
|
+
const signer = options.reactor ? options.privacy?.signer : options.privacy?.signer ?? renown?.signer;
|
|
2850
|
+
try {
|
|
2851
|
+
privacy = await startPrivacy({
|
|
2852
|
+
config: privacyConfig,
|
|
2853
|
+
reactorModule: (options.reactor ?? ownedReactorModule)?.reactorModule,
|
|
2854
|
+
signer,
|
|
2855
|
+
authorizationService: api.authorizationService,
|
|
2856
|
+
documentPermissionService: api.documentPermissionService,
|
|
2857
|
+
graphqlManager,
|
|
2858
|
+
reactorClient: client,
|
|
2859
|
+
logger: logger.child(["privacy"])
|
|
2860
|
+
});
|
|
2861
|
+
} catch (error) {
|
|
2862
|
+
await workflows?.stop();
|
|
2863
|
+
await abortBoot(api);
|
|
2864
|
+
throw error;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2760
2867
|
const shutdown = async () => {
|
|
2868
|
+
await privacy?.stop();
|
|
2761
2869
|
await workflows?.stop();
|
|
2762
2870
|
await api.dispose();
|
|
2763
2871
|
};
|
|
@@ -2852,6 +2960,8 @@ async function initServer(serverPort, options, renown, renownConfig) {
|
|
|
2852
2960
|
attachmentReferenceProjection: api.attachmentReferenceProjection,
|
|
2853
2961
|
workflowTriggers: workflows?.triggers,
|
|
2854
2962
|
workflowsEnabled,
|
|
2963
|
+
privacy: privacy ? { erasure: privacy.erasure } : void 0,
|
|
2964
|
+
mcpEnabled: options.mcp !== false,
|
|
2855
2965
|
renown,
|
|
2856
2966
|
port: serverPort,
|
|
2857
2967
|
shutdown
|
|
@@ -2881,6 +2991,10 @@ const startSwitchboard = async (options = {}) => {
|
|
|
2881
2991
|
const featureFlags = await initFeatureFlags();
|
|
2882
2992
|
const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(DOCUMENT_MODEL_SUBGRAPHS_ENABLED, options.enableDocumentModelSubgraphs ?? DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT);
|
|
2883
2993
|
options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;
|
|
2994
|
+
options.mcp = await resolveMcpEnabled({
|
|
2995
|
+
featureFlags,
|
|
2996
|
+
option: options.mcp
|
|
2997
|
+
});
|
|
2884
2998
|
const configPathForFlags = resolveConfigPath(options.configFile);
|
|
2885
2999
|
const workflowsEnabled = await resolveWorkflowsEnabled({
|
|
2886
3000
|
featureFlags,
|
|
@@ -2895,6 +3009,7 @@ const startSwitchboard = async (options = {}) => {
|
|
|
2895
3009
|
};
|
|
2896
3010
|
logger.info("Feature flags: @flags", JSON.stringify({
|
|
2897
3011
|
DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,
|
|
3012
|
+
MCP_ENABLED: options.mcp,
|
|
2898
3013
|
PH_WORKFLOWS_ENABLED: workflowsEnabled
|
|
2899
3014
|
}, null, 2));
|
|
2900
3015
|
let renown = null;
|
|
@@ -2917,5 +3032,5 @@ if (import.meta.main) await startSwitchboard({ fatalErrorShutdown: true });
|
|
|
2917
3032
|
//#endregion
|
|
2918
3033
|
export { resolveUnsupportedStoredDocuments as a, StoredDocumentsRefusedError as i, isPortAvailable as n, applySwitchboardReactorDefaults as o, startSwitchboard as r, parseForcePgVersion as s, deriveAttachmentServiceConfig as t };
|
|
2919
3034
|
|
|
2920
|
-
//# sourceMappingURL=server-
|
|
2921
|
-
//# debugId=
|
|
3035
|
+
//# sourceMappingURL=server-CXAp5TOI.mjs.map
|
|
3036
|
+
//# debugId=bf12fafa-a066-5f83-98b1-3dddd60afc6a
|