@forgezero/agent 0.1.32 → 0.1.34

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.
@@ -104,6 +104,121 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
104
104
  return results;
105
105
  }
106
106
 
107
+ // src/capacity-calibration.ts
108
+ var percentile95 = (values) => {
109
+ if (values.length === 0)
110
+ return Number.POSITIVE_INFINITY;
111
+ const sorted = values.toSorted((left, right) => left - right);
112
+ return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.95) - 1)];
113
+ };
114
+ function localCalibrationEndpoint(value) {
115
+ const endpoint = new URL(value);
116
+ if (endpoint.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(endpoint.hostname) || !endpoint.port || endpoint.username || endpoint.password || endpoint.hash) {
117
+ throw new Error("Capacity calibration requires an explicit loopback HTTP endpoint and port.");
118
+ }
119
+ return endpoint;
120
+ }
121
+ function validateCapacityCalibrationOptions(options) {
122
+ const endpoint = localCalibrationEndpoint(options.endpoint);
123
+ const maxConcurrency = options.maxConcurrency ?? 256;
124
+ const requestsPerWorker = options.requestsPerWorker ?? 8;
125
+ const maxP95Ms = options.maxP95Ms ?? 250;
126
+ const maxErrorRate = options.maxErrorRate ?? 0.01;
127
+ const headroomRatio = options.headroomRatio ?? 0.8;
128
+ const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
129
+ if (!Number.isSafeInteger(maxConcurrency) || maxConcurrency < 1 || maxConcurrency > 4096 || !Number.isSafeInteger(requestsPerWorker) || requestsPerWorker < 2 || requestsPerWorker > 100 || !Number.isFinite(maxP95Ms) || maxP95Ms < 1 || !Number.isFinite(maxErrorRate) || maxErrorRate < 0 || maxErrorRate > 0.2 || !Number.isFinite(headroomRatio) || headroomRatio < 0.25 || headroomRatio > 0.95 || !Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 100 || requestTimeoutMs > 30000) {
130
+ throw new Error("Capacity calibration bounds are invalid.");
131
+ }
132
+ return {
133
+ endpoint,
134
+ maxConcurrency,
135
+ requestsPerWorker,
136
+ maxP95Ms,
137
+ maxErrorRate,
138
+ headroomRatio,
139
+ requestTimeoutMs
140
+ };
141
+ }
142
+ async function stage(endpoint, concurrency, requestsPerWorker, timeoutMs, fetcher) {
143
+ const latencies = [];
144
+ let succeeded = 0;
145
+ let failed = 0;
146
+ let overloaded = 0;
147
+ const started = performance.now();
148
+ await Promise.all(Array.from({ length: concurrency }, async () => {
149
+ for (let request = 0;request < requestsPerWorker; request += 1) {
150
+ const requestStarted = performance.now();
151
+ try {
152
+ const response = await fetcher(endpoint, {
153
+ method: "GET",
154
+ headers: { accept: "application/json", "user-agent": "forgezero-capacity-calibration/1" },
155
+ signal: AbortSignal.timeout(timeoutMs),
156
+ redirect: "error"
157
+ });
158
+ await response.body?.cancel();
159
+ if (response.ok)
160
+ succeeded += 1;
161
+ else {
162
+ failed += 1;
163
+ if (response.status === 503)
164
+ overloaded += 1;
165
+ }
166
+ } catch {
167
+ failed += 1;
168
+ } finally {
169
+ latencies.push(performance.now() - requestStarted);
170
+ }
171
+ }
172
+ }));
173
+ const elapsedSeconds = Math.max((performance.now() - started) / 1000, 0.001);
174
+ return {
175
+ concurrency,
176
+ requests: concurrency * requestsPerWorker,
177
+ succeeded,
178
+ failed,
179
+ overloaded,
180
+ throughputPerSecond: Number(((succeeded + failed) / elapsedSeconds).toFixed(2)),
181
+ p95Ms: Number(percentile95(latencies).toFixed(2))
182
+ };
183
+ }
184
+ async function calibrateHttpConcurrency(options, fetcher = fetch) {
185
+ const {
186
+ endpoint,
187
+ maxConcurrency,
188
+ requestsPerWorker,
189
+ maxP95Ms,
190
+ maxErrorRate,
191
+ headroomRatio,
192
+ requestTimeoutMs: timeoutMs
193
+ } = validateCapacityCalibrationOptions(options);
194
+ const stages = [];
195
+ let lastSafe = 1;
196
+ let stopReason = "maximum-tested";
197
+ for (let concurrency = 1;; concurrency = Math.min(maxConcurrency, concurrency * 2)) {
198
+ const measured = await stage(endpoint, concurrency, requestsPerWorker, timeoutMs, fetcher);
199
+ stages.push(measured);
200
+ const errorRate = measured.failed / measured.requests;
201
+ const previous = stages.at(-2);
202
+ const throughputRegressed = Boolean(previous && concurrency > 1 && measured.throughputPerSecond < previous.throughputPerSecond * 0.9);
203
+ if (measured.overloaded > 0 || errorRate > maxErrorRate)
204
+ stopReason = "errors";
205
+ else if (measured.p95Ms > maxP95Ms)
206
+ stopReason = "latency";
207
+ else if (throughputRegressed)
208
+ stopReason = "throughput-regression";
209
+ else
210
+ lastSafe = concurrency;
211
+ if (stopReason !== "maximum-tested" || concurrency === maxConcurrency)
212
+ break;
213
+ }
214
+ return {
215
+ endpoint: endpoint.toString(),
216
+ recommendedConcurrency: Math.max(1, Math.floor(lastSafe * headroomRatio)),
217
+ stopReason,
218
+ stages
219
+ };
220
+ }
221
+
107
222
  // src/definition.ts
