@agent-finops/core 0.5.8 → 0.6.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.
- package/README.md +6 -0
- package/dist/agentInventory.d.ts +11 -14
- package/dist/agentInventory.js +180 -46
- package/dist/analyze.js +165 -113
- package/dist/contextHealth.d.ts +10 -1
- package/dist/contextHealth.js +216 -44
- package/dist/cutList.d.ts +9 -3
- package/dist/cutList.js +99 -41
- package/dist/deadContext.d.ts +8 -4
- package/dist/deadContext.js +83 -26
- package/dist/discovery.d.ts +6 -2
- package/dist/discovery.js +36 -14
- package/dist/glance.d.ts +9 -2
- package/dist/glance.js +107 -24
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/insights.js +53 -30
- package/dist/localAgentLogs.d.ts +80 -2
- package/dist/localAgentLogs.js +480 -88
- package/dist/modelPricing.js +0 -1
- package/dist/providerConnectors.d.ts +3 -2
- package/dist/providerConnectors.js +673 -89
- package/dist/sampleData.js +32 -4
- package/dist/schema.d.ts +105 -27
- package/dist/schema.js +110 -2
- package/dist/sourceRegistry.d.ts +30 -5
- package/dist/sourceRegistry.js +250 -21
- package/dist/sourceStatus.d.ts +65 -0
- package/dist/sourceStatus.js +147 -0
- package/dist/stateTrust.d.ts +37 -0
- package/dist/stateTrust.js +277 -0
- package/dist/toolInvocations.d.ts +47 -18
- package/dist/toolInvocations.js +200 -48
- package/package.json +1 -1
- package/samples/anthropic-usage.csv +4 -4
- package/samples/openai-usage.csv +7 -7
package/dist/sourceRegistry.js
CHANGED
|
@@ -3,31 +3,31 @@ export const ingestionLanes = [
|
|
|
3
3
|
id: "local_files_exports",
|
|
4
4
|
label: "Local files and provider exports",
|
|
5
5
|
sourceTypes: ["local_folder", "provider_export"],
|
|
6
|
-
|
|
6
|
+
defaultFinancialEvidence: "estimated"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
id: "provider_apis",
|
|
10
10
|
label: "Official provider APIs",
|
|
11
11
|
sourceTypes: ["provider_api"],
|
|
12
|
-
|
|
12
|
+
defaultFinancialEvidence: "verified"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
id: "browser_account_ui",
|
|
16
16
|
label: "Browser Account UI",
|
|
17
17
|
sourceTypes: ["browser_account"],
|
|
18
|
-
|
|
18
|
+
defaultFinancialEvidence: "verified"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
id: "local_cli_tool_detection",
|
|
22
22
|
label: "Local CLI/tool detection path",
|
|
23
23
|
sourceTypes: ["local_tool_detection"],
|
|
24
|
-
|
|
24
|
+
defaultFinancialEvidence: "detected_unverified"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
id: "mcp_internal_systems",
|
|
28
28
|
label: "MCP and internal systems",
|
|
29
29
|
sourceTypes: ["mcp_tool", "internal_system"],
|
|
30
|
-
|
|
30
|
+
defaultFinancialEvidence: "verified"
|
|
31
31
|
}
|
|
32
32
|
];
|
|
33
33
|
export const supportedSourceTypes = ingestionLanes.flatMap((lane) => lane.sourceTypes);
|
|
@@ -195,7 +195,9 @@ export function createLocalFolderSourceRegistry(rootPath, now = new Date()) {
|
|
|
195
195
|
scope: "Read-only scan of the explicit --path root. No writes outside .ai-spend-agent. No cloud upload.",
|
|
196
196
|
lane: "local_files_exports",
|
|
197
197
|
accessMethod: "file",
|
|
198
|
-
|
|
198
|
+
boundaryApproval: "approved",
|
|
199
|
+
validationCoverage: "untested",
|
|
200
|
+
financialEvidence: "missing",
|
|
199
201
|
fieldsVerified: ["approved local folder boundary"],
|
|
200
202
|
fieldsEstimated: [],
|
|
201
203
|
fieldsMissing: ["provider account billing data"]
|
|
@@ -209,14 +211,24 @@ export function createLocalFolderSourceRegistry(rootPath, now = new Date()) {
|
|
|
209
211
|
}
|
|
210
212
|
export function addApprovedSource(registry, source, now = new Date()) {
|
|
211
213
|
const timestamp = now.toISOString();
|
|
214
|
+
const canonicalRegistry = normalizeSourceRegistry(registry);
|
|
215
|
+
if (source.readOnly === false) {
|
|
216
|
+
throw new Error("Approved source boundaries must remain read-only.");
|
|
217
|
+
}
|
|
212
218
|
const nextSource = {
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
id: source.id,
|
|
220
|
+
type: source.type,
|
|
221
|
+
label: source.label,
|
|
222
|
+
...(source.path ? { path: source.path } : {}),
|
|
223
|
+
...(source.provider ? { provider: source.provider } : {}),
|
|
224
|
+
readOnly: true,
|
|
215
225
|
approvedAt: timestamp,
|
|
216
226
|
scope: source.scope ?? defaultScopeForSource(source.type),
|
|
217
227
|
lane: source.lane ?? laneForSourceType(source.type),
|
|
218
228
|
accessMethod: source.accessMethod ?? accessMethodForSourceType(source.type),
|
|
219
|
-
|
|
229
|
+
boundaryApproval: source.boundaryApproval ?? "approved",
|
|
230
|
+
validationCoverage: source.validationCoverage ?? "untested",
|
|
231
|
+
financialEvidence: source.type === "local_folder" ? "missing" : (source.financialEvidence ?? "missing"),
|
|
220
232
|
fieldsVerified: source.fieldsVerified ?? [],
|
|
221
233
|
fieldsEstimated: source.fieldsEstimated ?? [],
|
|
222
234
|
fieldsMissing: source.fieldsMissing ?? [],
|
|
@@ -225,11 +237,11 @@ export function addApprovedSource(registry, source, now = new Date()) {
|
|
|
225
237
|
tokenStorage: source.tokenStorage,
|
|
226
238
|
authReference: source.authReference
|
|
227
239
|
};
|
|
228
|
-
const withoutExisting =
|
|
240
|
+
const withoutExisting = canonicalRegistry.approvedSources.filter((candidate) => candidate.id !== nextSource.id);
|
|
229
241
|
return {
|
|
230
|
-
...
|
|
231
|
-
ingestionLanes:
|
|
232
|
-
supportedSourceTypes:
|
|
242
|
+
...canonicalRegistry,
|
|
243
|
+
ingestionLanes: canonicalRegistry.ingestionLanes ?? ingestionLanes,
|
|
244
|
+
supportedSourceTypes: canonicalRegistry.supportedSourceTypes ?? supportedSourceTypes,
|
|
233
245
|
approvedSources: [...withoutExisting, nextSource],
|
|
234
246
|
updatedAt: timestamp
|
|
235
247
|
};
|
|
@@ -248,7 +260,11 @@ export function createProviderConnectorStub(provider, type = providerCatalog.fin
|
|
|
248
260
|
scope: defaultScopeForSource(type),
|
|
249
261
|
lane: laneForSourceType(type),
|
|
250
262
|
accessMethod: accessMethodForSourceType(type, catalogEntry),
|
|
251
|
-
|
|
263
|
+
boundaryApproval: "approved",
|
|
264
|
+
// Registering a read-only boundary is not evidence that a connector ran.
|
|
265
|
+
// A successful provider result promotes this axis explicitly.
|
|
266
|
+
validationCoverage: "untested",
|
|
267
|
+
financialEvidence: "missing",
|
|
252
268
|
fieldsVerified: catalogEntry?.verifiedFields ?? [],
|
|
253
269
|
fieldsEstimated: [],
|
|
254
270
|
fieldsMissing: catalogEntry?.missingFields ?? ["approved account/API/export source"],
|
|
@@ -257,6 +273,157 @@ export function createProviderConnectorStub(provider, type = providerCatalog.fin
|
|
|
257
273
|
tokenStorage: tokenStorageForConnectorType(type, connectorEntry)
|
|
258
274
|
};
|
|
259
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Read a persisted source registry into the canonical three-axis contract.
|
|
278
|
+
*
|
|
279
|
+
* Version 1 registries used `verification` for several unrelated meanings.
|
|
280
|
+
* It is accepted here only as a migration input for financial evidence and is
|
|
281
|
+
* deliberately omitted from the returned object. A local-folder approval is
|
|
282
|
+
* permission metadata, so even a legacy `verification: "verified"` migrates
|
|
283
|
+
* to `financialEvidence: "missing"`.
|
|
284
|
+
*/
|
|
285
|
+
export function normalizeSourceRegistry(value) {
|
|
286
|
+
if (!isObject(value) || value.version !== 1 || value.localOnly !== true || value.cloudUpload !== false) {
|
|
287
|
+
throw new Error("Invalid local source registry: expected the canonical local-only registry shape.");
|
|
288
|
+
}
|
|
289
|
+
if (!Array.isArray(value.approvedSources) ||
|
|
290
|
+
!Array.isArray(value.deniedGlobs) ||
|
|
291
|
+
!Array.isArray(value.ingestionLanes) ||
|
|
292
|
+
!Array.isArray(value.supportedSourceTypes) ||
|
|
293
|
+
!isValidIso(value.updatedAt) ||
|
|
294
|
+
!isStringList(value.deniedGlobs) ||
|
|
295
|
+
!value.supportedSourceTypes.every(isSourceTypeValue)) {
|
|
296
|
+
throw new Error("Invalid local source registry: expected valid source, lane, and deny lists.");
|
|
297
|
+
}
|
|
298
|
+
const normalizedLanes = value.ingestionLanes.map(normalizeIngestionLane);
|
|
299
|
+
const normalizedSources = value.approvedSources.map(normalizeApprovedSource);
|
|
300
|
+
return {
|
|
301
|
+
version: 1,
|
|
302
|
+
localOnly: true,
|
|
303
|
+
cloudUpload: false,
|
|
304
|
+
approvedSources: normalizedSources,
|
|
305
|
+
deniedGlobs: [...value.deniedGlobs],
|
|
306
|
+
ingestionLanes: normalizedLanes,
|
|
307
|
+
supportedSourceTypes: [...value.supportedSourceTypes],
|
|
308
|
+
updatedAt: value.updatedAt
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function normalizeIngestionLane(value) {
|
|
312
|
+
if (!isObject(value) ||
|
|
313
|
+
!isIngestionLaneId(value.id) ||
|
|
314
|
+
!isNonEmptyString(value.label) ||
|
|
315
|
+
!Array.isArray(value.sourceTypes) ||
|
|
316
|
+
!value.sourceTypes.every(isSourceTypeValue)) {
|
|
317
|
+
throw new Error("Invalid local source registry: an ingestion lane has a malformed shape.");
|
|
318
|
+
}
|
|
319
|
+
if (value.defaultFinancialEvidence !== undefined && !isFinancialEvidence(value.defaultFinancialEvidence)) {
|
|
320
|
+
throw new Error("Invalid local source registry: an ingestion lane has invalid financial evidence.");
|
|
321
|
+
}
|
|
322
|
+
if (value.defaultVerification !== undefined && !isFinancialEvidence(value.defaultVerification)) {
|
|
323
|
+
throw new Error("Invalid local source registry: an ingestion lane has an invalid legacy verification value.");
|
|
324
|
+
}
|
|
325
|
+
const financialEvidence = isFinancialEvidence(value.defaultFinancialEvidence)
|
|
326
|
+
? value.defaultFinancialEvidence
|
|
327
|
+
: isFinancialEvidence(value.defaultVerification)
|
|
328
|
+
? value.defaultVerification
|
|
329
|
+
: undefined;
|
|
330
|
+
if (!financialEvidence) {
|
|
331
|
+
throw new Error("Invalid local source registry: an ingestion lane is missing its financial-evidence default.");
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
id: value.id,
|
|
335
|
+
label: value.label,
|
|
336
|
+
sourceTypes: [...value.sourceTypes],
|
|
337
|
+
defaultFinancialEvidence: financialEvidence
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function normalizeApprovedSource(value) {
|
|
341
|
+
if (!isObject(value) ||
|
|
342
|
+
!isNonEmptyString(value.id) ||
|
|
343
|
+
!isSourceTypeValue(value.type) ||
|
|
344
|
+
!isNonEmptyString(value.label) ||
|
|
345
|
+
value.readOnly !== true ||
|
|
346
|
+
!isValidIso(value.approvedAt) ||
|
|
347
|
+
!isNonEmptyString(value.scope) ||
|
|
348
|
+
!isIngestionLaneId(value.lane) ||
|
|
349
|
+
!isAccessMethod(value.accessMethod) ||
|
|
350
|
+
!isStringList(value.fieldsVerified) ||
|
|
351
|
+
!isStringList(value.fieldsEstimated) ||
|
|
352
|
+
!isStringList(value.fieldsMissing)) {
|
|
353
|
+
throw new Error("Invalid local source registry: an approved source has a malformed shape.");
|
|
354
|
+
}
|
|
355
|
+
if (value.boundaryApproval !== undefined && value.boundaryApproval !== "approved") {
|
|
356
|
+
throw new Error("Invalid local source registry: an approved source has an invalid boundary approval.");
|
|
357
|
+
}
|
|
358
|
+
if (value.validationCoverage !== undefined && !isValidationCoverage(value.validationCoverage)) {
|
|
359
|
+
throw new Error("Invalid local source registry: an approved source has an invalid validation coverage.");
|
|
360
|
+
}
|
|
361
|
+
if (value.financialEvidence !== undefined && !isFinancialEvidence(value.financialEvidence)) {
|
|
362
|
+
throw new Error("Invalid local source registry: an approved source has invalid financial evidence.");
|
|
363
|
+
}
|
|
364
|
+
if (value.verification !== undefined && !isFinancialEvidence(value.verification)) {
|
|
365
|
+
throw new Error("Invalid local source registry: an approved source has an invalid legacy verification value.");
|
|
366
|
+
}
|
|
367
|
+
const provider = optionalNonEmptyString(value.provider, "provider");
|
|
368
|
+
const path = optionalNonEmptyString(value.path, "path");
|
|
369
|
+
const authReference = optionalNonEmptyString(value.authReference, "auth reference");
|
|
370
|
+
const authMode = optionalEnum(value.authMode, isAuthMode, "auth mode");
|
|
371
|
+
const tokenStorage = optionalEnum(value.tokenStorage, isTokenStorage, "token storage");
|
|
372
|
+
if (value.authScopes !== undefined && !isStringList(value.authScopes)) {
|
|
373
|
+
throw new Error("Invalid local source registry: an approved source has invalid auth scopes.");
|
|
374
|
+
}
|
|
375
|
+
const migratedEvidence = isFinancialEvidence(value.financialEvidence)
|
|
376
|
+
? value.financialEvidence
|
|
377
|
+
: isFinancialEvidence(value.verification)
|
|
378
|
+
? value.verification
|
|
379
|
+
: "missing";
|
|
380
|
+
return {
|
|
381
|
+
id: value.id,
|
|
382
|
+
type: value.type,
|
|
383
|
+
label: value.label,
|
|
384
|
+
...(path ? { path } : {}),
|
|
385
|
+
...(provider ? { provider } : {}),
|
|
386
|
+
readOnly: true,
|
|
387
|
+
approvedAt: value.approvedAt,
|
|
388
|
+
scope: value.scope,
|
|
389
|
+
lane: value.lane,
|
|
390
|
+
accessMethod: value.accessMethod,
|
|
391
|
+
boundaryApproval: "approved",
|
|
392
|
+
validationCoverage: isValidationCoverage(value.validationCoverage)
|
|
393
|
+
? value.validationCoverage
|
|
394
|
+
: "untested",
|
|
395
|
+
financialEvidence: value.type === "local_folder" ? "missing" : migratedEvidence,
|
|
396
|
+
fieldsVerified: [...value.fieldsVerified],
|
|
397
|
+
fieldsEstimated: [...value.fieldsEstimated],
|
|
398
|
+
fieldsMissing: [...value.fieldsMissing],
|
|
399
|
+
...(authMode ? { authMode } : {}),
|
|
400
|
+
...(value.authScopes ? { authScopes: [...value.authScopes] } : {}),
|
|
401
|
+
...(tokenStorage ? { tokenStorage } : {}),
|
|
402
|
+
...(authReference ? { authReference } : {})
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Persisted source registries are repository-controlled configuration. Until
|
|
407
|
+
* an external provider-sync receipt binds their exact bytes, keep only the
|
|
408
|
+
* approved read-only boundary and remove any self-asserted validation or
|
|
409
|
+
* financial-evidence claims.
|
|
410
|
+
*/
|
|
411
|
+
export function downgradeUntrustedSourceRegistryClaims(registry) {
|
|
412
|
+
const canonical = normalizeSourceRegistry(registry);
|
|
413
|
+
return {
|
|
414
|
+
...canonical,
|
|
415
|
+
approvedSources: canonical.approvedSources.map((source) => ({
|
|
416
|
+
...source,
|
|
417
|
+
validationCoverage: "untested",
|
|
418
|
+
financialEvidence: "missing",
|
|
419
|
+
fieldsVerified: source.fieldsVerified.filter((field) => /approved|boundary|read-only|folder/i.test(field)),
|
|
420
|
+
fieldsMissing: Array.from(new Set([
|
|
421
|
+
...source.fieldsMissing,
|
|
422
|
+
"machine-bound provider validation and financial evidence"
|
|
423
|
+
]))
|
|
424
|
+
}))
|
|
425
|
+
};
|
|
426
|
+
}
|
|
260
427
|
export function buildMissingSourcePrompts(signals, registry) {
|
|
261
428
|
const providerSignals = new Map();
|
|
262
429
|
for (const signal of signals) {
|
|
@@ -267,7 +434,7 @@ export function buildMissingSourcePrompts(signals, registry) {
|
|
|
267
434
|
}
|
|
268
435
|
const prompts = [];
|
|
269
436
|
for (const [provider, detectedSignals] of Array.from(providerSignals.entries())) {
|
|
270
|
-
if (
|
|
437
|
+
if (hasCurrentProviderFinancialEvidence(registry, provider)) {
|
|
271
438
|
continue;
|
|
272
439
|
}
|
|
273
440
|
const catalogEntry = providerCatalog.find((entry) => entry.id === provider);
|
|
@@ -276,7 +443,7 @@ export function buildMissingSourcePrompts(signals, registry) {
|
|
|
276
443
|
prompts.push({
|
|
277
444
|
provider,
|
|
278
445
|
status: "detected_unverified",
|
|
279
|
-
reason: `${provider} was detected locally, but no
|
|
446
|
+
reason: `${provider} was detected locally, but no approved provider/API/browser/export boundary has current financial evidence. Connector validation is reported separately.`,
|
|
280
447
|
detectedEvidence: detectedSignals.map((signal) => signal.evidence),
|
|
281
448
|
suggestedConnector: `connect ${provider} --type ${preferredType}`,
|
|
282
449
|
suggestedSourceTypes
|
|
@@ -315,17 +482,26 @@ export function slugifySourceId(label) {
|
|
|
315
482
|
.replace(/^-+|-+$/g, "");
|
|
316
483
|
return slug || "approved-source";
|
|
317
484
|
}
|
|
318
|
-
function
|
|
485
|
+
function hasCurrentProviderFinancialEvidence(registry, provider) {
|
|
319
486
|
return registry.approvedSources.some((source) => {
|
|
320
487
|
if (source.provider !== provider) {
|
|
321
488
|
return false;
|
|
322
489
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
490
|
+
return source.boundaryApproval === "approved" &&
|
|
491
|
+
source.validationCoverage !== "failed" &&
|
|
492
|
+
source.financialEvidence !== "missing" &&
|
|
493
|
+
source.type !== "local_tool_detection";
|
|
327
494
|
});
|
|
328
495
|
}
|
|
496
|
+
function validationCoverageForSource(provider, type) {
|
|
497
|
+
if (type === "provider_api" && (provider === "openai" || provider === "anthropic"))
|
|
498
|
+
return "live_verified";
|
|
499
|
+
if (type === "provider_api" && (provider === "cursor" || provider === "github-copilot" || provider === "copilot"))
|
|
500
|
+
return "fixture_verified";
|
|
501
|
+
if (provider === "local-agent-logs" && type === "local_tool_detection")
|
|
502
|
+
return "live_verified";
|
|
503
|
+
return "untested";
|
|
504
|
+
}
|
|
329
505
|
function authModeForConnectorType(type, connectorEntry) {
|
|
330
506
|
if (type === "browser_account")
|
|
331
507
|
return "browser_session";
|
|
@@ -377,4 +553,57 @@ function accessMethodForSourceType(type, catalogEntry) {
|
|
|
377
553
|
return "internal";
|
|
378
554
|
return "file";
|
|
379
555
|
}
|
|
556
|
+
function isObject(value) {
|
|
557
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
558
|
+
}
|
|
559
|
+
function isNonEmptyString(value) {
|
|
560
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
561
|
+
}
|
|
562
|
+
function isStringList(value) {
|
|
563
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
564
|
+
}
|
|
565
|
+
function isValidIso(value) {
|
|
566
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
567
|
+
}
|
|
568
|
+
function isSourceTypeValue(value) {
|
|
569
|
+
return supportedSourceTypes.includes(value);
|
|
570
|
+
}
|
|
571
|
+
function isIngestionLaneId(value) {
|
|
572
|
+
return value === "local_files_exports" ||
|
|
573
|
+
value === "provider_apis" ||
|
|
574
|
+
value === "browser_account_ui" ||
|
|
575
|
+
value === "local_cli_tool_detection" ||
|
|
576
|
+
value === "mcp_internal_systems";
|
|
577
|
+
}
|
|
578
|
+
function isAccessMethod(value) {
|
|
579
|
+
return value === "file" || value === "api" || value === "browser" || value === "cli_detection" || value === "mcp" || value === "internal" || value === "manual";
|
|
580
|
+
}
|
|
581
|
+
function isFinancialEvidence(value) {
|
|
582
|
+
return value === "verified" || value === "estimated" || value === "detected_unverified" || value === "missing";
|
|
583
|
+
}
|
|
584
|
+
function isValidationCoverage(value) {
|
|
585
|
+
return value === "live_verified" || value === "fixture_verified" || value === "untested" || value === "failed";
|
|
586
|
+
}
|
|
587
|
+
function isAuthMode(value) {
|
|
588
|
+
return value === "oauth" || value === "api_token_ref" || value === "browser_session" || value === "mcp_auth" || value === "manual_export" || value === "none";
|
|
589
|
+
}
|
|
590
|
+
function isTokenStorage(value) {
|
|
591
|
+
return value === "local_reference_only" || value === "keychain_reference" || value === "none";
|
|
592
|
+
}
|
|
593
|
+
function optionalNonEmptyString(value, label) {
|
|
594
|
+
if (value === undefined)
|
|
595
|
+
return undefined;
|
|
596
|
+
if (!isNonEmptyString(value)) {
|
|
597
|
+
throw new Error(`Invalid local source registry: an approved source has an invalid ${label}.`);
|
|
598
|
+
}
|
|
599
|
+
return value;
|
|
600
|
+
}
|
|
601
|
+
function optionalEnum(value, predicate, label) {
|
|
602
|
+
if (value === undefined)
|
|
603
|
+
return undefined;
|
|
604
|
+
if (!predicate(value)) {
|
|
605
|
+
throw new Error(`Invalid local source registry: an approved source has an invalid ${label}.`);
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
380
609
|
//# sourceMappingURL=sourceRegistry.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CostConfidence, UsageRecord } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* How thoroughly an ingestion path itself has been exercised.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately separate from financial evidence quality. A connector
|
|
6
|
+
* can be live-verified while a particular record is still only an estimate.
|
|
7
|
+
*/
|
|
8
|
+
export declare const sourceValidationCoverageValues: readonly ["live_verified", "fixture_verified", "untested", "failed"];
|
|
9
|
+
export type SourceValidationCoverage = typeof sourceValidationCoverageValues[number];
|
|
10
|
+
/** The existing aibill financial-evidence vocabulary, named for this contract. */
|
|
11
|
+
export type FinancialEvidenceStatus = CostConfidence;
|
|
12
|
+
export declare const sourceFreshnessStatusValues: readonly ["fresh", "stale", "not_checked"];
|
|
13
|
+
export type SourceFreshnessStatus = typeof sourceFreshnessStatusValues[number];
|
|
14
|
+
export type SourceStatusId = "claude-code" | "codex" | "openai" | "anthropic" | "cursor" | "github-copilot";
|
|
15
|
+
export type SourceStatusDefinition = {
|
|
16
|
+
id: SourceStatusId;
|
|
17
|
+
label: string;
|
|
18
|
+
validationCoverage: SourceValidationCoverage;
|
|
19
|
+
validationNote: string;
|
|
20
|
+
staleAfterHours: number;
|
|
21
|
+
};
|
|
22
|
+
export type SourceStatusObservation = {
|
|
23
|
+
id: SourceStatusId;
|
|
24
|
+
financialEvidence: FinancialEvidenceStatus;
|
|
25
|
+
financialEvidenceNote: string;
|
|
26
|
+
/** When this source was last read or synced, even if it returned no records. */
|
|
27
|
+
checkedAt?: string;
|
|
28
|
+
/** Timestamp of the newest evidence row, which may precede the sync time. */
|
|
29
|
+
latestEvidenceAt?: string;
|
|
30
|
+
/** A sanitized error from the latest recorded attempt, if one exists. */
|
|
31
|
+
lastError?: string;
|
|
32
|
+
/** Runtime failures may override the shipped validation baseline. */
|
|
33
|
+
validationCoverage?: SourceValidationCoverage;
|
|
34
|
+
};
|
|
35
|
+
export type SourceStatusFreshness = {
|
|
36
|
+
status: SourceFreshnessStatus;
|
|
37
|
+
checkedAt?: string;
|
|
38
|
+
latestEvidenceAt?: string;
|
|
39
|
+
staleAfterHours: number;
|
|
40
|
+
};
|
|
41
|
+
export type SourceStatus = {
|
|
42
|
+
id: SourceStatusId;
|
|
43
|
+
label: string;
|
|
44
|
+
validationCoverage: SourceValidationCoverage;
|
|
45
|
+
validationNote: string;
|
|
46
|
+
financialEvidence: FinancialEvidenceStatus;
|
|
47
|
+
financialEvidenceNote: string;
|
|
48
|
+
freshness: SourceStatusFreshness;
|
|
49
|
+
lastError?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Shipped validation matrix. Keep these claims proof-conservative: a live
|
|
53
|
+
* authentication check is not the same as a non-empty end-to-end billing
|
|
54
|
+
* reconciliation.
|
|
55
|
+
*/
|
|
56
|
+
export declare const sourceStatusDefinitions: readonly SourceStatusDefinition[];
|
|
57
|
+
export declare function buildSourceStatuses(observations?: readonly SourceStatusObservation[], now?: Date): SourceStatus[];
|
|
58
|
+
/**
|
|
59
|
+
* Reduce one source's current rows to the evidence label used for its headline.
|
|
60
|
+
* Verified billed cost wins; otherwise estimates win over an unpriced signal.
|
|
61
|
+
*/
|
|
62
|
+
export declare function financialEvidenceForRecords(records: readonly UsageRecord[]): FinancialEvidenceStatus;
|
|
63
|
+
/** Stable, plain-text formatter shared by terminal surfaces. */
|
|
64
|
+
export declare function formatSourceStatuses(statuses: readonly SourceStatus[]): string;
|
|
65
|
+
//# sourceMappingURL=sourceStatus.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How thoroughly an ingestion path itself has been exercised.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately separate from financial evidence quality. A connector
|
|
5
|
+
* can be live-verified while a particular record is still only an estimate.
|
|
6
|
+
*/
|
|
7
|
+
export const sourceValidationCoverageValues = [
|
|
8
|
+
"live_verified",
|
|
9
|
+
"fixture_verified",
|
|
10
|
+
"untested",
|
|
11
|
+
"failed"
|
|
12
|
+
];
|
|
13
|
+
export const sourceFreshnessStatusValues = ["fresh", "stale", "not_checked"];
|
|
14
|
+
/**
|
|
15
|
+
* Shipped validation matrix. Keep these claims proof-conservative: a live
|
|
16
|
+
* authentication check is not the same as a non-empty end-to-end billing
|
|
17
|
+
* reconciliation.
|
|
18
|
+
*/
|
|
19
|
+
export const sourceStatusDefinitions = [
|
|
20
|
+
{
|
|
21
|
+
id: "claude-code",
|
|
22
|
+
label: "Claude Code local logs",
|
|
23
|
+
validationCoverage: "live_verified",
|
|
24
|
+
validationNote: "Local transcript parsing is exercised against live logs; dollar values remain API-rate estimates.",
|
|
25
|
+
staleAfterHours: 72
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "codex",
|
|
29
|
+
label: "Codex local logs",
|
|
30
|
+
validationCoverage: "live_verified",
|
|
31
|
+
validationNote: "Local parsing was replayed against live logs; total-only token shapes and unknown aliases remain missing rather than becoming estimated $0.",
|
|
32
|
+
staleAfterHours: 72
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "openai",
|
|
36
|
+
label: "OpenAI Costs and Usage API",
|
|
37
|
+
validationCoverage: "live_verified",
|
|
38
|
+
validationNote: "Product connector QA exercised non-empty Admin cost and usage API paths; the tested Costs total reconciled to invoiced API credits less the provider-UI balance with $0.00 variance. This does not reconcile the current user's account; final invoices, tax, discounts, and later adjustments remain separate.",
|
|
39
|
+
staleAfterHours: 48
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "anthropic",
|
|
43
|
+
label: "Anthropic Cost Report and Claude Code Analytics",
|
|
44
|
+
validationCoverage: "live_verified",
|
|
45
|
+
validationNote: "Admin cost and Claude Code usage paths are exercised with non-empty live records.",
|
|
46
|
+
staleAfterHours: 48
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "cursor",
|
|
50
|
+
label: "Cursor Admin API",
|
|
51
|
+
validationCoverage: "fixture_verified",
|
|
52
|
+
validationNote: "Canonical teamMemberSpend envelopes, totalPages pagination, completeness failures, and malformed responses pass recorded fixtures; live account QA is pending.",
|
|
53
|
+
staleAfterHours: 48
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "github-copilot",
|
|
57
|
+
label: "GitHub Copilot organization APIs",
|
|
58
|
+
validationCoverage: "fixture_verified",
|
|
59
|
+
validationNote: "The 2026-03-10 signed-NDJSON metrics workflow, seat pagination, per-seat plan types, and failure paths pass recorded fixtures; live account QA is pending.",
|
|
60
|
+
staleAfterHours: 48
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
export function buildSourceStatuses(observations = [], now = new Date()) {
|
|
64
|
+
const byId = new Map(observations.map((observation) => [observation.id, observation]));
|
|
65
|
+
return sourceStatusDefinitions.map((definition) => {
|
|
66
|
+
const observation = byId.get(definition.id);
|
|
67
|
+
return {
|
|
68
|
+
id: definition.id,
|
|
69
|
+
label: definition.label,
|
|
70
|
+
validationCoverage: observation?.validationCoverage ?? definition.validationCoverage,
|
|
71
|
+
validationNote: definition.validationNote,
|
|
72
|
+
financialEvidence: observation?.financialEvidence ?? "missing",
|
|
73
|
+
financialEvidenceNote: observation?.financialEvidenceNote ?? "No current financial evidence was observed on this machine.",
|
|
74
|
+
freshness: sourceFreshness(definition, observation, now),
|
|
75
|
+
...(observation?.lastError ? { lastError: observation.lastError } : {})
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reduce one source's current rows to the evidence label used for its headline.
|
|
81
|
+
* Verified billed cost wins; otherwise estimates win over an unpriced signal.
|
|
82
|
+
*/
|
|
83
|
+
export function financialEvidenceForRecords(records) {
|
|
84
|
+
if (records.some((record) => record.costConfidence === "verified" && typeof record.amountUsd === "number")) {
|
|
85
|
+
return "verified";
|
|
86
|
+
}
|
|
87
|
+
if (records.some((record) => record.costConfidence === "estimated" && typeof record.amountUsd === "number")) {
|
|
88
|
+
return "estimated";
|
|
89
|
+
}
|
|
90
|
+
if (records.some((record) => record.costConfidence === "detected_unverified")) {
|
|
91
|
+
return "detected_unverified";
|
|
92
|
+
}
|
|
93
|
+
return "missing";
|
|
94
|
+
}
|
|
95
|
+
/** Stable, plain-text formatter shared by terminal surfaces. */
|
|
96
|
+
export function formatSourceStatuses(statuses) {
|
|
97
|
+
return statuses.map((status) => {
|
|
98
|
+
const freshness = formatFreshness(status.freshness);
|
|
99
|
+
return [
|
|
100
|
+
`${status.label} (${status.id})`,
|
|
101
|
+
` validation coverage: ${status.validationCoverage}`,
|
|
102
|
+
` financial evidence: ${status.financialEvidence}`,
|
|
103
|
+
` freshness: ${freshness}`,
|
|
104
|
+
` last error: ${status.lastError ? singleLineStatusText(status.lastError) : "none recorded"}`,
|
|
105
|
+
` validation proof: ${status.validationNote}`,
|
|
106
|
+
` evidence note: ${status.financialEvidenceNote}`
|
|
107
|
+
].join("\n");
|
|
108
|
+
}).join("\n\n");
|
|
109
|
+
}
|
|
110
|
+
function sourceFreshness(definition, observation, now) {
|
|
111
|
+
const checkedAt = validIso(observation?.checkedAt);
|
|
112
|
+
const latestEvidenceAt = validIso(observation?.latestEvidenceAt);
|
|
113
|
+
if (!checkedAt) {
|
|
114
|
+
return {
|
|
115
|
+
status: "not_checked",
|
|
116
|
+
staleAfterHours: definition.staleAfterHours,
|
|
117
|
+
...(latestEvidenceAt ? { latestEvidenceAt } : {})
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const ageHours = (now.getTime() - Date.parse(checkedAt)) / 3_600_000;
|
|
121
|
+
return {
|
|
122
|
+
status: ageHours > definition.staleAfterHours ? "stale" : "fresh",
|
|
123
|
+
checkedAt,
|
|
124
|
+
...(latestEvidenceAt ? { latestEvidenceAt } : {}),
|
|
125
|
+
staleAfterHours: definition.staleAfterHours
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function validIso(value) {
|
|
129
|
+
return value && Number.isFinite(Date.parse(value)) ? value : undefined;
|
|
130
|
+
}
|
|
131
|
+
function formatFreshness(freshness) {
|
|
132
|
+
if (freshness.status === "not_checked")
|
|
133
|
+
return "not_checked (no local check recorded)";
|
|
134
|
+
const latest = freshness.latestEvidenceAt
|
|
135
|
+
? `; latest evidence ${freshness.latestEvidenceAt}`
|
|
136
|
+
: "; no evidence rows observed";
|
|
137
|
+
return `${freshness.status} (checked ${freshness.checkedAt}${latest}; stale after ${freshness.staleAfterHours}h)`;
|
|
138
|
+
}
|
|
139
|
+
function singleLineStatusText(value) {
|
|
140
|
+
return value
|
|
141
|
+
.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
142
|
+
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
143
|
+
.replace(/\s+/g, " ")
|
|
144
|
+
.trim()
|
|
145
|
+
.slice(0, 500) || "invalid empty error";
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=sourceStatus.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A connected-provider spend file lives inside a repository and is therefore
|
|
3
|
+
* untrusted by default. A successful provider sync writes this small receipt
|
|
4
|
+
* outside the repository, binding the exact spend.json bytes to this machine
|
|
5
|
+
* and canonical project root. Successful syncs also bind sources.json so a
|
|
6
|
+
* clone cannot forge validation or financial-evidence axes. Merely cloning
|
|
7
|
+
* committed state cannot create the external receipt.
|
|
8
|
+
*/
|
|
9
|
+
export type ConnectedSpendTrustResult = {
|
|
10
|
+
trusted: true;
|
|
11
|
+
trustedAt: string;
|
|
12
|
+
spendSha256: string;
|
|
13
|
+
sourceRegistrySha256?: string;
|
|
14
|
+
} | {
|
|
15
|
+
trusted: false;
|
|
16
|
+
reason: "missing" | "mismatch" | "invalid";
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export type ConnectedSpendTrustOptions = {
|
|
20
|
+
/** Test/embedding override. Production callers normally use user state. */
|
|
21
|
+
trustDirectory?: string;
|
|
22
|
+
/** Exact sources.json bytes written by the same successful provider sync. */
|
|
23
|
+
sourceRegistryContents?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function writeConnectedSpendTrustReceipt(rootPath: string, exactSpendContents: string, options?: ConnectedSpendTrustOptions): Promise<void>;
|
|
26
|
+
export declare function verifyConnectedSpendTrustReceipt(rootPath: string, exactSpendContents: string, options?: ConnectedSpendTrustOptions): Promise<ConnectedSpendTrustResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Provider/source truth axes are trusted only when the same external receipt
|
|
29
|
+
* binds both the connected spend state and the exact persisted sources.json.
|
|
30
|
+
* A repository clone or edit can therefore register a boundary, but cannot
|
|
31
|
+
* self-assert live validation or verified financial evidence.
|
|
32
|
+
*/
|
|
33
|
+
export declare function verifyConnectedSourceRegistryTrustReceipt(rootPath: string, exactSpendContents: string, exactSourceRegistryContents: string, options?: ConnectedSpendTrustOptions): Promise<ConnectedSpendTrustResult>;
|
|
34
|
+
/** Remove stale trust whenever connected state is reset or replaced locally. */
|
|
35
|
+
export declare function invalidateConnectedSpendTrustReceipt(rootPath: string, options?: ConnectedSpendTrustOptions): Promise<void>;
|
|
36
|
+
export declare function connectedTrustFailureMessage(reason: string): string;
|
|
37
|
+
//# sourceMappingURL=stateTrust.d.ts.map
|