@abstractframework/continuum 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +41 -7
  2. package/bin/cli.js +16 -160
  3. package/bin/hub_proxy.js +52 -43
  4. package/bin/request_gate.js +43 -0
  5. package/bin/server.js +232 -0
  6. package/bin/settings.js +45 -28
  7. package/dist/assets/{abnfDiagram-VRR7QNED-C3nZSPxd.js → abnfDiagram-VRR7QNED-E9lbmtBV.js} +2 -2
  8. package/dist/assets/{arc-Co6CTENJ.js → arc-XVGGKtJ-.js} +2 -2
  9. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CEz8QvCN.js → architectureDiagram-ZJ3FMSHR-GRV2Ej0c.js} +2 -2
  10. package/dist/assets/{blockDiagram-677ZJIJ3-B_5v6oR-.js → blockDiagram-677ZJIJ3-Dz8wPMHC.js} +2 -2
  11. package/dist/assets/{c4Diagram-LMCZKHZV-B2dYGCte.js → c4Diagram-LMCZKHZV-CAHne5Am.js} +2 -2
  12. package/dist/assets/channel-BbrZydfX.js +2 -0
  13. package/dist/assets/{chunk-2Q5K7J3B-2eGZLgzu.js → chunk-2Q5K7J3B-DFXMgoy3.js} +2 -2
  14. package/dist/assets/{chunk-32BRIVSS-szXiGdYg.js → chunk-32BRIVSS-D9T7Whin.js} +2 -2
  15. package/dist/assets/{chunk-5VM5RSS4-F7rd4WOC.js → chunk-5VM5RSS4-D8jr-VV1.js} +2 -2
  16. package/dist/assets/{chunk-EX3LRPZG-BBaSONus.js → chunk-EX3LRPZG-BSkCDWzU.js} +2 -2
  17. package/dist/assets/{chunk-JWPE2WC7-BzQLSiNr.js → chunk-JWPE2WC7-iYmSeuV1.js} +2 -2
  18. package/dist/assets/{chunk-MOJQB5TN-CV4r6R-P.js → chunk-MOJQB5TN-BxgBSbz5.js} +2 -2
  19. package/dist/assets/{chunk-RYQCIY6F-lSwdPTG2.js → chunk-RYQCIY6F-D5JkyQAc.js} +2 -2
  20. package/dist/assets/{chunk-V7JOEXUC-BVe47Wh6.js → chunk-V7JOEXUC-B_6OmmFi.js} +2 -2
  21. package/dist/assets/{chunk-VR4S4FIN-Xsn7KCEd.js → chunk-VR4S4FIN-BsoDEIUS.js} +2 -2
  22. package/dist/assets/{chunk-XXDRQBXY-DdG9ww2-.js → chunk-XXDRQBXY-DG-goPoO.js} +2 -2
  23. package/dist/assets/classDiagram-OUVF2IWQ-BvV49U6G.js +2 -0
  24. package/dist/assets/classDiagram-v2-EOCWNBFH-BvV49U6G.js +2 -0
  25. package/dist/assets/{cose-bilkent-JH36ORCC-DDdy2AQ-.js → cose-bilkent-JH36ORCC-C95YoC9P.js} +2 -2
  26. package/dist/assets/{cynefin-VYW2F7L2-PkaGuTvX.js → cynefin-VYW2F7L2-A7Vh09t2.js} +3 -3
  27. package/dist/assets/{cynefinDiagram-TSTJHNR4-BHIvz7Or.js → cynefinDiagram-TSTJHNR4-BI8-04G-.js} +2 -2
  28. package/dist/assets/{dagre-VKFMJZFB-Dt336PVs.js → dagre-VKFMJZFB-owAzFOdh.js} +2 -2
  29. package/dist/assets/{diagram-FQU43EPY-DrmE36bq.js → diagram-FQU43EPY-B8FPxd72.js} +2 -2
  30. package/dist/assets/{diagram-G47NLZAW-tpUlukyV.js → diagram-G47NLZAW-CwooMJF9.js} +2 -2
  31. package/dist/assets/{diagram-NH7WQ7WH-CO5Zb4_d.js → diagram-NH7WQ7WH-Cr-bMH-G.js} +2 -2
  32. package/dist/assets/{diagram-OA4YK3LP-B0FwAFRE.js → diagram-OA4YK3LP-3rHTMgXR.js} +2 -2
  33. package/dist/assets/{diagram-WEI45ONY-CcLxvhZ0.js → diagram-WEI45ONY-BG4PjrBn.js} +2 -2
  34. package/dist/assets/{ebnfDiagram-CCIWWBDH-CdGlfEot.js → ebnfDiagram-CCIWWBDH-B6uukG4X.js} +2 -2
  35. package/dist/assets/{erDiagram-Q63AITRT-CxYfUHK8.js → erDiagram-Q63AITRT-D3OgqYHT.js} +2 -2
  36. package/dist/assets/{flowDiagram-23GEKE2U-fW4F9z69.js → flowDiagram-23GEKE2U-CxTaDERr.js} +2 -2
  37. package/dist/assets/{ganttDiagram-NO4QXBWP-BWM-HcND.js → ganttDiagram-NO4QXBWP-CN7iAeQh.js} +2 -2
  38. package/dist/assets/{gitGraphDiagram-IHSO6WYX-BlcnohGg.js → gitGraphDiagram-IHSO6WYX-DbvcWmmM.js} +2 -2
  39. package/dist/assets/index-Ca1Qebcu.js +121 -0
  40. package/dist/assets/index-fh60G2KG.css +1 -0
  41. package/dist/assets/{infoDiagram-FWYZ7A6U-pJSOWVdi.js → infoDiagram-FWYZ7A6U-DX4NPNgR.js} +2 -2
  42. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CpvrrPMQ.js → ishikawaDiagram-FXEZZL3T-DtL3v287.js} +2 -2
  43. package/dist/assets/{journeyDiagram-5HDEW3XC-DzFKBKHk.js → journeyDiagram-5HDEW3XC-CtAZFiGl.js} +2 -2
  44. package/dist/assets/{kanban-definition-HUTT4EX6-BPmBQCPR.js → kanban-definition-HUTT4EX6-CvrCkrKt.js} +2 -2
  45. package/dist/assets/{linear-DmNa6TPI.js → linear-CbLBdTPp.js} +2 -2
  46. package/dist/assets/{mermaid.core-_-YW0o95.js → mermaid.core-zt0bcQ8D.js} +6 -6
  47. package/dist/assets/{mindmap-definition-LN4V7U3C-Bdwo0rlk.js → mindmap-definition-LN4V7U3C-DU-r_mE6.js} +2 -2
  48. package/dist/assets/{pegDiagram-2B236MQR-CHZoedfQ.js → pegDiagram-2B236MQR-CoiPuGzc.js} +2 -2
  49. package/dist/assets/{pieDiagram-ENE6RG2P-Blp676Hr.js → pieDiagram-ENE6RG2P-ClUZORQg.js} +2 -2
  50. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DhWHDdBX.js → quadrantDiagram-ABIIQ3AL-D0i0p2xo.js} +2 -2
  51. package/dist/assets/{railroadDiagram-RFXS5EU6-CEgbBDEg.js → railroadDiagram-RFXS5EU6-wI6Hdpa5.js} +2 -2
  52. package/dist/assets/{requirementDiagram-TGXJPOKE-Ds8x8FbV.js → requirementDiagram-TGXJPOKE-Bm4qvEra.js} +2 -2
  53. package/dist/assets/{sankeyDiagram-HTMAVEWB-yKIsBDhP.js → sankeyDiagram-HTMAVEWB-DX8is9uB.js} +2 -2
  54. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DAgYlOsI.js → sequenceDiagram-DBY2YBRQ-5RpIQiJh.js} +2 -2
  55. package/dist/assets/{sizeCapture-X5ZJPWSS-C-LAlxdw.js → sizeCapture-X5ZJPWSS-ChX6cFSk.js} +2 -2
  56. package/dist/assets/{stateDiagram-2N3HPSRC-BLiS6piZ.js → stateDiagram-2N3HPSRC-Tea20pcW.js} +2 -2
  57. package/dist/assets/stateDiagram-v2-6OUMAXLB-CCBRDLbX.js +2 -0
  58. package/dist/assets/swimlanes-5IMT3BWC-BY7gSBH7.js +3 -0
  59. package/dist/assets/swimlanesDiagram-G3AALYLV-Dtz4rZCK.js +9 -0
  60. package/dist/assets/{timeline-definition-FHXFAJF6-BrJ5JJYj.js → timeline-definition-FHXFAJF6-Bq52M8-r.js} +2 -2
  61. package/dist/assets/{vennDiagram-L72KCM5P-BT9M9-9s.js → vennDiagram-L72KCM5P-BGBcMmhw.js} +2 -2
  62. package/dist/assets/{wardleyDiagram-EHGQE667-CukiKHz8.js → wardleyDiagram-EHGQE667-T3JqsmMn.js} +2 -2
  63. package/dist/assets/{xychartDiagram-FW5EYKEG-CIidttLL.js → xychartDiagram-FW5EYKEG-B0yTh8-d.js} +2 -2
  64. package/dist/index.html +7 -3
  65. package/package.json +5 -3
  66. package/dist/assets/channel-D3yEdLfb.js +0 -2
  67. package/dist/assets/classDiagram-OUVF2IWQ-DJJxVI5p.js +0 -2
  68. package/dist/assets/classDiagram-v2-EOCWNBFH-DJJxVI5p.js +0 -2
  69. package/dist/assets/index-CFGJJTR4.js +0 -123
  70. package/dist/assets/index-DAHZ6K6R.css +0 -1
  71. package/dist/assets/stateDiagram-v2-6OUMAXLB-CdmuVCk9.js +0 -2
  72. package/dist/assets/swimlanes-5IMT3BWC-Bj_dHHPI.js +0 -3
  73. package/dist/assets/swimlanesDiagram-G3AALYLV-CYrgsk-L.js +0 -9
