-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadmission-gate.ts
More file actions
113 lines (105 loc) · 4.39 KB
/
Copy pathadmission-gate.ts
File metadata and controls
113 lines (105 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* Admission-gate adapters (master plan §B5.1 Phase G hot-path enforcement, ADR-021).
*
* Bridges the governance Action Gate (+ economics cost reservation) into the
* `IAdmissionGate` port the `AgentActor` consults before executing a task:
* - `buildAdmissionGate(gate, opts)` — wrap any `ActionGate` as an `IAdmissionGate`
* (allow/degrade ⇒ proceed; escalate/block/terminate ⇒ block → DLQ).
* - `buildWorkerAdmissionGate(governance, deps)` — assemble the default worker
* gate from config: policy-as-code (always) + cost reservation (when economics
* is enabled AND a limiter is injected). Returns `undefined` when governance is
* disabled, so the actor stays in its default (no-gate) behavior.
*
* The worker gate intentionally does NOT include firewall/breaker validators (the
* actor already runs those directly) nor the agent registry (an empty registry
* would block every agent — it is a consumer-managed validator). See ADR-021.
*/
import { readFileSync } from "fs";
import type {
IAdmissionGate,
AdmissionVerdict,
} from "../domain/security/admission-gate";
import type { EvaluationPayload } from "../domain/security/semantic-firewall";
import {
ActionGate,
costValidator,
} from "../governance/action-gate";
import { PolicyEngine, loadPolicySet } from "../governance/policy-engine";
import { AuditLog } from "../governance/audit-log";
import type {
GateContext,
GateOperation,
GateValidator,
GovernanceConfig,
PolicySet,
} from "../governance/types";
import { CostReservation } from "../economics/cost-reservation";
import type { CostLimiterPort, EconomicsConfig } from "../economics/types";
/** Gate actions that permit execution; everything else blocks (→ DLQ). */
const PASSING_ACTIONS: ReadonlySet<string> = new Set(["allow", "degrade"]);
export interface AdmissionGateOptions {
/** Gate operation for the context (default `"tool-call"`). */
operation?: GateOperation;
/** Derive the tenant id from the payload (for per-tenant budgets). */
tenantIdOf?: (payload: EvaluationPayload) => string | undefined;
/** Derive an estimated cost (cost units) for cost-reservation validators. */
estimatedCostUnitsOf?: (payload: EvaluationPayload) => number | undefined;
}
/** Wrap an `ActionGate` as the actor's `IAdmissionGate` guard. */
export function buildAdmissionGate(
gate: Pick<ActionGate, "evaluate">,
opts: AdmissionGateOptions = {},
): IAdmissionGate {
return {
async evaluate(payload: EvaluationPayload): Promise<AdmissionVerdict> {
const tenantId = opts.tenantIdOf?.(payload);
const cost = opts.estimatedCostUnitsOf?.(payload);
const ctx: GateContext = {
operation: opts.operation ?? "tool-call",
agentId: payload.agentId,
payload: payload.data,
...(tenantId !== undefined ? { tenantId } : {}),
...(cost !== undefined ? { estimatedCostUnits: cost } : {}),
};
const decision = await gate.evaluate(ctx);
return PASSING_ACTIONS.has(decision.action)
? { allowed: true }
: { allowed: false, reason: `gate:${decision.action}` };
},
};
}
export interface WorkerAdmissionGateDeps {
/** Economics config — when `enabled` AND `costLimiter` is given, adds cost reservation. */
economics?: EconomicsConfig;
/** Limiter backing the cost-reservation validator (e.g. a Redis `RateCostLimiter`). */
costLimiter?: CostLimiterPort;
/** Forwarded to `buildAdmissionGate` (operation / scope / cost derivation). */
options?: AdmissionGateOptions;
}
/**
* Assemble the default worker admission gate from config. Returns `undefined`
* (actor runs un-gated) when governance is disabled.
*/
export function buildWorkerAdmissionGate(
governance: GovernanceConfig,
deps: WorkerAdmissionGateDeps = {},
): IAdmissionGate | undefined {
if (!governance.enabled) return undefined;
const policies: PolicySet = governance.policiesPath
? loadPolicySet(readFileSync(governance.policiesPath, "utf8"))
: { default: "allow", rules: [] };
const validators: GateValidator[] = [new PolicyEngine(policies)];
if (deps.economics?.enabled && deps.costLimiter) {
const reservation = new CostReservation({
config: deps.economics,
limiter: deps.costLimiter,
});
validators.push(costValidator(reservation));
}
const gate = new ActionGate({
config: governance,
validators,
audit: new AuditLog(),
});
return buildAdmissionGate(gate, deps.options ?? {});
}