108
223
  var PIPELINE_VERSION = 2;
109
224
  var DEPLOY_SCHEMA_URL = "https://www.forgezero.net/schemas/deploy-v2.json";
@@ -144,6 +259,53 @@ var RESERVED_STEP_ENV = new Set([
144
259
  "GIT_SSH",
145
260
  "GIT_SSH_COMMAND"
146
261
  ]);
262
+ function capacityCalibration(value, where) {
263
+ const calibration = record(value, where);
264
+ exactKeys(calibration, [
265
+ "endpoint",
266
+ "maxConcurrency",
267
+ "requestsPerWorker",
268
+ "maxP95Ms",
269
+ "maxErrorRate",
270
+ "headroomRatio",
271
+ "requestTimeoutMs"
272
+ ], where);
273
+ const endpoint = text(calibration.endpoint, `${where}.endpoint`);
274
+ try {
275
+ localCalibrationEndpoint(endpoint);
276
+ } catch (cause) {
277
+ throw new DefinitionError(cause instanceof Error ? cause.message : `${where}.endpoint is invalid.`);
278
+ }
279
+ const optionalNumber = (name) => {
280
+ const raw = calibration[name];
281
+ if (raw === undefined)
282
+ return;
283
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
284
+ throw new DefinitionError(`${where}.${name} must be a finite number.`);
285
+ }
286
+ return raw;
287
+ };
288
+ const parsed = {
289
+ endpoint,
290
+ ...Object.fromEntries([
291
+ "maxConcurrency",
292
+ "requestsPerWorker",
293
+ "maxP95Ms",
294
+ "maxErrorRate",
295
+ "headroomRatio",
296
+ "requestTimeoutMs"
297
+ ].flatMap((name) => {
298
+ const found = optionalNumber(name);
299
+ return found === undefined ? [] : [[name, found]];
300
+ }))
301
+ };
302
+ try {
303
+ validateCapacityCalibrationOptions(parsed);
304
+ } catch (cause) {
305
+ throw new DefinitionError(cause instanceof Error ? cause.message : `${where} bounds are invalid.`);
306
+ }
307
+ return parsed;
308
+ }
147
309
  function parseDeployDefinition(value, options = {}) {
148
310
  const root = record(value, "pipeline");
149
311
  exactKeys(root, ["$schema", "version", "name", "requireAttestation", "profiles", "steps"], "pipeline");
@@ -169,11 +331,16 @@ function parseDeployDefinition(value, options = {}) {
169
331
  if (!NAME.test(name2))
170
332
  throw new DefinitionError(`pipeline profile name is invalid: ${name2}.`);
171
333
  const profile = record(raw, `profiles.${name2}`);
172
- exactKeys(profile, ["software"], `profiles.${name2}`);
334
+ exactKeys(profile, ["software", "capacityCalibration"], `profiles.${name2}`);
173
335
  if (!Array.isArray(profile.software)) {
174
336
  throw new DefinitionError(`profiles.${name2}.software must be an array.`);
175
337
  }
176
- profiles[name2] = { software: validateSoftwareRequirements(profile.software, options) };
338
+ profiles[name2] = {
339
+ software: validateSoftwareRequirements(profile.software, options),
340
+ ...profile.capacityCalibration === undefined ? {} : {
341
+ capacityCalibration: capacityCalibration(profile.capacityCalibration, `profiles.${name2}.capacityCalibration`)
342
+ }
343
+ };
177
344
  }
178
345
  const phases = new Set(["build", "release", "migrate", "health"]);
179
346
  const steps = root.steps.map((raw, index) => {
@@ -239,6 +406,11 @@ function parseDeployDefinition(value, options = {}) {
239
406
  if (new Set(steps.map((step) => step.name)).size !== steps.length) {
240
407
  throw new DefinitionError("pipeline.steps must have unique names.");
241
408
  }
409
+ for (const [profile, selected] of Object.entries(profiles)) {
410
+ if (selected.capacityCalibration && !steps.some((step) => step.phase === "health" && step.scope === "target" && (!step.profiles || step.profiles.includes(profile)))) {
411
+ throw new DefinitionError(`profiles.${profile}.capacityCalibration requires a target-scoped health step.`);
412
+ }
413
+ }
242
414
  const name = text(root.name, "pipeline.name");
243
415
  if (name.length > 120)
244
416
  throw new DefinitionError("pipeline.name must be at most 120 characters.");
@@ -104,6 +104,121 @@ async function ensureSoftwareRequirements(requirementsInput, options) {
104
104
  return results;
105
105
  }
106
106
 
107
+ // src/capacity-calibration.ts
108
+ var percentile95 = (values) => {
109
+ if (values.length === 0)
110
+ return Number.POSITIVE_INFINITY;
111
+ const sorted = values.toSorted((left, right) => left - right);
112
+ return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * 0.95) - 1)];
113
+ };
114
+ function localCalibrationEndpoint(value) {
115
+ const endpoint = new URL(value);
116
+ if (endpoint.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(endpoint.hostname) || !endpoint.port || endpoint.username || endpoint.password || endpoint.hash) {
117
+ throw new Error("Capacity calibration requires an explicit loopback HTTP endpoint and port.");
118
+ }
119
+ return endpoint;
120
+ }
121
+ function validateCapacityCalibrationOptions(options) {
122
+ const endpoint = localCalibrationEndpoint(options.endpoint);
123
+ const maxConcurrency = options.maxConcurrency ?? 256;
124
+ const requestsPerWorker = options.requestsPerWorker ?? 8;
125
+ const maxP95Ms = options.maxP95Ms ?? 250;
126
+ const maxErrorRate = options.maxErrorRate ?? 0.01;
127
+ const headroomRatio = options.headroomRatio ?? 0.8;
128
+ const requestTimeoutMs = options.requestTimeoutMs ?? 5000;
129
+ if (!Number.isSafeInteger(maxConcurrency) || maxConcurrency < 1 || maxConcurrency > 4096 || !Number.isSafeInteger(requestsPerWorker) || requestsPerWorker < 2 || requestsPerWorker > 100 || !Number.isFinite(maxP95Ms) || maxP95Ms < 1 || !Number.isFinite(maxErrorRate) || maxErrorRate < 0 || maxErrorRate > 0.2 || !Number.isFinite(headroomRatio) || headroomRatio < 0.25 || headroomRatio > 0.95 || !Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 100 || requestTimeoutMs > 30000) {
130
+ throw new Error("Capacity calibration bounds are invalid.");
131
+ }
132
+ return {
133
+ endpoint,
134
+ maxConcurrency,
135
+ requestsPerWorker,
136
+ maxP95Ms,
137
+ maxErrorRate,
138
+ headroomRatio,
139
+ requestTimeoutMs
140
+ };
141
+ }
142
+ async function stage(endpoint, concurrency, requestsPerWorker, timeoutMs, fetcher) {
143
+ const latencies = [];
144
+ let succeeded = 0;
145
+ let failed = 0;
146
+ let overloaded = 0;
147
+ const started = performance.now();
148
+ await Promise.all(Array.from({ length: concurrency }, async () => {
149
+ for (let request = 0;request < requestsPerWorker; request += 1) {
150
+ const requestStarted = performance.now();
151
+ try {
152
+ const response = await fetcher(endpoint, {
153
+ method: "GET",
154
+ headers: { accept: "application/json", "user-agent": "forgezero-capacity-calibration/1" },
155
+ signal: AbortSignal.timeout(timeoutMs),
156
+ redirect: "error"
157
+ });
158
+ await response.body?.cancel();
159
+ if (response.ok)
160
+ succeeded += 1;
161
+ else {
162
+ failed += 1;
163
+ if (response.status === 503)
164
+ overloaded += 1;
165
+ }
166
+ } catch {
167
+ failed += 1;
168
+ } finally {
169
+ latencies.push(performance.now() - requestStarted);
170
+ }
171
+ }
172
+ }));
173
+ const elapsedSeconds = Math.max((performance.now() - started) / 1000, 0.001);
174
+ return {
175
+ concurrency,
176
+ requests: concurrency * requestsPerWorker,
177
+ succeeded,
178
+ failed,
179
+ overloaded,
180
+ throughputPerSecond: Number(((succeeded + failed) / elapsedSeconds).toFixed(2)),
181
+ p95Ms: Number(percentile95(latencies).toFixed(2))
182
+ };
183
+ }
184
+ async function calibrateHttpConcurrency(options, fetcher = fetch) {
185
+ const {
186
+ endpoint,
187
+ maxConcurrency,
188
+ requestsPerWorker,
189
+ maxP95Ms,
190
+ maxErrorRate,
191
+ headroomRatio,
192
+ requestTimeoutMs: timeoutMs
193
+ } = validateCapacityCalibrationOptions(options);
194
+ const stages = [];
195
+ let lastSafe = 1;
196
+ let stopReason = "maximum-tested";
197
+ for (let concurrency = 1;; concurrency = Math.min(maxConcurrency, concurrency * 2)) {
198
+ const measured = await stage(endpoint, concurrency, requestsPerWorker, timeoutMs, fetcher);
199
+ stages.push(measured);
200
+ const errorRate = measured.failed / measured.requests;
201
+ const previous = stages.at(-2);
202
+ const throughputRegressed = Boolean(previous && concurrency > 1 && measured.throughputPerSecond < previous.throughputPerSecond * 0.9);
203
+ if (measured.overloaded > 0 || errorRate > maxErrorRate)
204
+ stopReason = "errors";
205
+ else if (measured.p95Ms > maxP95Ms)
206
+ stopReason = "latency";
207
+ else if (throughputRegressed)
208
+ stopReason = "throughput-regression";
209
+ else
210
+ lastSafe = concurrency;
211
+ if (stopReason !== "maximum-tested" || concurrency === maxConcurrency)
212
+ break;
213
+ }
214
+ return {
215
+ endpoint: endpoint.toString(),
216
+ recommendedConcurrency: Math.max(1, Math.floor(lastSafe * headroomRatio)),
217
+ stopReason,
218
+ stages
219
+ };
220
+ }
221
+
107
222
  // src/definition.ts
108
223
  var PIPELINE_VERSION = 2;
109
224
  var DEPLOY_SCHEMA_URL = "https://www.forgezero.net/schemas/deploy-v2.json";
@@ -144,6 +259,53 @@ var RESERVED_STEP_ENV = new Set([
144
259
  "GIT_SSH",
145
260
  "GIT_SSH_COMMAND"
146
261
  ]);
262
+ function capacityCalibration(value, where) {
263
+ const calibration = record(value, where);
264
+ exactKeys(calibration, [
265
+ "endpoint",
266
+ "maxConcurrency",
267
+ "requestsPerWorker",
268
+ "maxP95Ms",
269
+ "maxErrorRate",
270
+ "headroomRatio",
271
+ "requestTimeoutMs"
272
+ ], where);
273
+ const endpoint = text(calibration.endpoint, `${where}.endpoint`);
274
+ try {
275
+ localCalibrationEndpoint(endpoint);
276
+ } catch (cause) {
277
+ throw new DefinitionError(cause instanceof Error ? cause.message : `${where}.endpoint is invalid.`);
278
+ }
279
+ const optionalNumber = (name) => {
280
+ const raw = calibration[name];
281
+ if (raw === undefined)
282
+ return;
283
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
284
+ throw new DefinitionError(`${where}.${name} must be a finite number.`);
285
+ }
286
+ return raw;
287
+ };
288
+ const parsed = {
289
+ endpoint,
290
+ ...Object.fromEntries([
291
+ "maxConcurrency",
292
+ "requestsPerWorker",
293
+ "maxP95Ms",
294
+ "maxErrorRate",
295
+ "headroomRatio",
296
+ "requestTimeoutMs"
297
+ ].flatMap((name) => {
298
+ const found = optionalNumber(name);
299
+ return found === undefined ? [] : [[name, found]];
300
+ }))
301
+ };
302
+ try {
303
+ validateCapacityCalibrationOptions(parsed);
304
+ } catch (cause) {
305
+ throw new DefinitionError(cause instanceof Error ? cause.message : `${where} bounds are invalid.`);
306
+ }
307
+ return parsed;
308
+ }
147
309
  function parseDeployDefinition(value, options = {}) {
148
310
  const root = record(value, "pipeline");
149
311
  exactKeys(root, ["$schema", "version", "name", "requireAttestation", "profiles", "steps"], "pipeline");
@@ -169,11 +331,16 @@ function parseDeployDefinition(value, options = {}) {
169
331
  if (!NAME.test(name2))
170
332
  throw new DefinitionError(`pipeline profile name is invalid: ${name2}.`);
171
333
  const profile = record(raw, `profiles.${name2}`);
172
- exactKeys(profile, ["software"], `profiles.${name2}`);
334
+ exactKeys(profile, ["software", "capacityCalibration"], `profiles.${name2}`);
173
335
  if (!Array.isArray(profile.software)) {
174
336
  throw new DefinitionError(`profiles.${name2}.software must be an array.`);
175
337
  }
176
- profiles[name2] = { software: validateSoftwareRequirements(profile.software, options) };
338
+ profiles[name2] = {
339
+ software: validateSoftwareRequirements(profile.software, options),
340
+ ...profile.capacityCalibration === undefined ? {} : {
341
+ capacityCalibration: capacityCalibration(profile.capacityCalibration, `profiles.${name2}.capacityCalibration`)
342
+ }
343
+ };
177
344
  }
178
345
  const phases = new Set(["build", "release", "migrate", "health"]);
179
346
  const steps = root.steps.map((raw, index) => {
@@ -239,6 +406,11 @@ function parseDeployDefinition(value, options = {}) {
239
406
  if (new Set(steps.map((step) => step.name)).size !== steps.length) {
240
407
  throw new DefinitionError("pipeline.steps must have unique names.");
241
408
  }
409
+ for (const [profile, selected] of Object.entries(profiles)) {
410
+ if (selected.capacityCalibration && !steps.some((step) => step.phase === "health" && step.scope === "target" && (!step.profiles || step.profiles.includes(profile)))) {
411
+ throw new DefinitionError(`profiles.${profile}.capacityCalibration requires a target-scoped health step.`);
412
+ }
413
+ }
242
414
  const name = text(root.name, "pipeline.name");
243
415
  if (name.length > 120)
244
416
  throw new DefinitionError("pipeline.name must be at most 120 characters.");
@@ -1,5 +1,6 @@
1
1
  import type { NodeKeyPair } from '@forgezero/runtime/identity';
2
2
  import type { DeploymentManager, DeploymentResult, GitSourceAuth } from './deployment';
3
+ import type { AgentOperationTelemetry } from './telemetry-runtime';
3
4
  export interface RemoteDeploymentClaim {
4
5
  runKey: string;
5
6
  pipelineKey: string;
@@ -36,6 +37,7 @@ export interface DeploymentPullOptions {
36
37
  setTimer?: (callback: () => void, ms: number) => unknown;
37
38
  clearTimer?: (handle: unknown) => void;
38
39
  onEvent?: (event: string, detail?: unknown) => void;
40
+ telemetry?: AgentOperationTelemetry;
39
41
  }
40
42
  export type PullResult = {
41
43
  status: 'idle';
@@ -1,8 +1,10 @@
1
1
  import { type DrainReport, type QueueTask } from '@forgezero/runtime/queue';
2
+ import { type GitHostResolver } from './git-egress';
2
3
  import type { SoftwareRequirement } from './software';
3
4
  import { type RunResult } from './pipeline';
4
5
  import type { SecretCache } from './cache';
5
6
  import type { AttestationSource } from './socket';
7
+ import { type CapacityCalibration, type CapacityCalibrationOptions } from './capacity-calibration';
6
8
  export interface CommandInput {
7
9
  command: string;
8
10
  cwd?: string;
@@ -31,6 +33,12 @@ export interface DeploymentResult {
31
33
  release: string;
32
34
  ok: boolean;
33
35
  phases: readonly RunResult[];
36
+ capacity?: CapacityCalibration & {
37
+ evidencePath: string;
38
+ recommendedCoordinate: {
39
+ FZ_CONCURRENCY_LIMIT: string;
40
+ };
41
+ };
34
42
  }
35
43
  /**
36
44
  * How the credential-bearing agent may read one server-owned Git source.
@@ -58,6 +66,8 @@ export interface DeploymentOptions {
58
66
  knownHostsPath?: string;
59
67
  /** Server-owned, operator-pinned host keys for a dynamically assigned source. */
60
68
  knownHostsContent?: string;
69
+ /** Test/embedding seam. Production uses the operating system resolver. */
70
+ resolveGitHost?: GitHostResolver;
61
71
  cache?: Pick<SecretCache, 'get'>;
62
72
  /** Non-secret values explicitly passed to every project phase. */
63
73
  environment?: Record<string, string>;
@@ -67,6 +77,10 @@ export interface DeploymentOptions {
67
77
  projectExec?: (input: CommandInput) => Promise<CommandResult>;
68
78
  /** Root-owned fixed strategy helper; repository data contains coordinates only. */
69
79
  ensureSoftware?: (requirements: readonly SoftwareRequirement[]) => Promise<unknown>;
80
+ /** Agent-owned directory; definitions cannot choose or overwrite this path. */
81
+ capacityEvidenceDirectory?: string;
82
+ /** Test/embedding seam. Production runs the bounded loopback GET calibrator. */
83
+ calibrateCapacity?: (options: CapacityCalibrationOptions) => Promise<CapacityCalibration>;
70
84
  exec?: (input: CommandInput) => Promise<CommandResult>;
71
85
  now?: () => number;
72
86
  readDefinition?: (path: string) => unknown;
@@ -0,0 +1,50 @@
1
+ export declare const AGENT_EGRESS_TABLE = "forgezero_agent_egress";
2
+ export declare const SYSTEMD_RESOLVED_STUB = "/run/systemd/resolve/stub-resolv.conf";
3
+ export declare const SYSTEMD_RESOLVED_ADDRESS = "127.0.0.53";
4
+ export declare const BLOCKED_IPV4: readonly ["0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "168.63.129.16/32", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24", "192.88.99.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4"];
5
+ export declare const BLOCKED_IPV6: readonly ["::/128", "::1/128", "::ffff:0:0/96", "64:ff9b::/96", "64:ff9b:1::/48", "100::/64", "fc00::/7", "fec0::/10", "fe80::/10", "ff00::/8", "2001::/32", "2001:2::/48", "2001:10::/28", "2001:20::/28", "2001:db8::/32", "2002::/16", "3fff::/20"];
6
+ export declare const normalizeEgressTcpPorts: (ports: readonly number[]) => readonly number[];
7
+ export interface LoopbackEgressGrant {
8
+ uid: number;
9
+ tcpPorts: readonly number[];
10
+ /** When present, reject every other public protocol/port for this UID. */
11
+ publicTcpPorts?: readonly number[];
12
+ }
13
+ /** Native cgroup filtering stays active even if an operator later reloads nftables. */
14
+ export declare function systemdAgentEgressDirectives(loopbackTcpPorts?: readonly number[]): string;
15
+ /** One atomic nftables transaction installed before systemd marks the policy ready. */
16
+ export declare function renderAgentEgressNft(uids: readonly number[], replace?: boolean, loopback?: LoopbackEgressGrant): string;
17
+ export interface EgressCommandResult {
18
+ exitCode: number;
19
+ stdout: string;
20
+ stderr: string;
21
+ }
22
+ export type EgressCommand = (argv: readonly string[], stdin?: string) => EgressCommandResult;
23
+ export declare function resolveServiceUid(user: string, command?: EgressCommand): number;
24
+ export declare function assertResolvedStub(realpath?: (path: string) => string): void;
25
+ export declare function verifyAgentEgressPolicy(uids: readonly number[], command?: EgressCommand, loopback?: LoopbackEgressGrant): boolean;
26
+ export declare function applyAgentEgressPolicy(options: {
27
+ users: readonly string[];
28
+ loopback?: {
29
+ user: string;
30
+ tcpPorts: readonly number[];
31
+ publicTcpPorts?: readonly number[];
32
+ };
33
+ command?: EgressCommand;
34
+ realpath?: (path: string) => string;
35
+ getuid?: () => number;
36
+ }): readonly number[];
37
+ export declare function superviseAgentEgressPolicy(options: {
38
+ users: readonly string[];
39
+ loopback?: {
40
+ user: string;
41
+ tcpPorts: readonly number[];
42
+ publicTcpPorts?: readonly number[];
43
+ };
44
+ command?: EgressCommand;
45
+ realpath?: (path: string) => string;
46
+ getuid?: () => number;
47
+ intervalMs?: number;
48
+ notifyReady?: () => void;
49
+ wait?: (milliseconds: number) => Promise<void>;
50
+ }): Promise<never>;