@suveren/gateway 0.18.2 → 0.19.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.
@@ -315,6 +315,19 @@ interface ProfileBoundsField {
315
315
  * kinds, and the same boundType can carry different units.
316
316
  */
317
317
  unit?: FieldUnit;
318
+ /**
319
+ * The largest value a mandate may set for this (numeric) bound — a ceiling
320
+ * on what the owner can grant, declared by the profile. E.g. reporting@0.2
321
+ * `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
322
+ * further than a year.
323
+ *
324
+ * Distinct from the bound itself: the bound limits each ACTION; `maximum`
325
+ * limits the MANDATE. A value above it is not a valid mandate for this
326
+ * profile — the mandate editor caps its input, the issuing Authority Server
327
+ * should refuse it, and an enforcement point that reads the bound applies
328
+ * min(value, maximum). Absent → no ceiling.
329
+ */
330
+ maximum?: number;
318
331
  /** @deprecated v0.4: use boundType instead. */
319
332
  constraint?: FieldConstraint;
320
333
  /** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanagencyp/hap-core",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -320,6 +320,19 @@ export interface ProfileBoundsField {
320
320
  * kinds, and the same boundType can carry different units.
321
321
  */
322
322
  unit?: FieldUnit;
323
+ /**
324
+ * The largest value a mandate may set for this (numeric) bound — a ceiling
325
+ * on what the owner can grant, declared by the profile. E.g. reporting@0.2
326
+ * `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
327
+ * further than a year.
328
+ *
329
+ * Distinct from the bound itself: the bound limits each ACTION; `maximum`
330
+ * limits the MANDATE. A value above it is not a valid mandate for this
331
+ * profile — the mandate editor caps its input, the issuing Authority Server
332
+ * should refuse it, and an enforcement point that reads the bound applies
333
+ * min(value, maximum). Absent → no ceiling.
334
+ */
335
+ maximum?: number;
323
336
  /** @deprecated v0.4: use boundType instead. */
324
337
  constraint?: FieldConstraint;
325
338
  /** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suveren/gateway",
3
- "version": "0.18.2",
3
+ "version": "0.19.1",
4
4
  "description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -30,6 +30,7 @@
30
30
  "github.com/humanagencyprotocol/hap-profiles/sales@0.3": "sales/0.3.profile.json",
31
31
  "github.com/humanagencyprotocol/hap-profiles/purchase@0.6": "purchase/0.6.profile.json",
32
32
  "github.com/humanagencyprotocol/hap-profiles/reporting@0.1": "reporting/0.1.profile.json",
33
- "github.com/humanagencyprotocol/hap-profiles/delegation@0.1": "delegation/0.1.profile.json"
33
+ "github.com/humanagencyprotocol/hap-profiles/delegation@0.1": "delegation/0.1.profile.json",
34
+ "github.com/humanagencyprotocol/hap-profiles/reporting@0.2": "reporting/0.2.profile.json"
34
35
  }
35
36
  }
@@ -0,0 +1,98 @@
1
+ {
2
+ "id": "github.com/humanagencyprotocol/hap-profiles/reporting@0.2",
3
+ "name": "Reporting",
4
+ "version": "0.2",
5
+ "description": "Reporting authority — lets an agent read the evidence a gateway holds (tickets, approvals, mandates, records in the systems it connects) and write one report stored on that machine. Changes no system.",
6
+ "whatsNew": "Adds a reporting window: the agent only sees, opens and exports evidence from the last N days (read_max_age_days). Anything older is invisible to it and cannot enter a report or an export.",
7
+ "boundsSchema": {
8
+ "actionTypes": [
9
+ "report"
10
+ ],
11
+ "keyOrder": [
12
+ "profile",
13
+ "read_access",
14
+ "read_max_age_days",
15
+ "report_daily_max"
16
+ ],
17
+ "fields": {
18
+ "profile": {
19
+ "type": "string",
20
+ "required": true
21
+ },
22
+ "read_access": {
23
+ "type": "string",
24
+ "required": true,
25
+ "displayName": "Read evidence",
26
+ "description": "Allow the agent to read tickets, approvals, mandates and the records in connected systems, to build a report",
27
+ "boundType": {
28
+ "kind": "enum",
29
+ "values": [
30
+ "unlimited",
31
+ "none"
32
+ ]
33
+ },
34
+ "toolsDescription": "tickets, approvals, mandates, system records",
35
+ "default": "unlimited"
36
+ },
37
+ "read_max_age_days": {
38
+ "type": "number",
39
+ "required": true,
40
+ "displayName": "Reporting window (days)",
41
+ "description": "How far back the agent may read evidence for a report: tickets, approvals, mandates and records from the last N days, at most 366. Anything older is invisible to the agent and cannot enter a report or an export. Enforced locally by the gateway (reads carry no ticket); signed here so the window the owner approved is part of the mandate.",
42
+ "unit": "days",
43
+ "boundType": {
44
+ "kind": "per_transaction",
45
+ "of": "read_age_days"
46
+ },
47
+ "maximum": 366,
48
+ "default": 30
49
+ },
50
+ "report_daily_max": {
51
+ "type": "number",
52
+ "required": true,
53
+ "displayName": "Report updates per day",
54
+ "description": "Maximum times per day the agent may write or replace the report",
55
+ "unit": "count",
56
+ "boundType": {
57
+ "kind": "cumulative_count",
58
+ "window": "daily"
59
+ },
60
+ "appliesTo": [
61
+ "report"
62
+ ]
63
+ }
64
+ }
65
+ },
66
+ "executionContextSchema": {
67
+ "fields": {
68
+ "report_count_daily": {
69
+ "source": "cumulative",
70
+ "cumulativeField": "_count",
71
+ "window": "daily",
72
+ "description": "Running daily count of report writes",
73
+ "required": true,
74
+ "constraint": {
75
+ "type": "number",
76
+ "enforceable": [
77
+ "max"
78
+ ]
79
+ }
80
+ }
81
+ }
82
+ },
83
+ "requiredGates": [
84
+ "bounds",
85
+ "intent",
86
+ "commitment",
87
+ "decision_owner"
88
+ ],
89
+ "ttl": {
90
+ "default": 86400,
91
+ "max": 2592000
92
+ },
93
+ "retention_minimum": 7776000,
94
+ "content_binding": {
95
+ "version": "1",
96
+ "kind": "jcs"
97
+ }
98
+ }
package/server.js CHANGED
@@ -10,11 +10,12 @@
10
10
  import { spawn } from 'node:child_process';
11
11
  import { dirname, join } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
- import { homedir, tmpdir, constants as osConstants } from 'node:os';
13
+ import { tmpdir, constants as osConstants } from 'node:os';
14
14
  import { existsSync, mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
15
15
  import { resolveCaFile, resolveProxyUrl, resolveSimulation } from './lib/config.mjs';
16
16
  import { readPolicy, isPolicyLocked } from './lib/policy.mjs';
17
17
  import { unsupportedNodeReason } from './lib/node-version.mjs';
18
+ import { resolveInstallSettings } from './lib/install-settings.mjs';
18
19
 
19
20
  // Docker and the login service start this file directly, not through the CLI,
20
21
  // so it checks the Node version too (see lib/node-version.mjs).
@@ -63,7 +64,19 @@ function exitLikeChild(code, signal) {
63
64
  // MERGED into one combined PEM file instead. Comparing against that combined
64
65
  // path would never match the saved path alone, so the guard has to be a
65
66
  // dedicated marker, not a value comparison (which would also re-exec forever).
66
- const DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join(homedir(), '.suveren');
67
+ // Ports + data folder: IT policy > env > saved install setting > default
68
+ // (see lib/install-settings.mjs). Resolved here, not just in the CLI,
69
+ // because the login service starts this file directly. Refuses to start on
70
+ // an invalid value rather than guessing a port or a folder.
71
+ let INSTALL;
72
+ try {
73
+ INSTALL = resolveInstallSettings();
74
+ } catch (err) {
75
+ console.error(`[suveren-gateway] ${err instanceof Error ? err.message : String(err)}`);
76
+ console.error('[suveren-gateway] Fix it with `suveren-gateway config set …` (see `suveren-gateway config help`).');
77
+ process.exit(1);
78
+ }
79
+ const DATA_DIR = INSTALL.dataDir;
67
80
  const savedCaFile = resolveCaFile(DATA_DIR);
68
81
  if (savedCaFile && !process.env.SUVEREN_CA_REEXEC_DONE) {
69
82
  let effectiveCaFile = savedCaFile;
@@ -159,8 +172,8 @@ if (policyNoProxy) {
159
172
  process.env.NO_PROXY = policyNoProxy;
160
173
  }
161
174
 
162
- const CP_PORT = process.env.SUVEREN_CP_PORT ?? '3400';
163
- const MCP_PORT = process.env.SUVEREN_MCP_PORT ?? '3430';
175
+ const CP_PORT = String(INSTALL.cpPort);
176
+ const MCP_PORT = String(INSTALL.mcpPort);
164
177
  const UI_DIST = process.env.HAP_UI_DIST ?? join(__dirname, 'dist', 'ui');
165
178
  // Integration manifests + profile catalog ship inside the bundle so a
166
179
  // fresh `npm install -g` install has working integrations and profiles
@@ -176,6 +189,9 @@ const env = {
176
189
  NODE_ENV: 'production',
177
190
  SUVEREN_CP_PORT: CP_PORT,
178
191
  SUVEREN_MCP_PORT: MCP_PORT,
192
+ // Only when not the default: the control plane's one-time ~/.hap →
193
+ // ~/.suveren migration runs only while SUVEREN_DATA_DIR is unset.
194
+ ...(INSTALL.source.dataDir !== 'default' ? { SUVEREN_DATA_DIR: DATA_DIR } : {}),
179
195
  // The control plane talks to the MCP server over this URL. It defaults to
180
196
  // 127.0.0.1:3430 in mcp-bridge.ts, so if the user overrode SUVEREN_MCP_PORT
181
197
  // without also setting this, every CP→MCP internal call hit the wrong port