package/bin/server.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The AbstractContinuum HTTP server: the built console, the app-origin
3
+ * gateway session proxy (/api/* → the gateway), the Team page's hub proxy
4
+ * (/api/hub/*, HTTP + the /api/hub/ws relay) and the Settings route.
5
+ *
6
+ * Mountable under the gateway at /apps/continuum/ (the
7
+ * @abstractframework/app-server mount contract): every response, the
8
+ * WebSocket 101 included, announces `X-AbstractFramework-App: continuum;
9
+ * mount=1`; the page gets `<base href="<base path>/">` and `base_path`;
10
+ * cookies carry `Path=<base path>/`; and every "is this the operator's own
11
+ * computer?" check reads the kit's requestContext (bin/request_gate.js),
12
+ * never the socket peer. Served at `/` on its own port it works exactly as
13
+ * before.
14
+ */
15
+
16
+ import * as http from 'http';
17
+ import { createHash } from 'crypto';
18
+ import { readFileSync, existsSync, statSync } from 'fs';
19
+ import { join, extname } from 'path';
20
+ import {
21
+ MountRequestError,
22
+ createGatewaySessionProxy,
23
+ createGatewayUrlResolver,
24
+ createMountedHandler,
25
+ injectShell,
26
+ rejectUpgrade,
27
+ requestContext,
28
+ } from '@abstractframework/app-server';
29
+ import { APP_ID, createHubProxy } from './hub_proxy.js';
30
+ import { createSettingsRoute, expand_home } from './settings.js';
31
+
32
+ const MIME_TYPES = {
33
+ '.html': 'text/html',
34
+ '.js': 'application/javascript',
35
+ '.css': 'text/css',
36
+ '.json': 'application/json',
37
+ '.png': 'image/png',
38
+ '.jpg': 'image/jpeg',
39
+ '.jpeg': 'image/jpeg',
40
+ '.gif': 'image/gif',
41
+ '.svg': 'image/svg+xml',
42
+ '.ico': 'image/x-icon',
43
+ '.webmanifest': 'application/manifest+json',
44
+ };
45
+
46
+ function getMimeType(filePath) {
47
+ const ext = extname(filePath).toLowerCase();
48
+ return MIME_TYPES[ext] || 'application/octet-stream';
49
+ }
50
+
51
+ // Content-Security-Policy for the app document (security adversary P1
52
+ // 2026-07-16): untrusted markdown (channel files, messages, attachments)
53
+ // renders in this app, and standard markdown allows remote images — a
54
+ // crafted ![](https://attacker/beacon.png) would beacon the operator's IP
55
+ // and read-timing to an external host the moment a preview opens. img-src
56
+ // 'self' data: kills that class app-wide. script-src 'self' is a backstop
57
+ // belt (the renderer already cannot emit script). ws:/wss: are needed
58
+ // because 'self' does not reliably cover scheme-different WebSocket dials;
59
+ // only our own bundle can open sockets (script-src), so this stays tight.
60
+ const CSP = [
61
+ "default-src 'self'",
62
+ "script-src 'self'",
63
+ "style-src 'self' 'unsafe-inline'", // React style={} attributes
64
+ "img-src 'self' data:",
65
+ "font-src 'self' data:",
66
+ "connect-src 'self' ws: wss:",
67
+ "object-src 'none'",
68
+ "base-uri 'self'",
69
+ "form-action 'self'",
70
+ "frame-ancestors 'none'",
71
+ ].join('; ');
72
+
73
+ /**
74
+ * settings: live settings (bin/settings.js createLiveSettings); distDir: the
75
+ * built console. The gateway URL, like the port and host, is read once from
76
+ * the settings at creation.
77
+ */
78
+ export function createContinuumServer({ settings, distDir }) {
79
+ const AT_START = settings.get();
80
+ const DEFAULT_GATEWAY_URL = AT_START.gateway_url.value;
81
+ const DIST_DIR = distDir;
82
+ // A gateway URL nobody chose (the local gateway pointer, or the built-in
83
+ // default) follows the pointer while the server runs: the kit's resolver
84
+ // re-reads it when the gateway refuses a connection. A flag, setting or
85
+ // environment choice never moves.
86
+ const CHOSEN_GATEWAY = ['flag', 'setting', 'env'].includes(AT_START.gateway_url.source);
87
+
88
+ /**
89
+ * The console document for one request: the kit's injectShell puts
90
+ * `<base href="<base path>/">` first in <head> (every relative asset and
91
+ * fetch resolves under /apps/continuum/ behind the gateway, under / on the
92
+ * app's own port) plus `window.__ABSTRACT_UI_CONFIG__` (gateway_url,
93
+ * base_path) in one inline script. The CSP admits exactly that script by
94
+ * its hash; every other inline script stays refused.
95
+ */
96
+ function shellResponse(html, ctx) {
97
+ const page = injectShell(html, { basePath: ctx.basePath, config: { gateway_url: gatewaySessionProxy.defaultGatewayUrl } });
98
+ const inline = /<base [^>]*><script>([\s\S]*?)<\/script>/.exec(page);
99
+ if (!inline) throw new Error('injectShell produced no inline config script');
100
+ const hash = createHash('sha256').update(inline[1], 'utf8').digest('base64');
101
+ const csp = CSP.replace("script-src 'self'", `script-src 'self' 'sha256-${hash}'`);
102
+ return { page, csp };
103
+ }
104
+
105
+ function serveFile(res, filePath, ctx) {
106
+ try {
107
+ if (!existsSync(filePath) || !statSync(filePath).isFile()) {
108
+ return false;
109
+ }
110
+ const mime = getMimeType(filePath);
111
+ const headers = {
112
+ 'Content-Type': mime,
113
+ 'Cache-Control': 'no-cache',
114
+ 'X-Content-Type-Options': 'nosniff',
115
+ };
116
+ if (mime === 'text/html') {
117
+ // CSP is a document policy — attach it where a document loads.
118
+ const { page, csp } = shellResponse(readFileSync(filePath, 'utf8'), ctx);
119
+ headers['Content-Type'] = 'text/html; charset=utf-8';
120
+ headers['Content-Security-Policy'] = csp;
121
+ res.writeHead(200, headers);
122
+ res.end(page);
123
+ return true;
124
+ }
125
+ res.writeHead(200, headers);
126
+ res.end(readFileSync(filePath));
127
+ return true;
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ // Cookies abstractcontinuum_gateway_* and header x-abstractcontinuum-csrf
134
+ // derive from appId; mounted, the cookies carry Path=/apps/continuum/.
135
+ const gatewaySessionProxy = createGatewaySessionProxy({
136
+ appId: 'abstractcontinuum',
137
+ defaultGatewayUrl: CHOSEN_GATEWAY ? DEFAULT_GATEWAY_URL : createGatewayUrlResolver({}),
138
+ });
139
+
140
+ // Team page transport: /api/hub/* forwards an allowlisted hub API subset
141
+ // with the OPERATOR seat's key attached server-side (proposal c1692,
142
+ // agency contract c1696 — authorship stays the operator's own).
143
+ // Every hub setting is a getter: a seat saved on the Settings page or with
144
+ // `abstractcontinuum config set` applies without a restart.
145
+ const hubProxy = createHubProxy({
146
+ hubUrl: () => settings.value('hub_url'),
147
+ seat: () => settings.value('hub_seat'),
148
+ keysPath: () => expand_home(settings.value('hub_keys')),
149
+ token: () => settings.value('hub_token'),
150
+ allowRemote: () => settings.value('hub_allow_remote'),
151
+ });
152
+
153
+ // The Settings page's door to this server's own settings (hub seat).
154
+ const settingsRoute = createSettingsRoute({ live: settings });
155
+
156
+ const server = http.createServer(createMountedHandler({ appId: APP_ID }, (req, res, ctx) => {
157
+ // Parse against a FIXED base: a malformed Host header (e.g. "a b") makes
158
+ // new URL throw synchronously, which would kill the whole process from
159
+ // one bad request (adversarial P1 2026-07-13). The host plays no role in
160
+ // routing here — only the pathname does.
161
+ let pathname = '/';
162
+ let search = '';
163
+ try {
164
+ const u = new URL(req.url, 'http://local');
165
+ pathname = u.pathname;
166
+ search = u.search;
167
+ } catch {
168
+ res.writeHead(400);
169
+ res.end('Bad Request');
170
+ return;
171
+ }
172
+
173
+ if (settingsRoute.handle(req, res, pathname)) {
174
+ return;
175
+ }
176
+
177
+ if (hubProxy.handle(req, res, pathname, search)) {
178
+ return;
179
+ }
180
+
181
+ if (gatewaySessionProxy.handle(req, res, pathname)) {
182
+ return;
183
+ }
184
+
185
+ if (pathname.includes('..')) {
186
+ res.writeHead(400);
187
+ res.end('Bad Request');
188
+ return;
189
+ }
190
+
191
+ if (pathname === '/' || pathname === '') {
192
+ pathname = '/index.html';
193
+ }
194
+
195
+ const filePath = join(DIST_DIR, pathname);
196
+ if (serveFile(res, filePath, ctx)) return;
197
+ if (serveFile(res, filePath + '.html', ctx)) return;
198
+
199
+ // SPA fallback
200
+ if (serveFile(res, join(DIST_DIR, 'index.html'), ctx)) return;
201
+
202
+ res.writeHead(404);
203
+ res.end('Not Found');
204
+ }));
205
+
206
+ // Live hub updates: relay browser WebSockets to the hub with the seat key
207
+ // attached server-side (operator c2240 — realtime, not 5s polling).
208
+ server.on('upgrade', (req, socket, head) => {
209
+ // F7 (dm-99 tail): a raw upgrade socket with no 'error' listener is a
210
+ // process-killer (browser aborts mid-handshake raise uncaught 'error');
211
+ // and a rejected relay promise without .catch is an unhandled rejection.
212
+ socket.on('error', () => {
213
+ try { socket.destroy(); } catch { /* already gone */ }
214
+ });
215
+ // A malformed forwarded header or an unknown peer: refused before any
216
+ // route (the kit's rule; the hub relay applies it again for its gate).
217
+ try {
218
+ requestContext(req);
219
+ } catch (e) {
220
+ if (!(e instanceof MountRequestError)) throw e;
221
+ rejectUpgrade(socket, e.status, e.message);
222
+ return;
223
+ }
224
+ void hubProxy.handleUpgrade(req, socket, head).then((handled) => {
225
+ if (!handled) rejectUpgrade(socket, 404, 'No WebSocket here.');
226
+ }).catch(() => {
227
+ try { socket.destroy(); } catch { /* already gone */ }
228
+ });
229
+ });
230
+
231
+ return server;
232
+ }
package/bin/settings.js CHANGED
@@ -22,6 +22,8 @@
22
22
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
23
23
  import { homedir } from 'os';
24
24
  import { dirname, join } from 'path';
25
+ import { MountRequestError, resolveGatewayUrl } from '@abstractframework/app-server';
26
+ import { request_gate } from './request_gate.js';
25
27
 
26
28
  const TRUE_VALUES = new Set(['1', 'true', 'yes', 'y', 'on']);
27
29
  const FALSE_VALUES = new Set(['0', 'false', 'no', 'n', 'off']);
@@ -50,7 +52,12 @@ export const SETTINGS = [
50
52
  {
51
53
  key: 'gateway_url',
52
54
  flag: '--gateway-url',
55
+ // The same spellings as every AbstractFramework app
56
+ // (@abstractframework/app-server parseAppFlags).
57
+ aliases: ['--gateway', '--url'],
53
58
  type: 'url',
59
+ // With no flag, setting or environment: the gateway installed on this
60
+ // computer (~/.abstractframework/gateway.json), else this URL.
54
61
  default: 'http://127.0.0.1:8080',
55
62
  env: ['ABSTRACTCONTINUUM_GATEWAY_URL', 'ABSTRACTGATEWAY_URL'],
56
63
  metavar: '<url>',
@@ -134,7 +141,7 @@ export const SETTINGS = [
134
141
  export const UI_WRITABLE_KEYS = ['hub_seat'];
135
142
 
136
143
  const BY_KEY = new Map(SETTINGS.map((s) => [s.key, s]));
137
- const BY_FLAG = new Map(SETTINGS.map((s) => [s.flag, s]));
144
+ const BY_FLAG = new Map(SETTINGS.flatMap((s) => [s.flag, ...(s.aliases || [])].map((f) => [f, s])));
138
145
 
139
146
  export function setting_spec(key) {
140
147
  return BY_KEY.get(key) || null;
@@ -195,11 +202,16 @@ export function coerce(spec, raw) {
195
202
  /**
196
203
  * THE precedence function: flag > settings file > environment (legacy) >
197
204
  * default, per key. Returns `{ [key]: { value, source } }` with source one
198
- * of "flag" | "setting" | "env" | "default". An invalid file or env value
199
- * is skipped (reported in `problems`) so one bad entry cannot stop the
200
- * server; an invalid FLAG never reaches here (parse_args refuses it).
205
+ * of "flag" | "setting" | "env" | "pointer" | "default". An invalid file or
206
+ * env value is skipped (reported in `problems`) so one bad entry cannot
207
+ * stop the server; an invalid FLAG never reaches here (parse_args refuses
208
+ * it).
209
+ *
210
+ * gateway_url with nothing chosen follows the kit's local gateway pointer
211
+ * (`~/.abstractframework/gateway.json` under `home`, source "pointer"),
212
+ * else the built-in default: the same reader as every other app.
201
213
  */
202
- export function resolve_settings({ flags = {}, file = {}, env = {} } = {}) {
214
+ export function resolve_settings({ flags = {}, file = {}, env = {}, home, pointer } = {}) {
203
215
  const out = {};
204
216
  const problems = [];
205
217
  for (const spec of SETTINGS) {
@@ -227,6 +239,10 @@ export function resolve_settings({ flags = {}, file = {}, env = {} } = {}) {
227
239
  break;
228
240
  }
229
241
  }
242
+ if (!picked && k === 'gateway_url') {
243
+ const r = resolveGatewayUrl({ home, pointer, warn: (m) => problems.push(m) });
244
+ picked = { value: r.url, source: r.source === 'pointer' ? 'pointer' : 'default' };
245
+ }
230
246
  if (!picked) picked = { value: spec.default, source: 'default' };
231
247
  out[k] = picked;
232
248
  }
@@ -276,7 +292,13 @@ export function display_value(spec, value) {
276
292
  return String(value);
277
293
  }
278
294
 
279
- export const SOURCE_WORDS = { flag: 'launch flag', setting: 'setting', env: 'environment (legacy)', default: 'default' };
295
+ export const SOURCE_WORDS = {
296
+ flag: 'launch flag',
297
+ setting: 'setting',
298
+ env: 'environment (legacy)',
299
+ pointer: 'the gateway installed on this computer',
300
+ default: 'default',
301
+ };
280
302
 
281
303
  /**
282
304
  * Parse argv. Shapes:
@@ -379,7 +401,7 @@ export function apply_session_proxy_gates(settings, env) {
379
401
  * without a restart). Port, host, gateway URL and the sign-in gates are
380
402
  * read once at start.
381
403
  */
382
- export function createLiveSettings({ flags = {}, settingsPath, env = process.env, onProblem = () => {} }) {
404
+ export function createLiveSettings({ flags = {}, settingsPath, env = process.env, home, onProblem = () => {} }) {
383
405
  let stamp = '';
384
406
  let file = {};
385
407
  let resolved = null;
@@ -398,7 +420,7 @@ export function createLiveSettings({ flags = {}, settingsPath, env = process.env
398
420
  } catch (e) {
399
421
  onProblem(`settings file ${settingsPath}: ${e.message}`);
400
422
  }
401
- const r = resolve_settings({ flags, file, env });
423
+ const r = resolve_settings({ flags, file, env, home });
402
424
  for (const p of r.problems) onProblem(p);
403
425
  resolved = r.settings;
404
426
  return resolved;
@@ -421,21 +443,6 @@ function send_json(res, status, body) {
421
443
  res.end(JSON.stringify(body));
422
444
  }
423
445
 
424
- function is_loopback_peer(req) {
425
- const peer = String(req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : '');
426
- return peer === '127.0.0.1' || peer === '::1' || peer === '::ffff:127.0.0.1' || peer === '';
427
- }
428
-
429
- function same_origin(req) {
430
- const origin = String((req.headers && req.headers.origin) || '').trim();
431
- if (!origin) return true;
432
- try {
433
- return new URL(origin).host.toLowerCase() === String((req.headers && req.headers.host) || '').trim().toLowerCase();
434
- } catch {
435
- return false;
436
- }
437
- }
438
-
439
446
  /** Public view: every setting with its source; secrets as presence only. */
440
447
  export function settings_view(live, extra = {}) {
441
448
  const s = live.get();
@@ -449,9 +456,11 @@ export function settings_view(live, extra = {}) {
449
456
 
450
457
  /**
451
458
  * GET/PUT /api/continuum/settings — the Settings page's door to this
452
- * server's own settings. Local only: the socket peer must be loopback
453
- * (the same gate as the hub proxy, which this seat setting steers) and a
454
- * browser Origin must match the Host. PUT takes `{ hub_seat: "<seat>" }`
459
+ * server's own settings. Local only: the BROWSER must run on this computer
460
+ * (bin/request_gate.js — behind the gateway's /apps/continuum/ that is the
461
+ * forwarded client address, never the socket peer; the same gate as the hub
462
+ * proxy, which this seat setting steers) and a browser Origin must match
463
+ * the host it addressed. PUT takes `{ hub_seat: "<seat>" }`
455
464
  * or `{ hub_seat: null }` (back to the default) and persists to the
456
465
  * settings file.
457
466
  */
@@ -462,11 +471,19 @@ export function createSettingsRoute({ live, extra = () => ({}) }) {
462
471
  path: SETTINGS_ROUTE,
463
472
  handle(req, res, pathname) {
464
473
  if (pathname !== SETTINGS_ROUTE) return false;
465
- if (!is_loopback_peer(req)) {
474
+ let gate;
475
+ try {
476
+ gate = request_gate(req);
477
+ } catch (e) {
478
+ if (!(e instanceof MountRequestError)) throw e;
479
+ send_json(res, e.status, { error: 'bad_request', detail: e.message });
480
+ return true;
481
+ }
482
+ if (!gate.loopback) {
466
483
  send_json(res, 403, { error: 'settings_non_loopback', detail: "Continuum's settings can only be changed from the computer it runs on." });
467
484
  return true;
468
485
  }
469
- if (!same_origin(req)) {
486
+ if (!gate.same_origin) {
470
487
  send_json(res, 403, { error: 'settings_cross_origin', detail: 'Cross-origin requests to the settings route are refused.' });
471
488
  return true;
472
489
  }
@@ -1,2 +1,2 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-CV4r6R-P.js";import{p as f}from"./chunk-JWPE2WC7-BzQLSiNr.js";import{_ as n,l as o}from"./mermaid.core-_-YW0o95.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-PkaGuTvX.js";import"./index-CFGJJTR4.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
2
- //# sourceMappingURL=abnfDiagram-VRR7QNED-C3nZSPxd.js.map
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-BxgBSbz5.js";import{p as f}from"./chunk-JWPE2WC7-iYmSeuV1.js";import{_ as n,l as o}from"./mermaid.core-zt0bcQ8D.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-A7Vh09t2.js";import"./index-Ca1Qebcu.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
2
+ //# sourceMappingURL=abnfDiagram-VRR7QNED-E9lbmtBV.js.map
@@ -1,2 +1,2 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-_-YW0o95.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
2
- //# sourceMappingURL=arc-Co6CTENJ.js.map
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-zt0bcQ8D.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
2
+ //# sourceMappingURL=arc-XVGGKtJ-.js.map