@suveren/gateway 0.9.0 → 0.10.1
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/README.md +33 -1
- package/bin/suveren-gateway.js +203 -5
- package/content/integrations/crm.json +47 -3
- package/content/integrations/erp.json +231 -0
- package/content/integrations/index.json +3 -1
- package/content/integrations/mail.json +147 -0
- package/dist/control-plane/index.mjs +688 -179
- package/dist/mcp-server/http.mjs +483 -259
- package/dist/ui/assets/index-CV9Mbbkd.css +1 -0
- package/dist/ui/assets/{index-CIiOCgjJ.js → index-sq564t5H.js} +23 -23
- package/dist/ui/index.html +2 -2
- package/lib/as-pairing.mjs +77 -0
- package/lib/config.mjs +48 -0
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/zlib.d.ts +6 -0
- package/node_modules/follow-redirects/index.js +10 -1
- package/node_modules/follow-redirects/package.json +1 -1
- package/node_modules/ip-address/README.md +34 -34
- package/node_modules/ip-address/dist/ipv4.js +7 -0
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/undici/LICENSE +21 -0
- package/node_modules/undici/README.md +741 -0
- package/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/node_modules/undici/docs/docs/api/Client.md +288 -0
- package/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/node_modules/undici/docs/docs/api/Cookies.md +128 -0
- package/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
- package/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
- package/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/node_modules/undici/index-fetch.js +65 -0
- package/node_modules/undici/index.d.ts +3 -0
- package/node_modules/undici/index.js +234 -0
- package/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/node_modules/undici/lib/api/api-connect.js +110 -0
- package/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/node_modules/undici/lib/api/api-request.js +214 -0
- package/node_modules/undici/lib/api/api-stream.js +209 -0
- package/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/node_modules/undici/lib/api/index.js +7 -0
- package/node_modules/undici/lib/api/readable.js +580 -0
- package/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
- package/node_modules/undici/lib/core/connect.js +153 -0
- package/node_modules/undici/lib/core/constants.js +143 -0
- package/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/node_modules/undici/lib/core/errors.js +477 -0
- package/node_modules/undici/lib/core/request.js +485 -0
- package/node_modules/undici/lib/core/socks5-client.js +422 -0
- package/node_modules/undici/lib/core/socks5-utils.js +212 -0
- package/node_modules/undici/lib/core/symbols.js +75 -0
- package/node_modules/undici/lib/core/tree.js +160 -0
- package/node_modules/undici/lib/core/util.js +992 -0
- package/node_modules/undici/lib/dispatcher/agent.js +158 -0
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
- package/node_modules/undici/lib/dispatcher/client-h1.js +1746 -0
- package/node_modules/undici/lib/dispatcher/client-h2.js +1060 -0
- package/node_modules/undici/lib/dispatcher/client.js +668 -0
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +184 -0
- package/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
- package/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
- package/node_modules/undici/lib/encoding/index.js +33 -0
- package/node_modules/undici/lib/global.js +59 -0
- package/node_modules/undici/lib/handler/cache-handler.js +776 -0
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/node_modules/undici/lib/handler/deduplication-handler.js +460 -0
- package/node_modules/undici/lib/handler/redirect-handler.js +238 -0
- package/node_modules/undici/lib/handler/retry-handler.js +504 -0
- package/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
- package/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/node_modules/undici/lib/interceptor/cache.js +621 -0
- package/node_modules/undici/lib/interceptor/decompress.js +551 -0
- package/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/node_modules/undici/lib/interceptor/dump.js +99 -0
- package/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/node_modules/undici/lib/util/cache.js +676 -0
- package/node_modules/undici/lib/util/date.js +670 -0
- package/node_modules/undici/lib/util/promise.js +28 -0
- package/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/node_modules/undici/lib/util/stats.js +32 -0
- package/node_modules/undici/lib/util/timers.js +425 -0
- package/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/node_modules/undici/lib/web/cache/util.js +45 -0
- package/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/node_modules/undici/lib/web/cookies/index.js +199 -0
- package/node_modules/undici/lib/web/cookies/parse.js +314 -0
- package/node_modules/undici/lib/web/cookies/util.js +352 -0
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +494 -0
- package/node_modules/undici/lib/web/eventsource/eventsource.js +501 -0
- package/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
- package/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/node_modules/undici/lib/web/fetch/index.js +2413 -0
- package/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/node_modules/undici/lib/web/fetch/util.js +1522 -0
- package/node_modules/undici/lib/web/infra/index.js +229 -0
- package/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/node_modules/undici/lib/web/websocket/connection.js +339 -0
- package/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
- package/node_modules/undici/lib/web/websocket/receiver.js +507 -0
- package/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
- package/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/node_modules/undici/lib/web/websocket/websocket.js +758 -0
- package/node_modules/undici/package.json +152 -0
- package/node_modules/undici/scripts/strip-comments.js +10 -0
- package/node_modules/undici/types/README.md +6 -0
- package/node_modules/undici/types/agent.d.ts +32 -0
- package/node_modules/undici/types/api.d.ts +43 -0
- package/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/node_modules/undici/types/cache.d.ts +36 -0
- package/node_modules/undici/types/client-stats.d.ts +15 -0
- package/node_modules/undici/types/client.d.ts +139 -0
- package/node_modules/undici/types/connector.d.ts +36 -0
- package/node_modules/undici/types/content-type.d.ts +21 -0
- package/node_modules/undici/types/cookies.d.ts +30 -0
- package/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/node_modules/undici/types/dispatcher.d.ts +275 -0
- package/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/node_modules/undici/types/errors.d.ts +177 -0
- package/node_modules/undici/types/eventsource.d.ts +66 -0
- package/node_modules/undici/types/fetch.d.ts +231 -0
- package/node_modules/undici/types/formdata.d.ts +114 -0
- package/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/node_modules/undici/types/global-origin.d.ts +7 -0
- package/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/node_modules/undici/types/handlers.d.ts +14 -0
- package/node_modules/undici/types/header.d.ts +160 -0
- package/node_modules/undici/types/index.d.ts +91 -0
- package/node_modules/undici/types/interceptors.d.ts +82 -0
- package/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/node_modules/undici/types/mock-client.d.ts +27 -0
- package/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/node_modules/undici/types/patch.d.ts +29 -0
- package/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/node_modules/undici/types/pool.d.ts +41 -0
- package/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/node_modules/undici/types/readable.d.ts +68 -0
- package/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/node_modules/undici/types/util.d.ts +18 -0
- package/node_modules/undici/types/utility.d.ts +7 -0
- package/node_modules/undici/types/webidl.d.ts +347 -0
- package/node_modules/undici/types/websocket.d.ts +188 -0
- package/package.json +4 -2
- package/profiles/customers/0.8.profile.json +187 -0
- package/profiles/email/0.7.profile.json +254 -0
- package/profiles/index.json +4 -1
- package/profiles/sales/0.2.profile.json +282 -0
- package/dist/ui/assets/index-BJqF5rmx.css +0 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ Open `http://localhost:7400`. The MCP server is at `http://localhost:7430`.
|
|
|
106
106
|
|
|
107
107
|
### Option B — npm
|
|
108
108
|
|
|
109
|
-
Requires [Node.js 20+](https://nodejs.org/).
|
|
109
|
+
Requires [Node.js 20.18.1+](https://nodejs.org/).
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
npm install -g @suveren/gateway
|
|
@@ -153,6 +153,38 @@ See [`docs/development.md`](docs/development.md) for environment variables, test
|
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
|
+
## Pinning the Authority Server's TLS certificate
|
|
157
|
+
|
|
158
|
+
Opt-in, for self-hosted Authority Servers with a stable signing key — not needed against the hosted `suveren.ai`, and off by default.
|
|
159
|
+
|
|
160
|
+
The gateway already verifies the Authority Server can *sign* under the right key before it ever sends an API key or session cookie. What that alone does not cover: something sitting on the network path between the gateway and the Authority Server, presenting its own TLS certificate, that a locally-trusted CA (e.g. a custom `--ca-file`) would otherwise accept. TLS certificate pinning closes that gap by pinning the Authority Server's certificate public key (SPKI, SHA-256) — every connection after that must present the same key, or the gateway refuses it and locks.
|
|
161
|
+
|
|
162
|
+
**Pinning only protects from the moment the fingerprint has actually been checked over a second channel** — a phone call, a video call, a separate trusted connection. That is why enabling it requires `--expect-fingerprint`: not an optional confirmation step, but the check itself. Recommended: enable it once, right after pairing, from a network you already trust.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Get the Authority Server's own fingerprint independently — ask the operator,
|
|
166
|
+
# or run this yourself on a trusted network:
|
|
167
|
+
openssl s_client -connect your-as-host:443 </dev/null 2>/dev/null \
|
|
168
|
+
| openssl x509 -pubkey -noout \
|
|
169
|
+
| openssl pkey -pubin -outform der \
|
|
170
|
+
| openssl dgst -sha256
|
|
171
|
+
|
|
172
|
+
# Compare it against what you were told out-of-band, THEN enable:
|
|
173
|
+
suveren-gateway config set pin-tls on --expect-fingerprint <the-sha256-you-just-confirmed>
|
|
174
|
+
suveren-gateway restart
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
A few things worth knowing before turning it on:
|
|
178
|
+
|
|
179
|
+
- **Certificate renewal with the SAME key keeps the pin working** — nothing to do. Renew with the same key where your tooling supports it (e.g. `certbot renew --reuse-key`).
|
|
180
|
+
- **A renewal under a NEW key locks the gateway** (`as-tls-mismatch`) until an operator re-pairs — this is deliberate fail-closed behavior, not a bug. Re-pairing means clearing `<dataDir>/as-pairing.json` and signing in again.
|
|
181
|
+
- Pinned connections trust Node's own bundled root certificates plus `--ca-file` / `NODE_EXTRA_CA_CERTS` — **not** your operating system's trust store.
|
|
182
|
+
- `suveren-gateway config set pin-tls off` disables enforcement at any time; the stored fingerprint stays on file, so turning it back on later with the SAME `--expect-fingerprint` value succeeds immediately (it is still required every time — stating it again is cheap, and the command refuses outright if it doesn't match what's on file).
|
|
183
|
+
|
|
184
|
+
See `suveren-gateway config help` for the full command reference.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
156
188
|
## Technical Documentation
|
|
157
189
|
|
|
158
190
|
| Document | Contents |
|
package/bin/suveren-gateway.js
CHANGED
|
@@ -17,7 +17,8 @@ import { homedir, platform, userInfo } from 'node:os';
|
|
|
17
17
|
import { dirname, join, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml } from '../lib/autostart-templates.mjs';
|
|
20
|
-
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, resolveAsUrl } from '../lib/config.mjs';
|
|
20
|
+
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint } from '../lib/config.mjs';
|
|
21
|
+
import { readPairing as readAsPairing, recordTlsPin, formatFingerprint, normalizeFingerprint } from '../lib/as-pairing.mjs';
|
|
21
22
|
|
|
22
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
@@ -61,6 +62,101 @@ function flagValue(args, name) {
|
|
|
61
62
|
return args[i + 1];
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The TLS SPKI fingerprint currently on file for `asUrl`, if any — either a
|
|
67
|
+
* real, verified pin (as-pairing.json, captured at a past sign-in) or a
|
|
68
|
+
* staged-but-not-yet-verified one (config.json's `pinTlsExpectedFingerprint`
|
|
69
|
+
* — set by a PRIOR `--expect-fingerprint` run before any pairing existed).
|
|
70
|
+
* The pairing value always wins when both are present.
|
|
71
|
+
*/
|
|
72
|
+
function currentTlsFingerprintHex(dataDir, asUrl) {
|
|
73
|
+
const pairing = readAsPairing(dataDir);
|
|
74
|
+
if (pairing && pairing.asUrl === asUrl && pairing.tlsSpkiPinHex) return pairing.tlsSpkiPinHex;
|
|
75
|
+
return resolvePinTlsExpectedFingerprint(dataDir);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printFingerprintHowTo(asUrl) {
|
|
79
|
+
let host = asUrl;
|
|
80
|
+
try { host = new URL(asUrl).host; } catch { /* keep the raw string */ }
|
|
81
|
+
console.error(`Obtain the Authority Server's OWN fingerprint independently — e.g. ask the operator over`);
|
|
82
|
+
console.error(`the phone, or run this yourself on a network you already trust:`);
|
|
83
|
+
console.error(``);
|
|
84
|
+
console.error(` openssl s_client -connect ${host} </dev/null 2>/dev/null \\`);
|
|
85
|
+
console.error(` | openssl x509 -pubkey -noout \\`);
|
|
86
|
+
console.error(` | openssl pkey -pubin -outform der \\`);
|
|
87
|
+
console.error(` | openssl dgst -sha256`);
|
|
88
|
+
console.error(``);
|
|
89
|
+
console.error(`Compare it against the value shown above (if any) BEFORE re-running this with --expect-fingerprint.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Mandatory out-of-band check for enabling pin-tls (opt-in TLS certificate
|
|
94
|
+
* pinning) — shared by `start --pin-tls` and `config set pin-tls on`.
|
|
95
|
+
* Without this, turning pin-tls on would either silently trust whatever
|
|
96
|
+
* certificate the Authority Server happens to present at the next sign-in
|
|
97
|
+
* (defeating the whole point of pinning) or require a SEPARATE manual
|
|
98
|
+
* verification step a script could skip. `--expect-fingerprint` makes the
|
|
99
|
+
* operator state, on the command line, the value they already checked
|
|
100
|
+
* out-of-band — refusing outright when it's missing or doesn't match
|
|
101
|
+
* whatever is already on file.
|
|
102
|
+
*
|
|
103
|
+
* Returns the normalized (lowercase, no separators) hex fingerprint to
|
|
104
|
+
* commit, or exits the process (code 1) having already printed why.
|
|
105
|
+
*/
|
|
106
|
+
function requireConfirmedFingerprint(args, asUrl) {
|
|
107
|
+
const flag = flagValue(args, '--expect-fingerprint');
|
|
108
|
+
const existingHex = currentTlsFingerprintHex(DATA_DIR, asUrl);
|
|
109
|
+
|
|
110
|
+
if (!flag) {
|
|
111
|
+
console.error(`--expect-fingerprint is required to enable pin-tls — this is the Authority Server's TLS`);
|
|
112
|
+
console.error(`certificate public-key fingerprint (SHA-256 of the leaf SPKI), checked over a second`);
|
|
113
|
+
console.error(`channel BEFORE trusting it, not just whatever this gateway happens to see on the wire.`);
|
|
114
|
+
console.error(``);
|
|
115
|
+
if (existingHex) {
|
|
116
|
+
console.error(`Currently on file for ${asUrl}:`);
|
|
117
|
+
console.error(` ${formatFingerprint(existingHex)}`);
|
|
118
|
+
console.error(``);
|
|
119
|
+
}
|
|
120
|
+
printFingerprintHowTo(asUrl);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const norm = normalizeFingerprint(flag);
|
|
125
|
+
if (!norm.ok) {
|
|
126
|
+
console.error(`Invalid --expect-fingerprint: ${norm.error}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (existingHex && existingHex !== norm.hex) {
|
|
131
|
+
console.error(`--expect-fingerprint does not match the fingerprint already on file for ${asUrl}:`);
|
|
132
|
+
console.error(` on file: ${formatFingerprint(existingHex)}`);
|
|
133
|
+
console.error(` provided: ${formatFingerprint(norm.hex)}`);
|
|
134
|
+
console.error(``);
|
|
135
|
+
console.error(`If the Authority Server's certificate changed INTENTIONALLY (a new key, not just a`);
|
|
136
|
+
console.error(`renewal), clear the pairing (${join(DATA_DIR, 'as-pairing.json')}) and sign in again to`);
|
|
137
|
+
console.error(`re-pin it, then re-run this command with the new fingerprint.`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return norm.hex;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Commit a confirmed fingerprint: into the existing pairing record
|
|
146
|
+
* (as-pairing.json) when one exists for this URL, so it is enforced from
|
|
147
|
+
* the very next connection on — or, when no pairing exists yet (nothing to
|
|
148
|
+
* attach a TLS pin to), staged in config.json's `pinTlsExpectedFingerprint`
|
|
149
|
+
* for the control plane to enforce at the NEXT sign-in's challenge (see
|
|
150
|
+
* checkAsKeyBeforeLogin in apps/control-plane/src/routes/auth.ts), which
|
|
151
|
+
* moves it into as-pairing.json and clears the staging field once it
|
|
152
|
+
* verifies.
|
|
153
|
+
*/
|
|
154
|
+
function commitConfirmedFingerprint(dataDir, asUrl, hex) {
|
|
155
|
+
if (recordTlsPin(dataDir, asUrl, hex)) return 'pinned';
|
|
156
|
+
writeConfig(dataDir, { pinTlsExpectedFingerprint: hex });
|
|
157
|
+
return 'staged';
|
|
158
|
+
}
|
|
159
|
+
|
|
64
160
|
async function start(args) {
|
|
65
161
|
const detach = args.includes('--detach') || args.includes('-d');
|
|
66
162
|
|
|
@@ -93,6 +189,27 @@ async function start(args) {
|
|
|
93
189
|
caFileToSave = v.path;
|
|
94
190
|
}
|
|
95
191
|
|
|
192
|
+
// --pin-tls: a boolean flag (turns TLS certificate pinning ON — there is
|
|
193
|
+
// no `start --pin-tls off`; use `config set pin-tls off` for that).
|
|
194
|
+
// Validated against the EFFECTIVE as-url for this very start (the flag
|
|
195
|
+
// above, if given, wins over whatever is saved) — enabling it for an
|
|
196
|
+
// http:// Authority Server has nothing to pin. Also requires
|
|
197
|
+
// --expect-fingerprint (the mandatory out-of-band check — see
|
|
198
|
+
// requireConfirmedFingerprint) before it can be turned on at all.
|
|
199
|
+
const pinTlsFlag = args.includes('--pin-tls');
|
|
200
|
+
let pinTlsToSave = null;
|
|
201
|
+
let confirmedFingerprintHex = null;
|
|
202
|
+
if (pinTlsFlag) {
|
|
203
|
+
const effectiveAsUrl = asUrlToSave ?? resolveAsUrl(DATA_DIR);
|
|
204
|
+
const v = validatePinTls(effectiveAsUrl);
|
|
205
|
+
if (!v.ok) {
|
|
206
|
+
console.error(`Invalid --pin-tls: ${v.error}`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
confirmedFingerprintHex = requireConfirmedFingerprint(args, effectiveAsUrl);
|
|
210
|
+
pinTlsToSave = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
96
213
|
if (await isAlreadyRunning()) {
|
|
97
214
|
console.error(`suveren-gateway is already running (pid ${readPid()}). Use \`suveren-gateway stop\` first or \`suveren-gateway restart\`.`);
|
|
98
215
|
process.exit(1);
|
|
@@ -141,6 +258,17 @@ async function start(args) {
|
|
|
141
258
|
writeConfig(DATA_DIR, { caFile: caFileToSave });
|
|
142
259
|
console.log(`CA file: ${caFileToSave} (saved — applied to every process this gateway starts)`);
|
|
143
260
|
}
|
|
261
|
+
if (pinTlsToSave) {
|
|
262
|
+
const effectiveAsUrl = asUrlToSave ?? resolveAsUrl(DATA_DIR);
|
|
263
|
+
const committed = commitConfirmedFingerprint(DATA_DIR, effectiveAsUrl, confirmedFingerprintHex);
|
|
264
|
+
writeConfig(DATA_DIR, { pinTls: true });
|
|
265
|
+
console.log(`TLS pinning: ON (fingerprint ${formatFingerprint(confirmedFingerprintHex)} confirmed)`);
|
|
266
|
+
console.log(
|
|
267
|
+
committed === 'pinned'
|
|
268
|
+
? ` Enforced from your next connection on.`
|
|
269
|
+
: ` Staged — the first sign-in's challenge must match it (refused otherwise); it then becomes the permanent pin.`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
144
272
|
|
|
145
273
|
if (detach) {
|
|
146
274
|
const out = openSync(LOG_FILE, 'a');
|
|
@@ -368,14 +496,46 @@ function printConfigHelp() {
|
|
|
368
496
|
console.log(`suveren-gateway config — read or save gateway settings
|
|
369
497
|
|
|
370
498
|
Usage:
|
|
371
|
-
suveren-gateway config get [as-url|ca-file]
|
|
372
|
-
suveren-gateway config set as-url <url>
|
|
373
|
-
suveren-gateway config set ca-file <path>
|
|
499
|
+
suveren-gateway config get [as-url|ca-file|pin-tls] Print the resolved value(s)
|
|
500
|
+
suveren-gateway config set as-url <url> Save the Authority Server URL
|
|
501
|
+
suveren-gateway config set ca-file <path> Save a CA bundle for internal TLS
|
|
502
|
+
suveren-gateway config set pin-tls on --expect-fingerprint <sha256-hex>
|
|
503
|
+
Pin the Authority Server's TLS certificate
|
|
504
|
+
suveren-gateway config set pin-tls off Stop enforcing the TLS certificate pin
|
|
374
505
|
|
|
375
506
|
Saved in ${join(DATA_DIR, 'config.json')}.
|
|
376
507
|
Precedence at start: --as-url flag > env SUVEREN_AS_URL > saved as-url >
|
|
377
508
|
default (${DEFAULT_AS_URL}).
|
|
378
509
|
|
|
510
|
+
pin-tls (default off, self-hosted Authority Servers with a stable signing
|
|
511
|
+
key) requires https:// and pins the Authority Server's TLS certificate
|
|
512
|
+
public key; every connection after it's established must present that same
|
|
513
|
+
key, or the gateway refuses it and locks (reason: as-tls-mismatch) until an
|
|
514
|
+
operator resolves it. It protects against a party on the network between
|
|
515
|
+
this gateway and the Authority Server presenting its OWN certificate — but
|
|
516
|
+
ONLY from the moment the fingerprint has actually been checked over a
|
|
517
|
+
second channel (phone, a separate trusted connection), which is why
|
|
518
|
+
--expect-fingerprint is mandatory: it is not optional confirmation, it IS
|
|
519
|
+
the check. Recommended: enable it once, right after pairing, from a network
|
|
520
|
+
you already trust.
|
|
521
|
+
|
|
522
|
+
--expect-fingerprint <sha256-hex> The Authority Server's own TLS leaf
|
|
523
|
+
certificate public-key fingerprint (SHA-256 of the SPKI — colons/spacing
|
|
524
|
+
accepted, case-insensitive), confirmed over a second channel BEFORE
|
|
525
|
+
running this command. Without it, this command refuses and prints how to
|
|
526
|
+
obtain the fingerprint yourself.
|
|
527
|
+
- If a pin is already on file and it DIFFERS, this refuses outright —
|
|
528
|
+
clear the pairing first if the certificate changed intentionally (a
|
|
529
|
+
NEW key; a renewal with the SAME key never needs this).
|
|
530
|
+
- If none is on file yet, the value is trusted immediately if a
|
|
531
|
+
signing-key pairing already exists, or staged for the very next
|
|
532
|
+
sign-in's challenge to match (refused otherwise) if it doesn't.
|
|
533
|
+
|
|
534
|
+
Renewing the certificate under the SAME key (e.g. \`certbot renew
|
|
535
|
+
--reuse-key\`) keeps the pin working with no action needed; a renewal under
|
|
536
|
+
a NEW key locks the gateway until an operator re-pairs (clears
|
|
537
|
+
<dataDir>/as-pairing.json and signs in again).
|
|
538
|
+
|
|
379
539
|
Changes take effect on the next \`suveren-gateway start\` / \`restart\` — a
|
|
380
540
|
running gateway keeps using what it already resolved at its own startup.
|
|
381
541
|
`);
|
|
@@ -390,10 +550,12 @@ async function config(args) {
|
|
|
390
550
|
if (!key) {
|
|
391
551
|
console.log(`as-url: ${resolveAsUrl(DATA_DIR)}`);
|
|
392
552
|
console.log(`ca-file: ${saved.caFile ?? '(not set)'}`);
|
|
553
|
+
console.log(`pin-tls: ${resolvePinTls(DATA_DIR) ? 'on' : 'off'}`);
|
|
393
554
|
return;
|
|
394
555
|
}
|
|
395
556
|
if (key === 'as-url') { console.log(resolveAsUrl(DATA_DIR)); return; }
|
|
396
557
|
if (key === 'ca-file') { console.log(saved.caFile ?? ''); return; }
|
|
558
|
+
if (key === 'pin-tls') { console.log(resolvePinTls(DATA_DIR) ? 'on' : 'off'); return; }
|
|
397
559
|
console.error(`Unknown config key: ${key}\n`);
|
|
398
560
|
printConfigHelp();
|
|
399
561
|
process.exit(2);
|
|
@@ -432,6 +594,37 @@ async function config(args) {
|
|
|
432
594
|
console.log('Restart to pick it up: suveren-gateway restart');
|
|
433
595
|
return;
|
|
434
596
|
}
|
|
597
|
+
if (key === 'pin-tls') {
|
|
598
|
+
if (value !== 'on' && value !== 'off') {
|
|
599
|
+
console.error('Usage: suveren-gateway config set pin-tls on|off [--expect-fingerprint <sha256-hex>]');
|
|
600
|
+
process.exit(2);
|
|
601
|
+
}
|
|
602
|
+
if (value === 'off') {
|
|
603
|
+
writeConfig(DATA_DIR, { pinTls: false });
|
|
604
|
+
console.log('Saved pin-tls: off');
|
|
605
|
+
console.log('Restart to pick it up: suveren-gateway restart');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
// value === 'on': requires the out-of-band fingerprint check (see
|
|
609
|
+
// requireConfirmedFingerprint) — never trusts whatever the AS
|
|
610
|
+
// happens to present at the next sign-in without one.
|
|
611
|
+
const effectiveAsUrl = resolveAsUrl(DATA_DIR);
|
|
612
|
+
const v = validatePinTls(effectiveAsUrl);
|
|
613
|
+
if (!v.ok) {
|
|
614
|
+
console.error(`Invalid pin-tls: ${v.error}`);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
const hex = requireConfirmedFingerprint(args, effectiveAsUrl);
|
|
618
|
+
const committed = commitConfirmedFingerprint(DATA_DIR, effectiveAsUrl, hex);
|
|
619
|
+
writeConfig(DATA_DIR, { pinTls: true });
|
|
620
|
+
console.log(`Saved pin-tls: on (fingerprint ${formatFingerprint(hex)} confirmed)`);
|
|
621
|
+
console.log(
|
|
622
|
+
committed === 'pinned'
|
|
623
|
+
? 'Restart to pick it up: suveren-gateway restart'
|
|
624
|
+
: 'Restart and sign in again — the first challenge must match this fingerprint: suveren-gateway restart',
|
|
625
|
+
);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
435
628
|
console.error(`Unknown config key: ${key}\n`);
|
|
436
629
|
printConfigHelp();
|
|
437
630
|
process.exit(2);
|
|
@@ -945,15 +1138,20 @@ Usage:
|
|
|
945
1138
|
suveren-gateway start [--detach] Run the gateway (foreground by default)
|
|
946
1139
|
[--as-url <url>] Authority Server URL (saved for next time)
|
|
947
1140
|
[--ca-file <path>] Internal CA bundle (saved for next time)
|
|
1141
|
+
[--pin-tls --expect-fingerprint <hex>] Pin the AS TLS certificate (saved; https:// only — see below)
|
|
948
1142
|
suveren-gateway stop Stop a detached gateway
|
|
949
1143
|
suveren-gateway restart Stop, then start --detach
|
|
950
1144
|
suveren-gateway status Show running state + health
|
|
951
1145
|
suveren-gateway logs [--tail] Print or tail ~/.suveren/gateway.log
|
|
952
1146
|
suveren-gateway service <cmd> Run as a login service that survives reboot
|
|
953
1147
|
(install | uninstall | status)
|
|
954
|
-
suveren-gateway config get [as-url|ca-file]
|
|
1148
|
+
suveren-gateway config get [as-url|ca-file|pin-tls] Print the resolved value(s)
|
|
955
1149
|
suveren-gateway config set as-url <url> Save the Authority Server URL
|
|
956
1150
|
suveren-gateway config set ca-file <path> Save a CA bundle for internal TLS
|
|
1151
|
+
suveren-gateway config set pin-tls on --expect-fingerprint <hex>
|
|
1152
|
+
Pin the Authority Server's TLS certificate
|
|
1153
|
+
(see \`suveren-gateway config help\` for the fingerprint check)
|
|
1154
|
+
suveren-gateway config set pin-tls off Stop enforcing the TLS certificate pin
|
|
957
1155
|
suveren-gateway help Print this help
|
|
958
1156
|
|
|
959
1157
|
Environment:
|
|
@@ -18,12 +18,24 @@
|
|
|
18
18
|
"type": "text",
|
|
19
19
|
"placeholder": "Leave empty for local SQLite",
|
|
20
20
|
"optional": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "mode",
|
|
24
|
+
"label": "Mode (optional)",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"placeholder": "simulation (default) — live needs an adapter for your CRM",
|
|
27
|
+
"optional": true
|
|
21
28
|
}
|
|
22
29
|
],
|
|
23
30
|
"envMapping": {
|
|
24
|
-
"DATABASE_URL": "databaseUrl"
|
|
31
|
+
"DATABASE_URL": "databaseUrl",
|
|
32
|
+
"CRM_MODE": "mode"
|
|
25
33
|
}
|
|
26
34
|
},
|
|
35
|
+
"simulation": {
|
|
36
|
+
"field": "mode",
|
|
37
|
+
"default": "simulation"
|
|
38
|
+
},
|
|
27
39
|
"personalDefault": true,
|
|
28
40
|
"oauth": null,
|
|
29
41
|
"toolGating": {
|
|
@@ -118,6 +130,14 @@
|
|
|
118
130
|
"category": "read",
|
|
119
131
|
"boundField": "export_access",
|
|
120
132
|
"requiredValue": "allowed"
|
|
133
|
+
},
|
|
134
|
+
"load_simulation": {
|
|
135
|
+
"executionMapping": {},
|
|
136
|
+
"staticExecution": {
|
|
137
|
+
"contact_type": "customer",
|
|
138
|
+
"action_type": "setup"
|
|
139
|
+
},
|
|
140
|
+
"actionLabel": "Test data loaded"
|
|
121
141
|
}
|
|
122
142
|
}
|
|
123
143
|
},
|
|
@@ -130,7 +150,8 @@
|
|
|
130
150
|
"bounds": {
|
|
131
151
|
"read_access": "unlimited",
|
|
132
152
|
"write_daily_max": "50",
|
|
133
|
-
"delete_daily_max": "0"
|
|
153
|
+
"delete_daily_max": "0",
|
|
154
|
+
"setup_daily_max": "0"
|
|
134
155
|
},
|
|
135
156
|
"context": {
|
|
136
157
|
"contact_type": "customer,lead,partner,vendor"
|
|
@@ -150,7 +171,8 @@
|
|
|
150
171
|
"bounds": {
|
|
151
172
|
"read_access": "unlimited",
|
|
152
173
|
"write_daily_max": "50",
|
|
153
|
-
"delete_daily_max": "10"
|
|
174
|
+
"delete_daily_max": "10",
|
|
175
|
+
"setup_daily_max": "0"
|
|
154
176
|
},
|
|
155
177
|
"context": {
|
|
156
178
|
"contact_type": "customer,lead,partner,vendor"
|
|
@@ -162,6 +184,28 @@
|
|
|
162
184
|
"10 deletes/day",
|
|
163
185
|
"review mode"
|
|
164
186
|
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "Load test data (with approval)",
|
|
190
|
+
"description": "Load a simulation package (customers and optional contacts) into the simulated CRM, for setting up a three-week test. No browsing, writing, or deleting — this template grants nothing but the one-time test-data load, and it still requires your approval.",
|
|
191
|
+
"risk": "high",
|
|
192
|
+
"mode": "review",
|
|
193
|
+
"bounds": {
|
|
194
|
+
"read_access": "none",
|
|
195
|
+
"write_daily_max": "0",
|
|
196
|
+
"delete_daily_max": "0",
|
|
197
|
+
"setup_daily_max": "1"
|
|
198
|
+
},
|
|
199
|
+
"context": {
|
|
200
|
+
"contact_type": "customer"
|
|
201
|
+
},
|
|
202
|
+
"intent": "Allow the agent to load one simulation package of test data into the simulated CRM, for my review before it happens. No other CRM action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
|
|
203
|
+
"ttl": 2592000,
|
|
204
|
+
"tags": [
|
|
205
|
+
"setup only",
|
|
206
|
+
"review mode",
|
|
207
|
+
"1 load"
|
|
208
|
+
]
|
|
165
209
|
}
|
|
166
210
|
],
|
|
167
211
|
"setupHint": "No configuration needed — works out of the box.",
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "erp",
|
|
3
|
+
"name": "ERP",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Quote-to-order — items, customers, quotes, orders",
|
|
6
|
+
"icon": "briefcase",
|
|
7
|
+
"profile": "sales",
|
|
8
|
+
"npmPackage": "@humanagencyp/erp-mcp",
|
|
9
|
+
"mcp": {
|
|
10
|
+
"command": "erp-mcp",
|
|
11
|
+
"args": []
|
|
12
|
+
},
|
|
13
|
+
"credentials": {
|
|
14
|
+
"fields": [
|
|
15
|
+
{
|
|
16
|
+
"key": "databaseUrl",
|
|
17
|
+
"label": "Database URL (optional)",
|
|
18
|
+
"type": "text",
|
|
19
|
+
"placeholder": "Leave empty for local SQLite",
|
|
20
|
+
"optional": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"key": "mode",
|
|
24
|
+
"label": "Mode (optional)",
|
|
25
|
+
"type": "text",
|
|
26
|
+
"placeholder": "simulation (default) — live needs an adapter for your ERP",
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"envMapping": {
|
|
31
|
+
"DATABASE_URL": "databaseUrl",
|
|
32
|
+
"ERP_MODE": "mode"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"simulation": {
|
|
36
|
+
"field": "mode",
|
|
37
|
+
"default": "simulation"
|
|
38
|
+
},
|
|
39
|
+
"personalDefault": true,
|
|
40
|
+
"oauth": null,
|
|
41
|
+
"toolGating": {
|
|
42
|
+
"default": {
|
|
43
|
+
"executionMapping": {},
|
|
44
|
+
"staticExecution": {}
|
|
45
|
+
},
|
|
46
|
+
"overrides": {
|
|
47
|
+
"list_items": {
|
|
48
|
+
"category": "read",
|
|
49
|
+
"boundField": "read_access",
|
|
50
|
+
"requiredValue": "unlimited"
|
|
51
|
+
},
|
|
52
|
+
"get_item": {
|
|
53
|
+
"category": "read",
|
|
54
|
+
"boundField": "read_access",
|
|
55
|
+
"requiredValue": "unlimited"
|
|
56
|
+
},
|
|
57
|
+
"find_customers": {
|
|
58
|
+
"category": "read",
|
|
59
|
+
"boundField": "read_access",
|
|
60
|
+
"requiredValue": "unlimited"
|
|
61
|
+
},
|
|
62
|
+
"get_customer": {
|
|
63
|
+
"category": "read",
|
|
64
|
+
"boundField": "read_access",
|
|
65
|
+
"requiredValue": "unlimited"
|
|
66
|
+
},
|
|
67
|
+
"list_quotes": {
|
|
68
|
+
"category": "read",
|
|
69
|
+
"boundField": "read_access",
|
|
70
|
+
"requiredValue": "unlimited"
|
|
71
|
+
},
|
|
72
|
+
"get_quote": {
|
|
73
|
+
"category": "read",
|
|
74
|
+
"boundField": "read_access",
|
|
75
|
+
"requiredValue": "unlimited"
|
|
76
|
+
},
|
|
77
|
+
"list_orders": {
|
|
78
|
+
"category": "read",
|
|
79
|
+
"boundField": "read_access",
|
|
80
|
+
"requiredValue": "unlimited"
|
|
81
|
+
},
|
|
82
|
+
"get_order": {
|
|
83
|
+
"category": "read",
|
|
84
|
+
"boundField": "read_access",
|
|
85
|
+
"requiredValue": "unlimited"
|
|
86
|
+
},
|
|
87
|
+
"create_quote": {
|
|
88
|
+
"executionMapping": {
|
|
89
|
+
"value": "value",
|
|
90
|
+
"discount_pct": "discount_pct",
|
|
91
|
+
"currency": "currency"
|
|
92
|
+
},
|
|
93
|
+
"staticExecution": {
|
|
94
|
+
"action_type": "quote"
|
|
95
|
+
},
|
|
96
|
+
"actionLabel": "Quote drafted"
|
|
97
|
+
},
|
|
98
|
+
"update_quote": {
|
|
99
|
+
"executionMapping": {
|
|
100
|
+
"value": "value",
|
|
101
|
+
"discount_pct": "discount_pct",
|
|
102
|
+
"currency": "currency"
|
|
103
|
+
},
|
|
104
|
+
"staticExecution": {
|
|
105
|
+
"action_type": "quote"
|
|
106
|
+
},
|
|
107
|
+
"actionLabel": "Quote updated"
|
|
108
|
+
},
|
|
109
|
+
"send_quote": {
|
|
110
|
+
"executionMapping": {
|
|
111
|
+
"value": "value",
|
|
112
|
+
"discount_pct": "discount_pct",
|
|
113
|
+
"currency": "currency"
|
|
114
|
+
},
|
|
115
|
+
"staticExecution": {
|
|
116
|
+
"action_type": "send"
|
|
117
|
+
},
|
|
118
|
+
"actionLabel": "Quote sent"
|
|
119
|
+
},
|
|
120
|
+
"convert_quote_to_order": {
|
|
121
|
+
"executionMapping": {
|
|
122
|
+
"value": "value",
|
|
123
|
+
"discount_pct": "discount_pct",
|
|
124
|
+
"currency": "currency"
|
|
125
|
+
},
|
|
126
|
+
"staticExecution": {
|
|
127
|
+
"action_type": "order"
|
|
128
|
+
},
|
|
129
|
+
"actionLabel": "Quote converted to order"
|
|
130
|
+
},
|
|
131
|
+
"load_simulation": {
|
|
132
|
+
"executionMapping": {},
|
|
133
|
+
"staticExecution": {
|
|
134
|
+
"action_type": "setup"
|
|
135
|
+
},
|
|
136
|
+
"actionLabel": "Test data loaded"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"templates": [
|
|
141
|
+
{
|
|
142
|
+
"name": "Draft quotes",
|
|
143
|
+
"description": "Look up items and customers, create and edit draft quotes. Nothing is sent to a customer or converted to an order.",
|
|
144
|
+
"risk": "low",
|
|
145
|
+
"mode": "automatic",
|
|
146
|
+
"bounds": {
|
|
147
|
+
"read_access": "unlimited",
|
|
148
|
+
"value_max": "5000",
|
|
149
|
+
"discount_max": "15",
|
|
150
|
+
"order_value_daily_max": "0",
|
|
151
|
+
"quote_daily_max": "20",
|
|
152
|
+
"send_daily_max": "0",
|
|
153
|
+
"order_daily_max": "0",
|
|
154
|
+
"setup_daily_max": "0"
|
|
155
|
+
},
|
|
156
|
+
"context": {
|
|
157
|
+
"currency": "EUR"
|
|
158
|
+
},
|
|
159
|
+
"intent": "Allow the agent to browse items and customers and prepare draft quotes for my review. It cannot send a quote to a customer or convert one into an order.",
|
|
160
|
+
"ttl": 2592000,
|
|
161
|
+
"tags": [
|
|
162
|
+
"drafts only",
|
|
163
|
+
"no sends",
|
|
164
|
+
"no orders"
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "Send and confirm with review above limit",
|
|
169
|
+
"description": "Draft, send, and convert quotes into orders. Every send and every order requires your approval.",
|
|
170
|
+
"risk": "high",
|
|
171
|
+
"mode": "review",
|
|
172
|
+
"bounds": {
|
|
173
|
+
"read_access": "unlimited",
|
|
174
|
+
"value_max": "20000",
|
|
175
|
+
"discount_max": "20",
|
|
176
|
+
"order_value_daily_max": "50000",
|
|
177
|
+
"quote_daily_max": "50",
|
|
178
|
+
"send_daily_max": "20",
|
|
179
|
+
"order_daily_max": "10",
|
|
180
|
+
"setup_daily_max": "0"
|
|
181
|
+
},
|
|
182
|
+
"context": {
|
|
183
|
+
"currency": "EUR"
|
|
184
|
+
},
|
|
185
|
+
"intent": "Allow the agent to draft, send, and convert quotes into orders. Every quote sent to a customer and every order requires my review before it happens.",
|
|
186
|
+
"ttl": 2592000,
|
|
187
|
+
"tags": [
|
|
188
|
+
"20k EUR/document",
|
|
189
|
+
"review mode",
|
|
190
|
+
"10 orders/day"
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"name": "Load test data (with approval)",
|
|
195
|
+
"description": "Load a simulation package (customers and products) into the simulated ERP, for setting up a three-week test. No quoting, sending, or ordering — this template grants nothing but the one-time test-data load, and it still requires your approval.",
|
|
196
|
+
"risk": "high",
|
|
197
|
+
"mode": "review",
|
|
198
|
+
"bounds": {
|
|
199
|
+
"read_access": "none",
|
|
200
|
+
"value_max": "0",
|
|
201
|
+
"discount_max": "0",
|
|
202
|
+
"order_value_daily_max": "0",
|
|
203
|
+
"quote_daily_max": "0",
|
|
204
|
+
"send_daily_max": "0",
|
|
205
|
+
"order_daily_max": "0",
|
|
206
|
+
"setup_daily_max": "1"
|
|
207
|
+
},
|
|
208
|
+
"context": {
|
|
209
|
+
"currency": "EUR"
|
|
210
|
+
},
|
|
211
|
+
"intent": "Allow the agent to load one simulation package of test data into the simulated ERP, for my review before it happens. No other ERP action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
|
|
212
|
+
"ttl": 2592000,
|
|
213
|
+
"tags": [
|
|
214
|
+
"setup only",
|
|
215
|
+
"review mode",
|
|
216
|
+
"1 load"
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"setupHint": "No configuration needed — works out of the box.",
|
|
221
|
+
"setupGuide": [
|
|
222
|
+
{
|
|
223
|
+
"title": "Ready to use",
|
|
224
|
+
"description": "The ERP connector stores data locally on your machine, seeded with a small demo catalog and customer list. No external account or API key needed."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"title": "Optional: use a shared database",
|
|
228
|
+
"description": "For team use, provide a PostgreSQL connection string in the Database URL field. Otherwise, leave it empty to use local SQLite storage."
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}
|