instar 1.3.565 → 1.3.567
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/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +16 -1
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/BitwardenProvider.d.ts +8 -0
- package/dist/core/BitwardenProvider.d.ts.map +1 -1
- package/dist/core/BitwardenProvider.js +10 -0
- package/dist/core/BitwardenProvider.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +13 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +55 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/types.d.ts +42 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts +9 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +174 -84
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/monitoring/BlockerLedger.d.ts +43 -2
- package/dist/monitoring/BlockerLedger.d.ts.map +1 -1
- package/dist/monitoring/BlockerLedger.js +90 -5
- package/dist/monitoring/BlockerLedger.js.map +1 -1
- package/dist/monitoring/DurableVaultSession.d.ts +91 -0
- package/dist/monitoring/DurableVaultSession.d.ts.map +1 -0
- package/dist/monitoring/DurableVaultSession.js +145 -0
- package/dist/monitoring/DurableVaultSession.js.map +1 -0
- package/dist/monitoring/SelfUnblockChecklist.d.ts +281 -0
- package/dist/monitoring/SelfUnblockChecklist.d.ts.map +1 -0
- package/dist/monitoring/SelfUnblockChecklist.js +433 -0
- package/dist/monitoring/SelfUnblockChecklist.js.map +1 -0
- package/dist/monitoring/SelfUnblockProbeProviders.d.ts +116 -0
- package/dist/monitoring/SelfUnblockProbeProviders.d.ts.map +1 -0
- package/dist/monitoring/SelfUnblockProbeProviders.js +286 -0
- package/dist/monitoring/SelfUnblockProbeProviders.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +8 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +16 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +106 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +10 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +117 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +8 -0
- package/upgrades/1.3.566.md +104 -0
- package/upgrades/1.3.567.md +39 -0
- package/upgrades/side-effects/self-unblock-before-escalating.md +258 -0
- package/upgrades/side-effects/supervisor-respawn-guarantee.md +61 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SelfUnblockChecklist — the deterministic, code-driven exhaustion checklist that
|
|
3
|
+
* COMPLETES the constitutional standard "Self-Unblock Before Escalating"
|
|
4
|
+
* (docs/specs/self-unblock-before-escalating.md).
|
|
5
|
+
*
|
|
6
|
+
* Foundation (§0): this does NOT fork a parallel gate. BlockerLedger's
|
|
7
|
+
* `settleTrueBlocker` already MANDATES a recorded failed self-fetch/dry-run before
|
|
8
|
+
* a credential/account blocker can settle as a `true-blocker`. The MISSING half
|
|
9
|
+
* was a STANDARD, code-driven set of sources an agent must have probed first —
|
|
10
|
+
* turning "you must record a failed attempt" into "here is the ordered list of
|
|
11
|
+
* places a self-unblockable credential could live, all of which came up empty".
|
|
12
|
+
*
|
|
13
|
+
* What this module provides (the four genuine additions over BlockerLedger):
|
|
14
|
+
* 1. A deterministic relevance matcher (`isScopeRelevant`) — a credential's
|
|
15
|
+
* declared scope tag is "relevant" to a target zone/service iff a
|
|
16
|
+
* deterministic tag/zone match (domain hierarchy + wildcard). Ambiguous,
|
|
17
|
+
* conflicting, or MISSING metadata fails CLOSED. NO LLM in this path.
|
|
18
|
+
* 2. An ORDERED probe runner (`SelfUnblockChecklist.run`) — cheapest/local
|
|
19
|
+
* first, short-circuit on the first `holdsRelevantCred: true`. Each probe is
|
|
20
|
+
* independently timeout-bounded BY CLASS (local sub-second; remote 10–15s),
|
|
21
|
+
* failing toward `reachable: false` on timeout.
|
|
22
|
+
* 3. A durable run STORE (`SelfUnblockRunStore`) — persists each run keyed by an
|
|
23
|
+
* immutable runId; `loadRun(runId)` is what BlockerLedger LOADS + verifies so
|
|
24
|
+
* a caller cannot mint a run the runner did not produce (closes the round-1
|
|
25
|
+
* "self-asserted/gameable list" finding mechanically).
|
|
26
|
+
* 4. The ladder/rung-floor helper (`resolveRung` / `rungToAuthorityCheck`) — maps
|
|
27
|
+
* the human-requirement ladder (§3) onto BlockerLedger's existing
|
|
28
|
+
* `AuthorityCheckEvidence`; enforces the rung FLOOR (capability ≠ authority).
|
|
29
|
+
*
|
|
30
|
+
* Signal vs Authority: this module RECORDS and STRUCTURES. The one judgment —
|
|
31
|
+
* the `true-blocker` settle — stays with BlockerLedger's injected Tier-1
|
|
32
|
+
* authority. This module never blocks an outbound message.
|
|
33
|
+
*
|
|
34
|
+
* Ships DARK behind the existing `monitoring.blockerLedger.*` gate (dev-gate via
|
|
35
|
+
* omitted `enabled`). When the run store is not injected into BlockerLedger, the
|
|
36
|
+
* existing caller-supplied `failedAttempt` path is unchanged.
|
|
37
|
+
*/
|
|
38
|
+
import * as fs from 'fs';
|
|
39
|
+
import * as path from 'path';
|
|
40
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
41
|
+
/** The ordered probe sources (cheapest/local first). Closed taxonomy. */
|
|
42
|
+
export const SELF_UNBLOCK_PROBE_SOURCES = [
|
|
43
|
+
'own-vault', // 1. own per-agent vault (secret-get)
|
|
44
|
+
'org-bitwarden', // 2. org Bitwarden (durable session, §5.3)
|
|
45
|
+
'cloud-vercel', // 3a. authed Vercel account
|
|
46
|
+
'cloud-cloudflare', // 3b. authed Cloudflare account
|
|
47
|
+
'cloud-github', // 3c. authed GitHub (gh)
|
|
48
|
+
'cloud-launchd', // 3d. launchd (extensible: Netlify/Heroku)
|
|
49
|
+
'mcp-tools', // 4. MCP tools
|
|
50
|
+
'browser-playwright', // 5. browser/Playwright sessions
|
|
51
|
+
'controlled-resource', // 6. "a resource I already control?"
|
|
52
|
+
];
|
|
53
|
+
/** Default timeout budget (ms) per class. */
|
|
54
|
+
export const PROBE_TIMEOUT_MS = {
|
|
55
|
+
local: 800, // sub-second keychain/vault read
|
|
56
|
+
remote: 12_000, // remote CLI / network call (10–15s codebase norm)
|
|
57
|
+
};
|
|
58
|
+
/** Which class each source belongs to (drives the per-probe timeout). */
|
|
59
|
+
export const PROBE_SOURCE_CLASS = {
|
|
60
|
+
'own-vault': 'local',
|
|
61
|
+
'org-bitwarden': 'remote',
|
|
62
|
+
'cloud-vercel': 'remote',
|
|
63
|
+
'cloud-cloudflare': 'remote',
|
|
64
|
+
'cloud-github': 'remote',
|
|
65
|
+
'cloud-launchd': 'local',
|
|
66
|
+
'mcp-tools': 'local',
|
|
67
|
+
'browser-playwright': 'local',
|
|
68
|
+
'controlled-resource': 'local',
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Parse a `service:scope` tag. Returns null when the shape is malformed (a tag
|
|
72
|
+
* the matcher must treat as ambiguous → fail closed).
|
|
73
|
+
*
|
|
74
|
+
* A scope is allowed to contain ':' (rare), so only the FIRST colon splits
|
|
75
|
+
* service from scope. An empty service or empty scope is malformed.
|
|
76
|
+
*/
|
|
77
|
+
export function parseScopeTag(tag) {
|
|
78
|
+
if (typeof tag !== 'string')
|
|
79
|
+
return null;
|
|
80
|
+
const trimmed = tag.trim();
|
|
81
|
+
const idx = trimmed.indexOf(':');
|
|
82
|
+
if (idx <= 0 || idx === trimmed.length - 1)
|
|
83
|
+
return null; // no colon, leading colon, or trailing colon
|
|
84
|
+
const service = trimmed.slice(0, idx).trim().toLowerCase();
|
|
85
|
+
const scope = trimmed.slice(idx + 1).trim().toLowerCase();
|
|
86
|
+
if (!service || !scope)
|
|
87
|
+
return null;
|
|
88
|
+
return { service, scope };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Deterministic domain-hierarchy match between a credential's scope and a target
|
|
92
|
+
* scope, with wildcard support:
|
|
93
|
+
* - exact: `dawn-tunnel.dev` matches `dawn-tunnel.dev`
|
|
94
|
+
* - parent-zone: `dawn-tunnel.dev` matches `feedback.dawn-tunnel.dev`
|
|
95
|
+
* - wildcard: `*.dawn-tunnel.dev` matches `feedback.dawn-tunnel.dev`
|
|
96
|
+
* (but a bare wildcard label only matches a SUB-domain, not
|
|
97
|
+
* the apex — `*.dawn-tunnel.dev` does NOT match the apex
|
|
98
|
+
* `dawn-tunnel.dev`, matching CA/DNS wildcard semantics)
|
|
99
|
+
* - non-domain scope (e.g. `project`, `*`): exact string match only; a literal
|
|
100
|
+
* `*` matches ANYTHING for that service (a deliberate "whole-account" tag).
|
|
101
|
+
*
|
|
102
|
+
* NOT a match: a sub-zone credential against a parent target (a credential scoped
|
|
103
|
+
* to `feedback.dawn-tunnel.dev` is NOT relevant to the parent zone
|
|
104
|
+
* `dawn-tunnel.dev` — narrower authority cannot grant the broader goal).
|
|
105
|
+
*/
|
|
106
|
+
function domainScopeMatches(credScope, targetScope) {
|
|
107
|
+
// Whole-account wildcard tag for a non-domain service.
|
|
108
|
+
if (credScope === '*')
|
|
109
|
+
return true;
|
|
110
|
+
const looksLikeDomain = (s) => s.includes('.') || s.startsWith('*.');
|
|
111
|
+
// If neither looks like a domain, require an exact match (e.g. `project`).
|
|
112
|
+
if (!looksLikeDomain(credScope) && !looksLikeDomain(targetScope)) {
|
|
113
|
+
return credScope === targetScope;
|
|
114
|
+
}
|
|
115
|
+
// Wildcard domain: `*.dawn-tunnel.dev` matches a STRICT sub-domain of the base.
|
|
116
|
+
if (credScope.startsWith('*.')) {
|
|
117
|
+
const base = credScope.slice(2);
|
|
118
|
+
if (!base)
|
|
119
|
+
return false;
|
|
120
|
+
// strict sub-domain: target ends with `.base` AND is longer than the base.
|
|
121
|
+
return targetScope.endsWith('.' + base) && targetScope.length > base.length + 1;
|
|
122
|
+
}
|
|
123
|
+
// Exact zone match.
|
|
124
|
+
if (credScope === targetScope)
|
|
125
|
+
return true;
|
|
126
|
+
// Parent-zone match: a credential for `dawn-tunnel.dev` is relevant to the
|
|
127
|
+
// sub-zone `feedback.dawn-tunnel.dev` (broader authority covers the narrower
|
|
128
|
+
// target). Require a label boundary so `evil-dawn-tunnel.dev` never matches.
|
|
129
|
+
return targetScope.endsWith('.' + credScope) && targetScope.length > credScope.length + 1;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Is a credential whose declared scope tag is `credTag` RELEVANT to the blocker
|
|
133
|
+
* `target` (also a `service:scope` tag)? Deterministic. Fails CLOSED on any
|
|
134
|
+
* malformed/missing/cross-service input.
|
|
135
|
+
*
|
|
136
|
+
* - Both tags must parse (`service:scope`).
|
|
137
|
+
* - The SERVICE must match exactly (a Vercel cred is never relevant to a
|
|
138
|
+
* Cloudflare target).
|
|
139
|
+
* - The SCOPE must match per `domainScopeMatches`.
|
|
140
|
+
*/
|
|
141
|
+
export function isScopeRelevant(credTag, target) {
|
|
142
|
+
const cred = parseScopeTag(credTag);
|
|
143
|
+
const tgt = parseScopeTag(target);
|
|
144
|
+
if (!cred || !tgt)
|
|
145
|
+
return false; // missing/ambiguous metadata → fail closed
|
|
146
|
+
if (cred.service !== tgt.service)
|
|
147
|
+
return false; // cross-service never relevant
|
|
148
|
+
return domainScopeMatches(cred.scope, tgt.scope);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Given a set of scope tags a source advertises, is ANY of them relevant to the
|
|
152
|
+
* target? An empty/undefined set fails CLOSED → false (under-tagged credential is
|
|
153
|
+
* simply not surfaced). Returns the matching tags for the audit surface.
|
|
154
|
+
*/
|
|
155
|
+
export function relevantScopeTags(advertised, target) {
|
|
156
|
+
if (!Array.isArray(advertised))
|
|
157
|
+
return [];
|
|
158
|
+
const out = [];
|
|
159
|
+
for (const tag of advertised) {
|
|
160
|
+
if (isScopeRelevant(tag, target) && typeof tag === 'string')
|
|
161
|
+
out.push(tag);
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Durable JSONL store for checklist runs. One run per line, keyed by an immutable
|
|
167
|
+
* runId. `loadRun` is skip-corrupt-lines tolerant + bounded (precedent:
|
|
168
|
+
* ReapLog.read). A run is APPEND-only; the store never mutates a prior run.
|
|
169
|
+
*/
|
|
170
|
+
export class SelfUnblockRunStore {
|
|
171
|
+
runsPath;
|
|
172
|
+
/** Bound on how many trailing lines `loadRun` will scan (newest runs win). */
|
|
173
|
+
maxScan;
|
|
174
|
+
constructor(opts) {
|
|
175
|
+
this.runsPath = path.join(opts.stateDir, 'state', 'self-unblock-runs', 'runs.jsonl');
|
|
176
|
+
this.maxScan = opts.maxScan ?? 2000;
|
|
177
|
+
}
|
|
178
|
+
/** The file the store reads/writes (exposed for tests). */
|
|
179
|
+
get path() {
|
|
180
|
+
return this.runsPath;
|
|
181
|
+
}
|
|
182
|
+
/** Append a completed run. Best-effort durable (atomic append). */
|
|
183
|
+
save(run) {
|
|
184
|
+
fs.mkdirSync(path.dirname(this.runsPath), { recursive: true });
|
|
185
|
+
fs.appendFileSync(this.runsPath, JSON.stringify(run) + '\n');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Load a run by id, scanning the trailing `maxScan` lines newest-first so a
|
|
189
|
+
* recent run is found fast and a corrupt/partial line never fails the read.
|
|
190
|
+
* Returns null when the id is unknown.
|
|
191
|
+
*/
|
|
192
|
+
loadRun(runId) {
|
|
193
|
+
if (typeof runId !== 'string' || !runId)
|
|
194
|
+
return null;
|
|
195
|
+
let raw;
|
|
196
|
+
try {
|
|
197
|
+
raw = fs.readFileSync(this.runsPath, 'utf-8');
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// @silent-fallback-ok — no runs file yet means no run by that id.
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
204
|
+
const tail = lines.slice(-this.maxScan);
|
|
205
|
+
for (let i = tail.length - 1; i >= 0; i--) {
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(tail[i]);
|
|
208
|
+
if (parsed && parsed.runId === runId)
|
|
209
|
+
return parsed;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// @silent-fallback-ok — skip a corrupt/partial JSONL line rather than
|
|
213
|
+
// failing the whole read; a partial trailing line is a normal
|
|
214
|
+
// crash-during-append artifact, not a degradation worth reporting.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/** Read the most-recent `limit` runs (newest last) for the read surface. */
|
|
220
|
+
list(limit = 200) {
|
|
221
|
+
let raw;
|
|
222
|
+
try {
|
|
223
|
+
raw = fs.readFileSync(this.runsPath, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// @silent-fallback-ok — no runs file yet means an empty list (first run),
|
|
227
|
+
// the same expected first-run condition as loadRun's read above.
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
const lines = raw.split('\n').filter((l) => l.trim().length > 0);
|
|
231
|
+
const tail = limit > 0 ? lines.slice(-limit) : lines;
|
|
232
|
+
const out = [];
|
|
233
|
+
for (const line of tail) {
|
|
234
|
+
try {
|
|
235
|
+
out.push(JSON.parse(line));
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// skip a corrupt/partial line
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export class SelfUnblockChecklist {
|
|
245
|
+
providers;
|
|
246
|
+
store;
|
|
247
|
+
now;
|
|
248
|
+
timeouts;
|
|
249
|
+
mintRunId;
|
|
250
|
+
constructor(opts) {
|
|
251
|
+
this.providers = opts.providers ?? {};
|
|
252
|
+
this.store = opts.store;
|
|
253
|
+
this.now = opts.now ?? (() => new Date());
|
|
254
|
+
this.timeouts = {
|
|
255
|
+
local: opts.timeoutMs?.local ?? PROBE_TIMEOUT_MS.local,
|
|
256
|
+
remote: opts.timeoutMs?.remote ?? PROBE_TIMEOUT_MS.remote,
|
|
257
|
+
};
|
|
258
|
+
this.mintRunId =
|
|
259
|
+
opts.mintRunId ??
|
|
260
|
+
(() => `SUN-${this.now().getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Run the ORDERED checklist against `target`, short-circuiting on the first
|
|
264
|
+
* `holdsRelevantCred: true`. Persists the run and returns it.
|
|
265
|
+
*
|
|
266
|
+
* `requiredAttemptType` matches BlockerLedger's taxonomy: `self-fetch` for the
|
|
267
|
+
* credential/account-kind blockers (vault/cloud-account probes), `dry-run`
|
|
268
|
+
* otherwise.
|
|
269
|
+
*/
|
|
270
|
+
async run(input) {
|
|
271
|
+
const target = input.target;
|
|
272
|
+
const probes = [];
|
|
273
|
+
for (const source of SELF_UNBLOCK_PROBE_SOURCES) {
|
|
274
|
+
const result = await this.probeOne(source, target);
|
|
275
|
+
probes.push(result);
|
|
276
|
+
if (result.holdsRelevantCred) {
|
|
277
|
+
// Short-circuit: a self-unblock credential exists; the agent should USE it,
|
|
278
|
+
// not escalate. The run is NOT exhausted.
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const exhausted = probes.length > 0 && probes.every((p) => !p.holdsRelevantCred);
|
|
283
|
+
const run = {
|
|
284
|
+
runId: this.mintRunId(),
|
|
285
|
+
target,
|
|
286
|
+
requiredAttemptType: input.requiredAttemptType,
|
|
287
|
+
probes,
|
|
288
|
+
completedAt: this.now().toISOString(),
|
|
289
|
+
exhausted,
|
|
290
|
+
};
|
|
291
|
+
this.store.save(run);
|
|
292
|
+
return run;
|
|
293
|
+
}
|
|
294
|
+
/** Probe ONE source with a hard per-class timeout. Fails toward `reachable: false`. */
|
|
295
|
+
async probeOne(source, target) {
|
|
296
|
+
const probedAt = this.now().toISOString();
|
|
297
|
+
const provider = this.providers[source];
|
|
298
|
+
if (!provider) {
|
|
299
|
+
// No provider wired for this source → unreachable (degrade gracefully).
|
|
300
|
+
return { source, reachable: false, holdsRelevantCred: false, probedAt, detail: 'no provider' };
|
|
301
|
+
}
|
|
302
|
+
const budget = this.timeouts[PROBE_SOURCE_CLASS[source]];
|
|
303
|
+
let providerResult;
|
|
304
|
+
try {
|
|
305
|
+
providerResult = await this.withTimeout(provider(target), budget);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Timeout or provider error → unreachable, fail closed.
|
|
309
|
+
return {
|
|
310
|
+
source,
|
|
311
|
+
reachable: false,
|
|
312
|
+
holdsRelevantCred: false,
|
|
313
|
+
probedAt,
|
|
314
|
+
detail: 'probe timed out or errored',
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (!providerResult.reachable) {
|
|
318
|
+
return {
|
|
319
|
+
source,
|
|
320
|
+
reachable: false,
|
|
321
|
+
holdsRelevantCred: false,
|
|
322
|
+
probedAt,
|
|
323
|
+
detail: providerResult.detail,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const matched = relevantScopeTags(providerResult.advertisedScopeTags, target);
|
|
327
|
+
return {
|
|
328
|
+
source,
|
|
329
|
+
reachable: true,
|
|
330
|
+
holdsRelevantCred: matched.length > 0,
|
|
331
|
+
probedAt,
|
|
332
|
+
detail: providerResult.detail,
|
|
333
|
+
matchedScopeTags: matched.length > 0 ? matched : undefined,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** Reject after `ms`, so a hung provider can never stall the path. */
|
|
337
|
+
withTimeout(p, ms) {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
const timer = setTimeout(() => reject(new Error('probe-timeout')), ms);
|
|
340
|
+
// Do not keep the event loop alive on the timer.
|
|
341
|
+
if (typeof timer.unref === 'function') {
|
|
342
|
+
timer.unref();
|
|
343
|
+
}
|
|
344
|
+
p.then((v) => {
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
resolve(v);
|
|
347
|
+
}, (e) => {
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
reject(e);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** True iff the action class triggers the rung-1 floor. */
|
|
355
|
+
export function actionTriggersRungFloor(action) {
|
|
356
|
+
if (!action)
|
|
357
|
+
return false;
|
|
358
|
+
return !!(action.irreversible ||
|
|
359
|
+
action.costBearingAboveThreshold ||
|
|
360
|
+
action.outOfScope ||
|
|
361
|
+
action.policySensitive);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Resolve the lowest legitimate rung for a blocker, ENFORCING the rung floor.
|
|
365
|
+
*
|
|
366
|
+
* Base resolution:
|
|
367
|
+
* - run NOT exhausted (a self-unblock cred exists) → rung 0 (nothing required)
|
|
368
|
+
* - run exhausted + operator-only secret → rung 2 (operator-only credential)
|
|
369
|
+
* - run exhausted otherwise → rung 1 (an approval)
|
|
370
|
+
*
|
|
371
|
+
* Floor (capability ≠ authority): if the action class is irreversible /
|
|
372
|
+
* cost-bearing-above-threshold / out-of-scope / policy-sensitive, the rung can
|
|
373
|
+
* never be BELOW 1 — even a rung-0 self-unblock is raised to rung 1 (approval).
|
|
374
|
+
*/
|
|
375
|
+
export function resolveRung(input) {
|
|
376
|
+
let base;
|
|
377
|
+
let reason;
|
|
378
|
+
if (!input.run.exhausted) {
|
|
379
|
+
base = 0;
|
|
380
|
+
reason = 'a self-unblock credential is available within the agent\'s own access';
|
|
381
|
+
}
|
|
382
|
+
else if (input.operatorOnlySecret) {
|
|
383
|
+
base = 2;
|
|
384
|
+
reason = 'exhausted self-unblock paths; the dependency is an operator-only credential';
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
base = 1;
|
|
388
|
+
reason = 'exhausted self-unblock paths; an operator approval is required';
|
|
389
|
+
}
|
|
390
|
+
const floor = actionTriggersRungFloor(input.action);
|
|
391
|
+
if (floor && base < 1) {
|
|
392
|
+
return {
|
|
393
|
+
rung: 1,
|
|
394
|
+
raisedByFloor: true,
|
|
395
|
+
reason: 'the action is irreversible/cost-bearing/out-of-scope/policy-sensitive, so an approval ' +
|
|
396
|
+
'is required even though a self-unblock credential exists (capability is not authority)',
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return { rung: base, raisedByFloor: false, reason };
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Map a resolved rung onto BlockerLedger's existing `AuthorityCheckEvidence`
|
|
403
|
+
* shape (§3 — the rung is recorded there, NOT in a new field). A rung-1 grant
|
|
404
|
+
* MUST resolve against a VERIFIED principal (Know Your Principal): when the rung
|
|
405
|
+
* is >=1 and the grant matters, `principalVerified` must be true or the grant is
|
|
406
|
+
* not honored. This function refuses to assert `userHasAuthority: true` for an
|
|
407
|
+
* unverified principal.
|
|
408
|
+
*/
|
|
409
|
+
export function rungToAuthorityCheck(input) {
|
|
410
|
+
const { rung, reason, raisedByFloor } = input.resolution;
|
|
411
|
+
// Rung 0: the agent has the authority itself (no human needed).
|
|
412
|
+
if (rung === 0) {
|
|
413
|
+
return {
|
|
414
|
+
agentHasAuthority: true,
|
|
415
|
+
userHasAuthority: false,
|
|
416
|
+
note: `rung 0 — ${reason}`,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
// Rung 1/2: a human is required. The grant only counts against a VERIFIED
|
|
420
|
+
// principal — an unverified principal can never be recorded as holding the
|
|
421
|
+
// authority (a name seen in content is a question, not a fact).
|
|
422
|
+
const userHasAuthority = input.principalVerified;
|
|
423
|
+
const floorNote = raisedByFloor ? ' (rung raised by the action-class floor)' : '';
|
|
424
|
+
const principalNote = input.principalVerified
|
|
425
|
+
? 'verified principal'
|
|
426
|
+
: 'principal NOT verified — grant cannot be honored until resolved against a verified-operator/mandate surface';
|
|
427
|
+
return {
|
|
428
|
+
agentHasAuthority: false,
|
|
429
|
+
userHasAuthority,
|
|
430
|
+
note: `rung ${rung} — ${reason}${floorNote}; ${principalNote}`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
//# sourceMappingURL=SelfUnblockChecklist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelfUnblockChecklist.js","sourceRoot":"","sources":["../../src/monitoring/SelfUnblockChecklist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SelfUnblockProbeProviders — the PRODUCTION probe providers that wire
|
|
3
|
+
* SelfUnblockChecklist to the real world. This is the PRODUCER half of
|
|
4
|
+
* "Self-Unblock Before Escalating" (docs/specs/self-unblock-before-escalating.md):
|
|
5
|
+
* SelfUnblockChecklist (the runner), DurableVaultSession (the warm org-vault
|
|
6
|
+
* session), and BlockerLedger (the verifier) already exist — but until this file
|
|
7
|
+
* NOTHING in production instantiates the checklist with REAL providers, so a
|
|
8
|
+
* credential-blocker could never be settled (the gate demands a run that could
|
|
9
|
+
* not be produced). This closes that gap.
|
|
10
|
+
*
|
|
11
|
+
* The checklist runner owns the deterministic relevance match and the per-class
|
|
12
|
+
* timeout; a provider's ONLY job is to (a) reach its source and (b) report the
|
|
13
|
+
* NON-SECRET scope tags that source ADVERTISES, so the runner can match them.
|
|
14
|
+
*
|
|
15
|
+
* HARD SAFETY RULES (a violation is a bug — the prior incident spiked machine
|
|
16
|
+
* load to ~70 from a recursive grep; never repeat that):
|
|
17
|
+
* (a) A provider NEVER returns or logs a secret VALUE. It returns ONLY
|
|
18
|
+
* `{ reachable, advertisedScopeTags?, detail? }`, where advertisedScopeTags
|
|
19
|
+
* are non-secret scope strings (zone/project/service names).
|
|
20
|
+
* (b) Each provider does AT MOST ONE bounded call — a single CLI exec with a
|
|
21
|
+
* small explicit timeout, or a single fetch. NEVER a recursive/unbounded
|
|
22
|
+
* filesystem scan or `grep -r` over a large tree. The runner's per-class
|
|
23
|
+
* timeout is the backstop; every execFile here ALSO carries its own timeout.
|
|
24
|
+
* (c) Relevance is OPERATOR-DECLARED + fail-closed. A provider NEVER infers
|
|
25
|
+
* which credential is relevant; advertised scope tags come from the
|
|
26
|
+
* `credentialScopeTags` config map (keyed by source name and/or vault key).
|
|
27
|
+
* A source with no declared tags advertises an EMPTY tag list → the runner's
|
|
28
|
+
* `relevantScopeTags` never matches it → it is never surfaced. Nothing
|
|
29
|
+
* declared ⇒ every probe non-matching ⇒ runs exhaust ⇒ behaves like today
|
|
30
|
+
* (under-self-unblock, never mis-apply — the safe direction).
|
|
31
|
+
*
|
|
32
|
+
* Everything external is INJECTED via `deps`, so unit tests never shell out.
|
|
33
|
+
*/
|
|
34
|
+
import type { DurableVaultSession } from './DurableVaultSession.js';
|
|
35
|
+
import type { ProbeProviders } from './SelfUnblockChecklist.js';
|
|
36
|
+
/**
|
|
37
|
+
* The result of a single bounded child-process call. `code` 0 = success.
|
|
38
|
+
* `stdout` is the captured output (a provider parses NON-SECRET tags from it; it
|
|
39
|
+
* MUST NOT return raw stdout as a tag without scrubbing).
|
|
40
|
+
*/
|
|
41
|
+
export interface BoundedExecResult {
|
|
42
|
+
code: number | null;
|
|
43
|
+
stdout: string;
|
|
44
|
+
stderr: string;
|
|
45
|
+
timedOut: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A single, timeout-bounded `execFile`. Always pass an explicit `timeoutMs`
|
|
49
|
+
* (rule (b)) — never rely on the runner's class timeout alone. Returns a
|
|
50
|
+
* non-throwing result (a non-zero exit is `{ code: !=0 }`, not an exception) so
|
|
51
|
+
* a provider stays a single bounded call with no surprise rejections.
|
|
52
|
+
*/
|
|
53
|
+
export type ExecFileBounded = (file: string, args: string[], opts: {
|
|
54
|
+
timeoutMs: number;
|
|
55
|
+
env?: NodeJS.ProcessEnv;
|
|
56
|
+
}) => Promise<BoundedExecResult>;
|
|
57
|
+
/** The default bounded execFile — one child process, hard timeout, captured output. */
|
|
58
|
+
export declare const defaultExecFileBounded: ExecFileBounded;
|
|
59
|
+
export interface SelfUnblockProbeDeps {
|
|
60
|
+
/**
|
|
61
|
+
* OPERATOR-DECLARED scope tags, keyed by source name (e.g. `org-bitwarden`,
|
|
62
|
+
* `cloud-vercel`) AND/OR by vault key name. A source with no entry advertises an
|
|
63
|
+
* EMPTY tag list → it is never surfaced (rule (c), fail-closed). The values are
|
|
64
|
+
* non-secret `service:scope` strings (e.g. `["cloudflare:dawn-tunnel.dev"]`).
|
|
65
|
+
*/
|
|
66
|
+
credentialScopeTags?: Record<string, string[]>;
|
|
67
|
+
/**
|
|
68
|
+
* The flag-gated warm org-Bitwarden session (§5.3). When absent the org-vault
|
|
69
|
+
* probe reports unreachable with a clear detail (never a throw-stub).
|
|
70
|
+
*/
|
|
71
|
+
durableVaultSession?: DurableVaultSession;
|
|
72
|
+
/**
|
|
73
|
+
* Returns the NAMES of secrets in the agent's own vault (NEVER values). Used by
|
|
74
|
+
* the own-vault probe to decide reachability + which declared tags to advertise.
|
|
75
|
+
* Absent (or a thrown read) → own-vault reports unreachable, fail-closed.
|
|
76
|
+
*/
|
|
77
|
+
getVaultKeys?: () => string[];
|
|
78
|
+
/**
|
|
79
|
+
* Returns the Cloudflare API token VALUE for the single bounded zones fetch, or
|
|
80
|
+
* null when none is configured. The VALUE is used in-process only (an
|
|
81
|
+
* Authorization header) — NEVER logged, NEVER returned. Absent/null → the
|
|
82
|
+
* cloudflare probe reports unreachable.
|
|
83
|
+
*/
|
|
84
|
+
getCloudflareToken?: () => string | null;
|
|
85
|
+
/** Injectable bounded execFile (tests pass a fake; production uses the default). */
|
|
86
|
+
execFileBounded?: ExecFileBounded;
|
|
87
|
+
/** Injectable fetch (tests pass a fake; production uses global fetch). */
|
|
88
|
+
fetchImpl?: typeof fetch;
|
|
89
|
+
}
|
|
90
|
+
/** The minimal BitwardenProvider surface the session derivation needs. */
|
|
91
|
+
export interface BitwardenUnlockSurface {
|
|
92
|
+
unlock(masterPassword: string): boolean;
|
|
93
|
+
getSessionKey(): string | null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Derive a fresh org-Bitwarden session value for `DurableVaultSession.deriveSession`,
|
|
97
|
+
* TESTABLY. The AgentServer closure that wires this is otherwise untestable — which
|
|
98
|
+
* is exactly how a `process.env.BW_SESSION` read (wrong: `unlock()` stores the
|
|
99
|
+
* session in a PRIVATE field, never the env) slipped past the injected-fake tests.
|
|
100
|
+
*
|
|
101
|
+
* Reads the operator-held master password from the agent's own vault (an EXISTING
|
|
102
|
+
* key — no new on-disk secret) via the injected getter, unlocks via the injected
|
|
103
|
+
* BitwardenProvider surface, and returns the live session from `getSessionKey()`.
|
|
104
|
+
* The password and the session are used in-process ONLY and NEVER logged or returned
|
|
105
|
+
* through any other surface.
|
|
106
|
+
*/
|
|
107
|
+
export declare function deriveBitwardenSession(opts: {
|
|
108
|
+
getMasterPassword: () => string | null;
|
|
109
|
+
bw: BitwardenUnlockSurface;
|
|
110
|
+
}): string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Build the full production provider set — a REAL provider for EVERY one of the 9
|
|
113
|
+
* sources in `SELF_UNBLOCK_PROBE_SOURCES`. No source is left unwired.
|
|
114
|
+
*/
|
|
115
|
+
export declare function buildProductionProbeProviders(deps: SelfUnblockProbeDeps): ProbeProviders;
|
|
116
|
+
//# sourceMappingURL=SelfUnblockProbeProviders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelfUnblockProbeProviders.d.ts","sourceRoot":"","sources":["../../src/monitoring/SelfUnblockProbeProviders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAEV,cAAc,EAGf,MAAM,2BAA2B,CAAC;AAKnC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,KACjD,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,EAAE,eAuBjC,CAAC;AAIL,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACzC,oFAAoF;IACpF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAqMD,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,aAAa,IAAI,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,iBAAiB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvC,EAAE,EAAE,sBAAsB,CAAC;CAC5B,GAAG,MAAM,GAAG,IAAI,CAYhB;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CAoDxF"}
|