@aldus-runtime/gate-engine 0.1.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.
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Gate definitions (architecture contract §12, §13).
3
+ *
4
+ * A definition is **configuration**: what a gate binds to, what it depends on, how strongly it
5
+ * blocks, and who may decide it. A {@link GateDecision} is a **record**: what someone decided,
6
+ * when, and against which exact inputs. Keeping them apart is what lets a decision stay a
7
+ * faithful historical fact while the configuration around it evolves.
8
+ *
9
+ * Contract §13 names four gates — Content Freeze (§13.1), Performance Freeze (§13.2), Human Ear
10
+ * (§13.3), Final Release (§13.4). None of them is hardcoded here. §4.2 keeps show-specific
11
+ * process out of Core's reach and §4.3 gives adopters their own gates, so `gateId` and the
12
+ * subject keys are open strings and the contract's four gates are simply the definitions an
13
+ * adopter is most likely to write. The tests construct them to show the model expresses §13, not
14
+ * because the engine knows their names.
15
+ */
16
+ import { GateEngineErrorCodes, gateEngineError } from "./errors.js";
17
+ /**
18
+ * The four quality levels of contract §12.
19
+ *
20
+ * These describe *what kind of judgement* a gate represents, which is independent of how
21
+ * strongly it blocks. §12's own table pairs them freely: a hard gate blocks, an advisory signal
22
+ * does not, and a model-assisted review may do either depending on whether it has been
23
+ * calibrated (§12.1).
24
+ */
25
+ export const GATE_LEVELS = [
26
+ /** Blocks on an objectively testable failure (§12 level 1). */
27
+ "hard_gate",
28
+ /** Reports a possible issue without blocking (§12 level 2). */
29
+ "advisory_signal",
30
+ /** Evaluates meaning, stance, style, or claims under uncertainty (§12 level 3). */
31
+ "model_assisted",
32
+ /** A human owns the judgement, because it is subjective or asymmetric-risk (§12 level 4). */
33
+ "human_oracle",
34
+ ];
35
+ /**
36
+ * Whether a gate stops work or merely reports.
37
+ *
38
+ * Deliberately a two-state enumeration rather than a boolean. Contract §12.1 permits an
39
+ * evaluator to *become* blocking only after calibration, which makes this a promotion with
40
+ * evidence behind it — and a field named `blocking: boolean` invites someone to flip it in a
41
+ * config file without producing any.
42
+ */
43
+ export const GATE_ENFORCEMENTS = ["blocking", "advisory"];
44
+ /** Actor kinds a gate accepts when the definition does not say. */
45
+ function defaultPermittedActorKinds(level) {
46
+ // §12 level 4 is "human oracle — owns subjective judgment or asymmetric-risk decisions", and
47
+ // §13.3 keeps final performance approval human-owned. Defaulting these to any actor would let
48
+ // an agent satisfy the one gate the contract most insists a person owns.
49
+ return level === "human_oracle" ? ["human"] : ["human", "agent", "worker", "system"];
50
+ }
51
+ /**
52
+ * Resolve defaults and refuse an internally inconsistent definition.
53
+ *
54
+ * @throws {AldusError} `ALDUS_GATE_DEFINITION_INVALID`
55
+ */
56
+ export function validateGateDefinition(definition) {
57
+ const fail = (message, details = {}) => {
58
+ throw gateEngineError(GateEngineErrorCodes.GATE_DEFINITION_INVALID, message, {
59
+ category: "validation",
60
+ details: { gateId: definition.gateId, ...details },
61
+ });
62
+ };
63
+ if (definition.gateId.trim().length === 0)
64
+ fail("A gate definition needs a non-empty gateId.");
65
+ if (definition.binds.length === 0) {
66
+ // A gate binding nothing cannot be invalidated by anything, which makes its approval
67
+ // permanent — the precise failure §13.1 and §13.2 exist to prevent.
68
+ fail("A gate must bind at least one subject. A gate that binds nothing can never be " +
69
+ "invalidated by a change, so its approval would outlive the content it approved " +
70
+ "(contract §13.1, §13.2).");
71
+ }
72
+ const duplicates = definition.binds.filter((key, index) => definition.binds.indexOf(key) !== index);
73
+ if (duplicates.length > 0) {
74
+ fail(`A gate cannot bind the same subject twice: ${[...new Set(duplicates)].join(", ")}.`, {
75
+ duplicates: [...new Set(duplicates)],
76
+ });
77
+ }
78
+ if (definition.dependsOn?.includes(definition.gateId) === true) {
79
+ fail("A gate cannot depend on itself.");
80
+ }
81
+ if (definition.level === "model_assisted" && definition.enforcement === "blocking") {
82
+ if (definition.promotionEvidence === undefined) {
83
+ fail("A model-assisted gate may only block once it has been calibrated against human-labeled " +
84
+ "examples (contract §12.1). Set `promotionEvidence`, or leave the gate advisory. " +
85
+ "Contract §12 forbids presenting a machine pass as semantic correctness.", { level: definition.level, enforcement: definition.enforcement });
86
+ }
87
+ }
88
+ const permittedActorKinds = definition.permittedActorKinds ?? defaultPermittedActorKinds(definition.level);
89
+ if (permittedActorKinds.length === 0) {
90
+ fail("A gate that permits no actor kind can never be decided.");
91
+ }
92
+ if (definition.level === "human_oracle" && !permittedActorKinds.includes("human")) {
93
+ fail("A human-oracle gate must permit a human actor (contract §12 level 4, §13.3).", {
94
+ permittedActorKinds: [...permittedActorKinds],
95
+ });
96
+ }
97
+ return {
98
+ ...definition,
99
+ dependsOn: definition.dependsOn ?? [],
100
+ permittedActorKinds,
101
+ expiresOnChange: definition.expiresOnChange ?? true,
102
+ grants: definition.grants ?? [],
103
+ };
104
+ }
105
+ /**
106
+ * A validated set of gates and the dependency graph between them.
107
+ *
108
+ * Built once and reused: cycle detection and unknown-dependency checks run at construction, so a
109
+ * misconfiguration surfaces when the registry is assembled rather than when an operator is
110
+ * waiting on an approval.
111
+ */
112
+ export class GateRegistry {
113
+ #gates;
114
+ constructor(gates) {
115
+ this.#gates = gates;
116
+ }
117
+ /**
118
+ * Validate a set of definitions and the graph they form.
119
+ *
120
+ * @throws {AldusError} `ALDUS_GATE_DEFINITION_INVALID` for an invalid or duplicate definition,
121
+ * or an edge naming a gate that does not exist.
122
+ * @throws {AldusError} `ALDUS_GATE_DEPENDENCY_CYCLE` if the dependency edges form a cycle.
123
+ */
124
+ static from(definitions) {
125
+ const gates = new Map();
126
+ for (const definition of definitions) {
127
+ const resolved = validateGateDefinition(definition);
128
+ if (gates.has(resolved.gateId)) {
129
+ throw gateEngineError(GateEngineErrorCodes.GATE_DEFINITION_INVALID, `Gate "${resolved.gateId}" is defined more than once.`, { category: "validation", details: { gateId: resolved.gateId } });
130
+ }
131
+ gates.set(resolved.gateId, resolved);
132
+ }
133
+ for (const gate of gates.values()) {
134
+ for (const dependency of gate.dependsOn) {
135
+ if (!gates.has(dependency)) {
136
+ throw gateEngineError(GateEngineErrorCodes.GATE_DEFINITION_INVALID, `Gate "${gate.gateId}" depends on "${dependency}", which is not defined. An edge to ` +
137
+ "a missing gate would silently drop out of the invalidation cascade (§13.1).", { category: "validation", details: { gateId: gate.gateId, dependency } });
138
+ }
139
+ }
140
+ }
141
+ const cycle = findCycle(gates);
142
+ if (cycle !== undefined) {
143
+ throw gateEngineError(GateEngineErrorCodes.GATE_DEPENDENCY_CYCLE, `Gate dependencies form a cycle: ${cycle.join(" → ")}. Contract §13.1's cascade is only ` +
144
+ 'meaningful over an acyclic graph; with a cycle, "what does this invalidate" has no answer.', { category: "validation", details: { cycle } });
145
+ }
146
+ return new GateRegistry(gates);
147
+ }
148
+ /** Every gate, in definition order. */
149
+ list() {
150
+ return [...this.#gates.values()];
151
+ }
152
+ /** True if the gate is registered. */
153
+ has(gateId) {
154
+ return this.#gates.has(gateId);
155
+ }
156
+ /** A gate definition, or `undefined` if it is not registered. */
157
+ get(gateId) {
158
+ return this.#gates.get(gateId);
159
+ }
160
+ /**
161
+ * A gate definition.
162
+ *
163
+ * @throws {AldusError} `ALDUS_GATE_NOT_FOUND`
164
+ */
165
+ require(gateId) {
166
+ const gate = this.#gates.get(gateId);
167
+ if (gate === undefined) {
168
+ throw gateEngineError(GateEngineErrorCodes.GATE_NOT_FOUND, `Gate "${gateId}" is not registered.`, { category: "not_found", details: { gateId } });
169
+ }
170
+ return gate;
171
+ }
172
+ /** Gates that directly depend on `gateId`. */
173
+ dependentsOf(gateId) {
174
+ return this.list().filter((gate) => gate.dependsOn.includes(gateId));
175
+ }
176
+ /**
177
+ * Gates that transitively depend on `gateId`, nearest first, excluding `gateId` itself.
178
+ *
179
+ * This is the reach of contract §13.1's cascade: invalidating a Content Freeze invalidates
180
+ * every approval downstream of it.
181
+ */
182
+ downstreamOf(gateId) {
183
+ const seen = new Set();
184
+ const ordered = [];
185
+ let frontier = [gateId];
186
+ while (frontier.length > 0) {
187
+ const next = [];
188
+ for (const current of frontier) {
189
+ for (const dependent of this.dependentsOf(current)) {
190
+ if (seen.has(dependent.gateId))
191
+ continue;
192
+ seen.add(dependent.gateId);
193
+ ordered.push(dependent.gateId);
194
+ next.push(dependent.gateId);
195
+ }
196
+ }
197
+ frontier = next;
198
+ }
199
+ return ordered;
200
+ }
201
+ }
202
+ /** Depth-first cycle search, returning the offending path if there is one. */
203
+ function findCycle(gates) {
204
+ const visiting = new Set();
205
+ const done = new Set();
206
+ const path = [];
207
+ const walk = (gateId) => {
208
+ if (done.has(gateId))
209
+ return undefined;
210
+ if (visiting.has(gateId))
211
+ return [...path.slice(path.indexOf(gateId)), gateId];
212
+ visiting.add(gateId);
213
+ path.push(gateId);
214
+ for (const dependency of gates.get(gateId)?.dependsOn ?? []) {
215
+ const found = walk(dependency);
216
+ if (found !== undefined)
217
+ return found;
218
+ }
219
+ path.pop();
220
+ visiting.delete(gateId);
221
+ done.add(gateId);
222
+ return undefined;
223
+ };
224
+ for (const gateId of gates.keys()) {
225
+ const found = walk(gateId);
226
+ if (found !== undefined)
227
+ return found;
228
+ }
229
+ return undefined;
230
+ }
231
+ //# sourceMappingURL=definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.js","sourceRoot":"","sources":["../src/definition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Gate evaluation, decision recording, and authorization (architecture contract §12, §13, §19.3).
3
+ *
4
+ * The central design choice: **invalidation is derived, never stored.**
5
+ *
6
+ * Contract §13.1 requires a content-changing edit to invalidate the Content Freeze "and
7
+ * downstream approvals". The obvious implementation writes invalidation records and walks the
8
+ * graph marking approvals dead. That implementation has a failure mode the contract cannot
9
+ * tolerate — if the cascade is ever interrupted, or a gate is added after the fact, some approval
10
+ * stays marked valid while the thing it approved has moved underneath it, and §13.2 forbids
11
+ * exactly that.
12
+ *
13
+ * So nothing is marked. A gate's state is computed on every evaluation from three inputs: its
14
+ * latest decision, the current digests of what it binds, and the state of the gates it depends
15
+ * on. A stale approval cannot survive because there is no stored "valid" flag for it to survive
16
+ * in. Adding a dependency edge invalidates downstream approvals immediately, with no migration.
17
+ */
18
+ import type { ActorRef, CostRecord, GateDecision } from "@aldus-runtime/core";
19
+ import { type GateSubject, type SubjectDrift } from "./binding.js";
20
+ import type { GateEnforcement, GateLevel, GateRegistry } from "./definition.js";
21
+ import type { CostReader, GateDecisionStore, GateEventSink } from "./ports.js";
22
+ import { type SpendCheck, type SpendGrant, type SpendRequest } from "./spend.js";
23
+ /**
24
+ * What a gate currently is.
25
+ *
26
+ * `stale` is deliberately distinct from `pending`: a gate that was approved and then drifted is
27
+ * not the same operator situation as one nobody has looked at, and §13.1 wants the difference
28
+ * visible. `blocked_upstream` is likewise distinct from both — the gate itself may be perfectly
29
+ * approved while something it depends on is not.
30
+ */
31
+ export declare const GATE_STATES: readonly [
32
+ /** No decision has been recorded. */
33
+ "pending",
34
+ /** Approved, and still bound to the current inputs. */
35
+ "satisfied",
36
+ /** Approved, but a bound value has changed since (§13.1, §13.2). */
37
+ "stale",
38
+ /** The operator rejected it. */
39
+ "rejected",
40
+ /** The operator asked for changes. */
41
+ "changes_requested",
42
+ /** The operator deliberately bypassed the check (§13, distinct from approval). */
43
+ "waived",
44
+ /** A blocking gate this one depends on is not satisfied (§13.1 cascade). */
45
+ "blocked_upstream"];
46
+ /** @see GATE_STATES */
47
+ export type GateState = (typeof GATE_STATES)[number];
48
+ /** The evaluated state of one gate. */
49
+ export interface GateStatus {
50
+ gateId: string;
51
+ state: GateState;
52
+ /** What kind of judgement this gate represents (§12). */
53
+ level: GateLevel;
54
+ /** Whether it stops work or merely reports (§12). */
55
+ enforcement: GateEnforcement;
56
+ /** The decision this state was computed from, if any. */
57
+ decision?: GateDecision;
58
+ /** Which bound values moved, when `state` is `stale`. */
59
+ drift?: SubjectDrift;
60
+ /** Gates that blocked this one, when `state` is `blocked_upstream`. */
61
+ blockedBy?: string[];
62
+ /** Operator-facing explanation of why work may not proceed past this gate. */
63
+ explanation?: string;
64
+ /**
65
+ * Whether this state stops work.
66
+ *
67
+ * An advisory gate is never blocking whatever its state — §12 level 2 "reports a possible issue
68
+ * without blocking" — which is why enforcement and state are separate fields rather than one
69
+ * conflated verdict.
70
+ */
71
+ blocking: boolean;
72
+ }
73
+ /** Current digests of what each gate binds, keyed by gate. */
74
+ export type SubjectsByGate = Readonly<Record<string, readonly GateSubject[]>>;
75
+ /** What {@link GateEngine.decide} needs. */
76
+ export interface DecideInput {
77
+ runId: string;
78
+ gateId: string;
79
+ decision: GateDecision["decision"];
80
+ /** Current digests of everything the gate binds. */
81
+ subjects: readonly GateSubject[];
82
+ /** Who decided (§19.2). */
83
+ decidedBy: ActorRef;
84
+ /** ISO-8601 timestamp with offset. */
85
+ decidedAt: string;
86
+ comment?: string;
87
+ /** Canonical Episode identity, for the emitted event (§6.4). */
88
+ episodeId: string;
89
+ /** Overrides the gate's default. Defaults to the definition's `expiresOnChange`. */
90
+ expiresOnChange?: boolean;
91
+ /** Supplied for deterministic tests; defaults to a fresh ULID-based id. */
92
+ decisionId?: string;
93
+ /** Supplied for deterministic tests; defaults to a fresh ULID-based id. */
94
+ eventId?: string;
95
+ }
96
+ /** Why an operation was refused. */
97
+ export interface AuthorizationRefusal {
98
+ authorized: false;
99
+ /** Gates that could have authorized the operation, and why none did. */
100
+ statuses: GateStatus[];
101
+ explanation: string;
102
+ }
103
+ /** An operation the engine permits. */
104
+ export interface AuthorizationGrant {
105
+ authorized: true;
106
+ /** The gate whose approval authorized it. */
107
+ gateId: string;
108
+ decision: GateDecision;
109
+ }
110
+ /** Outcome of an authorization check. */
111
+ export type AuthorizationResult = AuthorizationGrant | AuthorizationRefusal;
112
+ /** Outcome of a spend authorization: the gate check and the budget check together. */
113
+ export type SpendAuthorization = {
114
+ authorized: true;
115
+ gateId: string;
116
+ decision: GateDecision;
117
+ check: Extract<SpendCheck, {
118
+ allowed: true;
119
+ }>;
120
+ } | {
121
+ authorized: false;
122
+ explanation: string;
123
+ statuses?: GateStatus[];
124
+ check?: SpendCheck;
125
+ };
126
+ /** Wiring for a {@link GateEngine}. */
127
+ export interface GateEngineOptions {
128
+ registry: GateRegistry;
129
+ decisions: GateDecisionStore;
130
+ events: GateEventSink;
131
+ costs?: CostReader;
132
+ }
133
+ /**
134
+ * Evaluates gates, records decisions, and authorizes operations and spend.
135
+ */
136
+ export declare class GateEngine {
137
+ #private;
138
+ constructor(options: GateEngineOptions);
139
+ /** The gate definitions this engine evaluates. */
140
+ get registry(): GateRegistry;
141
+ /**
142
+ * Record a human decision (contract §3.6).
143
+ *
144
+ * §3.6: "Human review MUST create a durable `GateDecision`. A chat message saying 'looks good'
145
+ * is not enough unless it is translated into a recorded decision tied to exact inputs." This is
146
+ * that translation, and it refuses anything that would produce a decision tied to less than the
147
+ * gate binds.
148
+ *
149
+ * @throws {AldusError} `ALDUS_GATE_NOT_FOUND` if the gate is not registered.
150
+ * @throws {AldusError} `ALDUS_GATE_SUBJECTS_INCOMPLETE` if the subjects do not cover the gate.
151
+ * @throws {AldusError} `ALDUS_GATE_ACTOR_NOT_PERMITTED` if the actor may not decide this gate.
152
+ */
153
+ decide(input: DecideInput): Promise<GateDecision>;
154
+ /**
155
+ * Evaluate every registered gate for a Run.
156
+ *
157
+ * `subjects` supplies the current digests of what each gate binds. A gate absent from it is
158
+ * evaluated as having no current inputs, which reads as `pending` — never as satisfied.
159
+ */
160
+ evaluate(runId: string, subjects: SubjectsByGate): Promise<Map<string, GateStatus>>;
161
+ /**
162
+ * Evaluate against a decision list already in hand.
163
+ *
164
+ * Separated from {@link GateEngine.evaluate} so the whole cascade is a pure function of its
165
+ * inputs — which is what makes it testable without a store and impossible to get into a
166
+ * partially-updated state.
167
+ */
168
+ evaluateWith(decisions: readonly GateDecision[], subjects: SubjectsByGate): Map<string, GateStatus>;
169
+ /**
170
+ * Whether an operation is authorized (contract §13.4).
171
+ *
172
+ * An approval authorizes exactly the operations its gate names in `grants` and nothing else.
173
+ * That is what keeps §13.4's "Uploading and making public SHOULD be separate operations"
174
+ * enforceable: they are two gates granting two operations, and approving one leaves the other
175
+ * refused.
176
+ *
177
+ * Returns a refusal rather than throwing, because a caller needs to display why an operation is
178
+ * unavailable.
179
+ */
180
+ authorize(runId: string, operation: string, subjects: SubjectsByGate): Promise<AuthorizationResult>;
181
+ /**
182
+ * Whether a paid request may proceed (contract §13.2, §19.3).
183
+ *
184
+ * Three things must hold, and all three are checked here because any one of them alone is
185
+ * insufficient:
186
+ *
187
+ * 1. the gate is satisfied — §13.2 forbids paid synthesis before the operator approves;
188
+ * 2. the grant's limits are among what that decision bound — otherwise the ceiling could be
189
+ * raised after approval without voiding it;
190
+ * 3. the spend fits the remaining budget — §19.3 stop-on-budget.
191
+ */
192
+ authorizeSpend(runId: string, grant: SpendGrant, request: SpendRequest, subjects: SubjectsByGate, costs?: readonly CostRecord[]): Promise<SpendAuthorization>;
193
+ }
194
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}