fidelis 0.1.2 → 0.1.3
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/auditor.js +64 -57
- package/dist/compiler.js +2 -1
- package/dist/config.js +2 -1
- package/dist/providers.d.ts +4 -0
- package/dist/providers.js +17 -4
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/auditor.js
CHANGED
|
@@ -158,68 +158,75 @@ export class Auditor {
|
|
|
158
158
|
const state = finish("incomplete", reason, findings);
|
|
159
159
|
return state ? { action: "none", state, reason, notice: `Fidelis audit incomplete: ${reason}` } : stale();
|
|
160
160
|
};
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const state = finish("stop", reason, []);
|
|
164
|
-
return state ? { action: "none", state, reason, notice: `Fidelis stopped: ${reason}` } : stale();
|
|
165
|
-
}
|
|
166
|
-
if (!input.sources)
|
|
167
|
-
return incomplete("The host did not reveal which instructions it loaded.");
|
|
168
|
-
const sources = resolveSources(input.sources);
|
|
169
|
-
if (sources.length === 0) {
|
|
170
|
-
const reason = "No effective instructions to audit.";
|
|
171
|
-
const state = finish("clean", reason, []);
|
|
172
|
-
return state ? { action: "none", state, reason } : stale();
|
|
173
|
-
}
|
|
174
|
-
const options = this.o.options ?? {};
|
|
175
|
-
const compileConsent = consentProblem(config.compilerModel, options, config);
|
|
176
|
-
const compiled = await getChecklist({
|
|
177
|
-
store,
|
|
178
|
-
host: this.o.host,
|
|
179
|
-
sources,
|
|
180
|
-
config,
|
|
181
|
-
generate: compileConsent ? null : this.o.compiler,
|
|
182
|
-
onCompile: () => this.save(sessionID, (state) => void (state.phase = "compiling"), "compiling"),
|
|
183
|
-
});
|
|
184
|
-
metrics.cacheHit = compiled.cacheHit;
|
|
185
|
-
metrics.compilerCalls = compiled.calls;
|
|
186
|
-
metrics.tokens = compiled.tokens;
|
|
187
|
-
metrics.costUSD = compiled.costUSD;
|
|
188
|
-
charge(compiled.costUSD);
|
|
189
|
-
if (!compiled.checklist)
|
|
190
|
-
return incomplete(compileConsent && !compiled.cacheHit ? compileConsent : (compiled.reason ?? "Checklist unavailable."));
|
|
191
|
-
this.save(sessionID, (state) => void (state.phase = "judging"), "judging");
|
|
192
|
-
const judgeConsent = consentProblem(config.judgeModel, options, config);
|
|
193
|
-
if (judgeConsent)
|
|
194
|
-
return incomplete(judgeConsent);
|
|
195
|
-
if (!this.o.judge)
|
|
196
|
-
return incomplete("No decision model is available.");
|
|
197
|
-
let verdict;
|
|
161
|
+
// Past this point the session is AUDITING, so any failure must still end in a recorded outcome.
|
|
162
|
+
const bounded = { ...config, deadlineAt: started + config.auditDeadlineMs, onFailedCost: (costUSD) => { metrics.costUSD += costUSD; charge(costUSD); } };
|
|
198
163
|
try {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
164
|
+
if (config.maxAuditCostUSD !== null && begin.spentUSD >= config.maxAuditCostUSD) {
|
|
165
|
+
const reason = `Audit budget of $${config.maxAuditCostUSD} is exhausted.`;
|
|
166
|
+
const state = finish("stop", reason, []);
|
|
167
|
+
return state ? { action: "none", state, reason, notice: `Fidelis stopped: ${reason}` } : stale();
|
|
168
|
+
}
|
|
169
|
+
if (!input.sources)
|
|
170
|
+
return incomplete("The host did not reveal which instructions it loaded.");
|
|
171
|
+
const sources = resolveSources(input.sources);
|
|
172
|
+
if (sources.length === 0) {
|
|
173
|
+
const reason = "No effective instructions to audit.";
|
|
174
|
+
const state = finish("clean", reason, []);
|
|
175
|
+
return state ? { action: "none", state, reason } : stale();
|
|
176
|
+
}
|
|
177
|
+
const options = this.o.options ?? {};
|
|
178
|
+
const compileConsent = consentProblem(config.compilerModel, options, config);
|
|
179
|
+
const compiled = await getChecklist({
|
|
180
|
+
store,
|
|
181
|
+
host: this.o.host,
|
|
182
|
+
sources,
|
|
183
|
+
config: bounded,
|
|
184
|
+
generate: compileConsent ? null : this.o.compiler,
|
|
185
|
+
onCompile: () => this.save(sessionID, (state) => void (state.phase = "compiling"), "compiling"),
|
|
216
186
|
});
|
|
187
|
+
metrics.cacheHit = compiled.cacheHit;
|
|
188
|
+
metrics.compilerCalls = compiled.calls;
|
|
189
|
+
metrics.tokens = compiled.tokens;
|
|
190
|
+
metrics.costUSD = compiled.costUSD;
|
|
191
|
+
charge(compiled.costUSD);
|
|
192
|
+
if (!compiled.checklist)
|
|
193
|
+
return incomplete(compileConsent && !compiled.cacheHit ? compileConsent : (compiled.reason ?? "Checklist unavailable."));
|
|
194
|
+
this.save(sessionID, (state) => void (state.phase = "judging"), "judging");
|
|
195
|
+
const judgeConsent = consentProblem(config.judgeModel, options, config);
|
|
196
|
+
if (judgeConsent)
|
|
197
|
+
return incomplete(judgeConsent);
|
|
198
|
+
if (!this.o.judge)
|
|
199
|
+
return incomplete("No decision model is available.");
|
|
200
|
+
let verdict;
|
|
201
|
+
try {
|
|
202
|
+
verdict = await judge({
|
|
203
|
+
checklist: compiled.checklist,
|
|
204
|
+
evidence: input.evidence,
|
|
205
|
+
prior: begin.findings.filter((finding) => finding.status === "VIOLATED"),
|
|
206
|
+
model: this.o.judge,
|
|
207
|
+
config: bounded,
|
|
208
|
+
stopReason: () => {
|
|
209
|
+
const current = store.read(sessionID);
|
|
210
|
+
if (current.audit?.id !== auditID)
|
|
211
|
+
return "the audit was superseded by a newer turn";
|
|
212
|
+
return config.maxAuditCostUSD !== null && current.spentUSD >= config.maxAuditCostUSD ? "the audit budget is exhausted" : null;
|
|
213
|
+
},
|
|
214
|
+
onUsage: (tokens, costUSD) => {
|
|
215
|
+
metrics.tokens += tokens;
|
|
216
|
+
metrics.costUSD += costUSD;
|
|
217
|
+
charge(costUSD);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
return incomplete(`Judge failed: ${error.message}`);
|
|
223
|
+
}
|
|
224
|
+
metrics.judgeCalls = verdict.calls;
|
|
225
|
+
return this.route(verdict.findings, verdict.waiting, compiled.checklist.dropped.length, begin, auditID, finish, stale, incomplete);
|
|
217
226
|
}
|
|
218
227
|
catch (error) {
|
|
219
|
-
return incomplete(`
|
|
228
|
+
return incomplete(`Audit failed: ${error.message}`);
|
|
220
229
|
}
|
|
221
|
-
metrics.judgeCalls = verdict.calls;
|
|
222
|
-
return this.route(verdict.findings, verdict.waiting, compiled.checklist.dropped.length, begin, auditID, finish, stale, incomplete);
|
|
223
230
|
}
|
|
224
231
|
/** Deterministic routing. Model output only suggests; these branches decide. */
|
|
225
232
|
route(findings, waiting, dropped, begin, auditID, finish, stale, incomplete) {
|
package/dist/compiler.js
CHANGED
|
@@ -55,7 +55,8 @@ export function validateRules(raw, sources) {
|
|
|
55
55
|
return void dropped.push(`${label}: invalid shape`);
|
|
56
56
|
// The verbatim quote is the traceability anchor. Models often shorten a path, so it may correct the source.
|
|
57
57
|
const key = normalize(item.quote);
|
|
58
|
-
|
|
58
|
+
// The compiler reads redacted text, so a rule that mentions a credential comes back with [REDACTED] in its quote.
|
|
59
|
+
const holds = (candidate) => normalize(candidate.text).includes(key) || normalize(redact(candidate.text)).includes(key);
|
|
59
60
|
const source = sources.find((candidate) => candidate.path === item.source && holds(candidate)) ?? sources.find(holds);
|
|
60
61
|
if (!source || !key)
|
|
61
62
|
return void dropped.push(`${label}: quote not verbatim`);
|
package/dist/config.js
CHANGED
|
@@ -18,7 +18,8 @@ const BASE = {
|
|
|
18
18
|
allowFreeModelDataUse: false,
|
|
19
19
|
trustSdkPrompts: false,
|
|
20
20
|
timeoutMs: 240_000,
|
|
21
|
-
retries:
|
|
21
|
+
retries: 1,
|
|
22
|
+
auditDeadlineMs: 600_000,
|
|
22
23
|
retentionDays: 30,
|
|
23
24
|
};
|
|
24
25
|
const UI = { enabled: true, footer: true, notifications: "important", auditPanel: true, plainTextIcons: false };
|
package/dist/providers.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export interface RetryOptions {
|
|
|
38
38
|
retries: number;
|
|
39
39
|
timeoutMs: number;
|
|
40
40
|
baseDelayMs?: number;
|
|
41
|
+
/** Whole-audit deadline, so retries cannot outlast the host's hook timeout. */
|
|
42
|
+
deadlineAt?: number;
|
|
43
|
+
/** A failed attempt can still have been billed. */
|
|
44
|
+
onFailedCost?: (costUSD: number) => void;
|
|
41
45
|
}
|
|
42
46
|
/** Bounded retries with backoff and a timeout. Auth, availability and quota errors fail at once. */
|
|
43
47
|
export declare function withRetry<T>(run: () => Promise<T>, options: RetryOptions): Promise<T>;
|
package/dist/providers.js
CHANGED
|
@@ -6,15 +6,22 @@ export async function withRetry(run, options) {
|
|
|
6
6
|
for (let attempt = 0; attempt <= options.retries; attempt++) {
|
|
7
7
|
if (attempt > 0)
|
|
8
8
|
await new Promise((done) => setTimeout(done, (options.baseDelayMs ?? 1000) * 2 ** (attempt - 1)));
|
|
9
|
+
const left = (options.deadlineAt ?? Infinity) - Date.now();
|
|
10
|
+
if (left <= 0)
|
|
11
|
+
throw lastError ?? new Error("the audit time limit was reached");
|
|
12
|
+
const limit = Math.min(options.timeoutMs, left);
|
|
9
13
|
let timer;
|
|
10
14
|
try {
|
|
11
15
|
const timeout = new Promise((_, reject) => {
|
|
12
|
-
timer = setTimeout(() => reject(new Error(`timed out after ${
|
|
16
|
+
timer = setTimeout(() => reject(new Error(`timed out after ${limit} ms`)), limit);
|
|
13
17
|
});
|
|
14
18
|
return await Promise.race([run(), timeout]);
|
|
15
19
|
}
|
|
16
20
|
catch (error) {
|
|
17
21
|
lastError = error;
|
|
22
|
+
const cost = error?.costUSD;
|
|
23
|
+
if (typeof cost === "number" && cost > 0)
|
|
24
|
+
options.onFailedCost?.(cost);
|
|
18
25
|
if (HARD_FAILURE.test(String(error?.message ?? error)))
|
|
19
26
|
break;
|
|
20
27
|
}
|
|
@@ -118,9 +125,15 @@ export function textDecisions(generate, id) {
|
|
|
118
125
|
: `- ${question.name} (choice): ${question.instructions} Values: ${question.choices.map((choice) => `${choice.value} = ${choice.description}`).join("; ")}`),
|
|
119
126
|
].join("\n");
|
|
120
127
|
const output = generated(await generate(prompt, DECISIONS_SCHEMA));
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = parseJson(output.text);
|
|
131
|
+
if (!parsed || typeof parsed.answers !== "object")
|
|
132
|
+
throw new Error("decision output lacks answers");
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
throw Object.assign(error, { costUSD: output.costUSD });
|
|
136
|
+
}
|
|
124
137
|
const answers = {};
|
|
125
138
|
for (const question of questions) {
|
|
126
139
|
const answer = parsed.answers[question.name];
|
package/dist/types.d.ts
CHANGED
|
@@ -164,6 +164,8 @@ export interface Config {
|
|
|
164
164
|
trustSdkPrompts: boolean;
|
|
165
165
|
timeoutMs: number;
|
|
166
166
|
retries: number;
|
|
167
|
+
/** Whole-audit limit. Must stay below the host's hook timeout. */
|
|
168
|
+
auditDeadlineMs: number;
|
|
167
169
|
retentionDays: number;
|
|
168
170
|
ui: {
|
|
169
171
|
enabled: boolean;
|