@govplane/runtime-sdk 0.2.4
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.
- package/README.md +365 -0
- package/dist/index.cjs +1080 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +413 -0
- package/dist/index.d.ts +413 -0
- package/dist/index.js +1054 -0
- package/dist/index.js.map +1 -0
- package/docs/operations/Govplane_Incident_Playbook.md +318 -0
- package/docs/operations/Govplane_Runtime_Incident_Controls.md +101 -0
- package/docs/security/Govplane_Threat_Model.md +137 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
type Target = {
|
|
2
|
+
service: string;
|
|
3
|
+
resource: string;
|
|
4
|
+
action: string;
|
|
5
|
+
};
|
|
6
|
+
type Effect = {
|
|
7
|
+
type: "allow";
|
|
8
|
+
} | {
|
|
9
|
+
type: "deny";
|
|
10
|
+
} | {
|
|
11
|
+
type: "kill_switch";
|
|
12
|
+
killSwitch: {
|
|
13
|
+
service: string;
|
|
14
|
+
reason?: string;
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
type: "throttle";
|
|
18
|
+
throttle: {
|
|
19
|
+
limit: number;
|
|
20
|
+
windowSeconds: number;
|
|
21
|
+
key: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type WhenAstV1 = {
|
|
25
|
+
op: "and" | "or";
|
|
26
|
+
args: WhenAstV1[];
|
|
27
|
+
} | {
|
|
28
|
+
op: "not";
|
|
29
|
+
arg: WhenAstV1;
|
|
30
|
+
} | {
|
|
31
|
+
op: "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
|
|
32
|
+
path: string;
|
|
33
|
+
value: any;
|
|
34
|
+
} | {
|
|
35
|
+
op: "in";
|
|
36
|
+
path: string;
|
|
37
|
+
values: any[];
|
|
38
|
+
} | {
|
|
39
|
+
op: "exists";
|
|
40
|
+
path: string;
|
|
41
|
+
};
|
|
42
|
+
type RuntimeRule = {
|
|
43
|
+
id: string;
|
|
44
|
+
status: "active" | "disabled";
|
|
45
|
+
priority: number;
|
|
46
|
+
target: Target;
|
|
47
|
+
when?: WhenAstV1;
|
|
48
|
+
effect: Effect;
|
|
49
|
+
description?: string;
|
|
50
|
+
};
|
|
51
|
+
type RuntimePolicy = {
|
|
52
|
+
policyKey: string;
|
|
53
|
+
activeVersion: number;
|
|
54
|
+
defaults: {
|
|
55
|
+
effect: "allow" | "deny";
|
|
56
|
+
};
|
|
57
|
+
rules: RuntimeRule[];
|
|
58
|
+
};
|
|
59
|
+
type RuntimeBundleV1 = {
|
|
60
|
+
schemaVersion: 1;
|
|
61
|
+
orgId: string;
|
|
62
|
+
projectId: string;
|
|
63
|
+
env: string;
|
|
64
|
+
generatedAt: string;
|
|
65
|
+
policies: RuntimePolicy[];
|
|
66
|
+
};
|
|
67
|
+
type Decision = {
|
|
68
|
+
decision: "allow";
|
|
69
|
+
reason: "default" | "rule";
|
|
70
|
+
policyKey?: string;
|
|
71
|
+
ruleId?: string;
|
|
72
|
+
} | {
|
|
73
|
+
decision: "deny";
|
|
74
|
+
reason: "default" | "rule";
|
|
75
|
+
policyKey?: string;
|
|
76
|
+
ruleId?: string;
|
|
77
|
+
} | {
|
|
78
|
+
decision: "kill_switch";
|
|
79
|
+
reason: "rule";
|
|
80
|
+
policyKey: string;
|
|
81
|
+
ruleId: string;
|
|
82
|
+
killSwitch: {
|
|
83
|
+
service: string;
|
|
84
|
+
reason?: string;
|
|
85
|
+
};
|
|
86
|
+
} | {
|
|
87
|
+
decision: "throttle";
|
|
88
|
+
reason: "rule";
|
|
89
|
+
policyKey: string;
|
|
90
|
+
ruleId: string;
|
|
91
|
+
throttle: {
|
|
92
|
+
limit: number;
|
|
93
|
+
windowSeconds: number;
|
|
94
|
+
key: string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
type TraceDiscardReason = "disabled" | "target_mismatch" | "when_false" | "invalid_effect";
|
|
98
|
+
type TraceRule = {
|
|
99
|
+
policyKey: string;
|
|
100
|
+
ruleId: string;
|
|
101
|
+
priority: number;
|
|
102
|
+
effectType?: string;
|
|
103
|
+
matched: boolean;
|
|
104
|
+
discardedReason?: TraceDiscardReason;
|
|
105
|
+
};
|
|
106
|
+
type DecisionTrace = {
|
|
107
|
+
evaluatedAt: string;
|
|
108
|
+
target: Target;
|
|
109
|
+
summary: {
|
|
110
|
+
policiesSeen: number;
|
|
111
|
+
rulesSeen: number;
|
|
112
|
+
matched: number;
|
|
113
|
+
considered: {
|
|
114
|
+
kill_switch: number;
|
|
115
|
+
deny: number;
|
|
116
|
+
throttle: number;
|
|
117
|
+
allow: number;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
winner?: {
|
|
121
|
+
policyKey: string;
|
|
122
|
+
ruleId: string;
|
|
123
|
+
effectType: string;
|
|
124
|
+
priority: number;
|
|
125
|
+
};
|
|
126
|
+
rules: TraceRule[];
|
|
127
|
+
};
|
|
128
|
+
type DecisionWithTrace = Decision & {
|
|
129
|
+
trace: DecisionTrace;
|
|
130
|
+
};
|
|
131
|
+
type TraceLevel = "off" | "errors" | "sampled" | "full";
|
|
132
|
+
type TraceMode = "compact" | "full";
|
|
133
|
+
type TraceOptions = {
|
|
134
|
+
level?: TraceLevel;
|
|
135
|
+
mode?: TraceMode;
|
|
136
|
+
sampling?: number;
|
|
137
|
+
force?: boolean;
|
|
138
|
+
budget?: {
|
|
139
|
+
maxTraces: number;
|
|
140
|
+
windowMs: number;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
type PolicyEngineEvaluateInput = {
|
|
144
|
+
target: Target;
|
|
145
|
+
context?: Record<string, unknown>;
|
|
146
|
+
};
|
|
147
|
+
type PolicyEngine = {
|
|
148
|
+
evaluate(input: PolicyEngineEvaluateInput): Decision;
|
|
149
|
+
evaluateWithTrace(input: PolicyEngineEvaluateInput, options?: TraceOptions): DecisionWithOptionalTrace;
|
|
150
|
+
flushTraces(): Promise<void>;
|
|
151
|
+
};
|
|
152
|
+
type DecisionTraceCompact = {
|
|
153
|
+
traceId: string;
|
|
154
|
+
sampled: "forced" | "random";
|
|
155
|
+
evaluatedAt: string;
|
|
156
|
+
target: Target;
|
|
157
|
+
summary: {
|
|
158
|
+
policiesSeen: number;
|
|
159
|
+
rulesSeen: number;
|
|
160
|
+
matched: number;
|
|
161
|
+
considered: {
|
|
162
|
+
kill_switch: number;
|
|
163
|
+
deny: number;
|
|
164
|
+
throttle: number;
|
|
165
|
+
allow: number;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
winner?: {
|
|
169
|
+
policyKey: string;
|
|
170
|
+
ruleId: string;
|
|
171
|
+
effectType: string;
|
|
172
|
+
priority: number;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
type DecisionTraceFull = DecisionTraceCompact & {
|
|
176
|
+
rules: TraceRule[];
|
|
177
|
+
};
|
|
178
|
+
type DecisionWithOptionalTrace = Decision & {
|
|
179
|
+
trace?: DecisionTraceCompact | DecisionTraceFull;
|
|
180
|
+
};
|
|
181
|
+
type StructuredTraceEvent = {
|
|
182
|
+
v: 1;
|
|
183
|
+
ts: string;
|
|
184
|
+
traceId: string;
|
|
185
|
+
sampled: "forced" | "random" | "errors";
|
|
186
|
+
level: TraceLevel;
|
|
187
|
+
target: Target;
|
|
188
|
+
decision: Decision["decision"];
|
|
189
|
+
reason: Decision["reason"];
|
|
190
|
+
winner?: {
|
|
191
|
+
policyKey: string;
|
|
192
|
+
ruleId: string;
|
|
193
|
+
effectType: string;
|
|
194
|
+
priority: number;
|
|
195
|
+
};
|
|
196
|
+
summary: {
|
|
197
|
+
policiesSeen: number;
|
|
198
|
+
rulesSeen: number;
|
|
199
|
+
matched: number;
|
|
200
|
+
considered: {
|
|
201
|
+
kill_switch: number;
|
|
202
|
+
deny: number;
|
|
203
|
+
throttle: number;
|
|
204
|
+
allow: number;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
rules?: Array<{
|
|
208
|
+
policyKey: string;
|
|
209
|
+
ruleId: string;
|
|
210
|
+
priority: number;
|
|
211
|
+
effectType?: string;
|
|
212
|
+
matched: boolean;
|
|
213
|
+
discardedReason?: string;
|
|
214
|
+
}>;
|
|
215
|
+
};
|
|
216
|
+
type TraceSink = (evt: StructuredTraceEvent) => void;
|
|
217
|
+
type TraceSinkAsync = (evt: StructuredTraceEvent) => Promise<void>;
|
|
218
|
+
type TraceQueueDropPolicy = "drop_new" | "drop_old";
|
|
219
|
+
type DecisionTraceHook = (evt: StructuredTraceEvent) => void;
|
|
220
|
+
type RuntimeClientOptions = {
|
|
221
|
+
trace?: {
|
|
222
|
+
level?: TraceLevel;
|
|
223
|
+
sampling?: number;
|
|
224
|
+
budget?: {
|
|
225
|
+
maxTraces: number;
|
|
226
|
+
windowMs: number;
|
|
227
|
+
};
|
|
228
|
+
onDecisionTrace?: DecisionTraceHook;
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
type ContextPolicy = {
|
|
233
|
+
allowedKeys: string[];
|
|
234
|
+
maxStringLen: number;
|
|
235
|
+
maxArrayLen: number;
|
|
236
|
+
blockLikelyPiiKeys: boolean;
|
|
237
|
+
};
|
|
238
|
+
declare const DEFAULT_CONTEXT_POLICY: ContextPolicy;
|
|
239
|
+
declare function validateContext(ctx: Record<string, unknown>, policy: ContextPolicy): void;
|
|
240
|
+
|
|
241
|
+
type RuntimeClientConfig = {
|
|
242
|
+
baseUrl: string;
|
|
243
|
+
runtimeKey: string;
|
|
244
|
+
orgId: string;
|
|
245
|
+
projectId: string;
|
|
246
|
+
env: string;
|
|
247
|
+
pollMs?: number;
|
|
248
|
+
burstPollMs?: number;
|
|
249
|
+
burstDurationMs?: number;
|
|
250
|
+
timeoutMs?: number;
|
|
251
|
+
userAgent?: string;
|
|
252
|
+
backoffBaseMs?: number;
|
|
253
|
+
backoffMaxMs?: number;
|
|
254
|
+
backoffJitter?: number;
|
|
255
|
+
degradeAfterFailures?: number;
|
|
256
|
+
/**
|
|
257
|
+
* Engine config
|
|
258
|
+
*/
|
|
259
|
+
engine?: {
|
|
260
|
+
validateContext?: boolean;
|
|
261
|
+
contextPolicy?: ContextPolicy;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Decision trace (SDK-only, zero PII)
|
|
265
|
+
*/
|
|
266
|
+
trace?: {
|
|
267
|
+
defaults?: TraceOptions;
|
|
268
|
+
onDecisionTrace?: (evt: StructuredTraceEvent) => void;
|
|
269
|
+
onDecisionTraceAsync?: (evt: StructuredTraceEvent) => Promise<void>;
|
|
270
|
+
queueMax?: number;
|
|
271
|
+
dropPolicy?: "drop_new" | "drop_old";
|
|
272
|
+
onTraceError?: (err: unknown) => void;
|
|
273
|
+
};
|
|
274
|
+
incidentEnvFlag?: string;
|
|
275
|
+
incidentFilePath?: string;
|
|
276
|
+
incidentFilePollMs?: number;
|
|
277
|
+
incidentSignal?: "SIGUSR1" | false;
|
|
278
|
+
};
|
|
279
|
+
type BundleMeta = {
|
|
280
|
+
etag: string;
|
|
281
|
+
bundleVersion?: number;
|
|
282
|
+
updatedAt?: string;
|
|
283
|
+
};
|
|
284
|
+
type RuntimeCache<TBundle = unknown> = {
|
|
285
|
+
meta?: BundleMeta;
|
|
286
|
+
bundle?: TBundle;
|
|
287
|
+
};
|
|
288
|
+
type RefreshResult<TBundle = unknown> = {
|
|
289
|
+
changed: false;
|
|
290
|
+
meta?: BundleMeta;
|
|
291
|
+
} | {
|
|
292
|
+
changed: true;
|
|
293
|
+
meta: BundleMeta;
|
|
294
|
+
bundle: TBundle;
|
|
295
|
+
};
|
|
296
|
+
type RuntimeStatus = {
|
|
297
|
+
state: "warming_up";
|
|
298
|
+
} | {
|
|
299
|
+
state: "ok";
|
|
300
|
+
} | {
|
|
301
|
+
state: "degraded";
|
|
302
|
+
consecutiveFailures: number;
|
|
303
|
+
lastError: {
|
|
304
|
+
message: string;
|
|
305
|
+
at: string;
|
|
306
|
+
};
|
|
307
|
+
nextRetryAt?: string;
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* RuntimeClient = polling/backoff/degraded + cached runtime bundle + policy evaluation.
|
|
311
|
+
*
|
|
312
|
+
* - No endpoints expuestos por el cliente
|
|
313
|
+
* - No PII en trace: el engine emite StructuredTraceEvent sin context ni reglas completas
|
|
314
|
+
*/
|
|
315
|
+
declare class RuntimeClient<TBundle = RuntimeBundleV1> {
|
|
316
|
+
private readonly cfg;
|
|
317
|
+
private cache;
|
|
318
|
+
private timer;
|
|
319
|
+
private isRunning;
|
|
320
|
+
private hasValidBundle;
|
|
321
|
+
private inFlightRefresh;
|
|
322
|
+
private burstUntil;
|
|
323
|
+
private consecutiveFailures;
|
|
324
|
+
private lastError;
|
|
325
|
+
private nextRetryAtMs;
|
|
326
|
+
private updateListeners;
|
|
327
|
+
private statusListeners;
|
|
328
|
+
private readonly engine;
|
|
329
|
+
private incidentPollTimer;
|
|
330
|
+
private incidentFileLastMtimeMs;
|
|
331
|
+
private installedSignalHandler;
|
|
332
|
+
private sigHandler?;
|
|
333
|
+
constructor(config: RuntimeClientConfig);
|
|
334
|
+
/** Subscribe to bundle updates (only when changed). */
|
|
335
|
+
onUpdate(fn: (res: Extract<RefreshResult<TBundle>, {
|
|
336
|
+
changed: true;
|
|
337
|
+
}>) => void): () => void;
|
|
338
|
+
/** Subscribe to status changes (ok/degraded). */
|
|
339
|
+
onStatus(fn: (s: RuntimeStatus) => void): () => void;
|
|
340
|
+
/** Evaluate a decision using the cached bundle (deny-by-default if bundle missing/invalid). */
|
|
341
|
+
evaluate(input: {
|
|
342
|
+
target: Target;
|
|
343
|
+
context?: Record<string, unknown>;
|
|
344
|
+
}): Decision;
|
|
345
|
+
/** Evaluate and (optionally) attach trace based on trace defaults/overrides. */
|
|
346
|
+
evaluateWithTrace(input: {
|
|
347
|
+
target: Target;
|
|
348
|
+
context?: Record<string, unknown>;
|
|
349
|
+
}, options?: TraceOptions): DecisionWithOptionalTrace;
|
|
350
|
+
/** Flush async trace queue (only does something if traceSinkAsync is configured). */
|
|
351
|
+
flushTraces(): Promise<void>;
|
|
352
|
+
getCached(): RuntimeCache<TBundle>;
|
|
353
|
+
getStatus(): RuntimeStatus;
|
|
354
|
+
start(): void;
|
|
355
|
+
warmStart(opts?: {
|
|
356
|
+
timeoutMs?: number;
|
|
357
|
+
burst?: boolean;
|
|
358
|
+
}): Promise<void>;
|
|
359
|
+
stop(): void;
|
|
360
|
+
refreshNow(opts?: {
|
|
361
|
+
burst?: boolean;
|
|
362
|
+
}): Promise<RefreshResult<TBundle>>;
|
|
363
|
+
private emitStatusIfNeeded;
|
|
364
|
+
private refresh;
|
|
365
|
+
private scheduleNext;
|
|
366
|
+
private effectivePollMs;
|
|
367
|
+
private markSuccess;
|
|
368
|
+
private markFailure;
|
|
369
|
+
private computeBackoffDelayMs;
|
|
370
|
+
private bundleUrl;
|
|
371
|
+
private commonHeaders;
|
|
372
|
+
private headBundle;
|
|
373
|
+
private getBundle;
|
|
374
|
+
private enableBurst;
|
|
375
|
+
private disableBurst;
|
|
376
|
+
private applyIncidentFromEnv;
|
|
377
|
+
private startIncidentFilePoller;
|
|
378
|
+
private checkIncidentFileOnce;
|
|
379
|
+
private installIncidentSignal;
|
|
380
|
+
private uninstallIncidentSignal;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
declare class GovplaneError extends Error {
|
|
384
|
+
readonly code: string;
|
|
385
|
+
readonly details?: any | undefined;
|
|
386
|
+
constructor(message: string, code?: string, details?: any | undefined);
|
|
387
|
+
}
|
|
388
|
+
declare class HttpError extends GovplaneError {
|
|
389
|
+
readonly status: number;
|
|
390
|
+
readonly headers?: Record<string, string> | undefined;
|
|
391
|
+
constructor(message: string, status: number, headers?: Record<string, string> | undefined, details?: any);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
type EngineOpts = {
|
|
395
|
+
getBundle: () => RuntimeBundleV1 | undefined;
|
|
396
|
+
validateContext?: boolean;
|
|
397
|
+
contextPolicy?: ContextPolicy;
|
|
398
|
+
traceDefaults?: TraceOptions;
|
|
399
|
+
traceSink?: TraceSink;
|
|
400
|
+
traceSinkAsync?: TraceSinkAsync;
|
|
401
|
+
traceQueueMax?: number;
|
|
402
|
+
traceQueueDropPolicy?: TraceQueueDropPolicy;
|
|
403
|
+
onTraceSinkError?: (err: unknown) => void;
|
|
404
|
+
};
|
|
405
|
+
declare function createPolicyEngine(opts: EngineOpts): PolicyEngine;
|
|
406
|
+
|
|
407
|
+
type FormatOpts = {
|
|
408
|
+
multiline?: boolean;
|
|
409
|
+
includeDiscarded?: boolean;
|
|
410
|
+
};
|
|
411
|
+
declare function formatTrace(trace: DecisionTraceCompact | DecisionTraceFull, opts?: FormatOpts): string;
|
|
412
|
+
|
|
413
|
+
export { type BundleMeta, type ContextPolicy, DEFAULT_CONTEXT_POLICY, type Decision, type DecisionTrace, type DecisionTraceCompact, type DecisionTraceFull, type DecisionTraceHook, type DecisionWithOptionalTrace, type DecisionWithTrace, type Effect, GovplaneError, HttpError, type PolicyEngine, type PolicyEngineEvaluateInput, type RefreshResult, type RuntimeBundleV1, type RuntimeCache, RuntimeClient, type RuntimeClientConfig, type RuntimeClientOptions, type RuntimePolicy, type RuntimeRule, type RuntimeStatus, type StructuredTraceEvent, type Target, type TraceDiscardReason, type TraceLevel, type TraceMode, type TraceOptions, type TraceQueueDropPolicy, type TraceRule, type TraceSink, type TraceSinkAsync, type WhenAstV1, createPolicyEngine, formatTrace, validateContext };
|