@akasecurity/ai-tc-claude-code 0.9.9 → 0.9.10
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/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/hooks/hooks.json +1 -1
- package/package.json +5 -5
- package/scripts/apply-suppressions.js +1200 -671
- package/scripts/backfill.js +2299 -1434
- package/scripts/filescan.js +2053 -1249
- package/scripts/firstrun.js +1961 -1188
- package/scripts/history-sync.js +1882 -1029
- package/scripts/intro.js +363 -238
- package/scripts/message-display.js +1212 -662
- package/scripts/onboard.js +1598 -1066
- package/scripts/post-model-switch.js +447 -311
- package/scripts/post-tool-use.js +2101 -1274
- package/scripts/pre-model-switch.js +1981 -1204
- package/scripts/pre-tool-use.js +2113 -1102
- package/scripts/query.js +1961 -1188
- package/scripts/reconcile.js +2082 -1242
- package/scripts/remediate.js +2152 -1279
- package/scripts/scan-worker.js +278 -176
- package/scripts/session-start.js +2050 -1277
- package/scripts/start-light.js +366 -241
- package/scripts/statusline.js +1961 -1188
- package/scripts/stop.js +448 -315
- package/scripts/sync.js +17422 -2018
- package/scripts/user-prompt-submit.js +2110 -1279
package/scripts/query.js
CHANGED
|
@@ -494,6 +494,7 @@ var require_ignore = __commonJS({
|
|
|
494
494
|
// ../../packages/persistence/src/attached-derived.ts
|
|
495
495
|
import { rmSync } from "fs";
|
|
496
496
|
import { join } from "path";
|
|
497
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
497
498
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
498
499
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
499
500
|
|
|
@@ -594,6 +595,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
594
595
|
{
|
|
595
596
|
tag: "0022_audit_inspection_ms",
|
|
596
597
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
tag: "0023_secret_vault_user_authorized",
|
|
601
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
605
|
+
sql: "DROP INDEX IF EXISTS `idx_finding_resolution_key`;--> statement-breakpoint\nCREATE INDEX `idx_finding_resolution_key_created` ON `finding_resolution` (`finding_key`,`created_at`);"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
609
|
+
sql: "ALTER TABLE `audit_events` ADD `source_tool` text GENERATED ALWAYS AS (json_extract(attributes, '$.source_tool')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `repo` text GENERATED ALWAYS AS (json_extract(attributes, '$.repo')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `file_path` text GENERATED ALWAYS AS (json_extract(attributes, '$.file_path')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `tool_name` text GENERATED ALWAYS AS (json_extract(attributes, '$.tool_name')) VIRTUAL;"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
613
|
+
sql: "ALTER TABLE `audit_events` ADD `service_tier` text GENERATED ALWAYS AS (json_extract(attributes, '$.service_tier')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_1h_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_1h_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `ephemeral_5m_input_tokens` integer GENERATED ALWAYS AS (json_extract(attributes, '$.ephemeral_5m_input_tokens')) VIRTUAL;--> statement-breakpoint\nALTER TABLE `audit_events` ADD `web_search_requests` integer GENERATED ALWAYS AS (json_extract(attributes, '$.web_search_requests')) VIRTUAL;"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
tag: "0027_audit_llm_usage_index",
|
|
617
|
+
sql: "CREATE INDEX `idx_audit_llm_usage` ON `audit_events` (`started_at`,`root_session_id`,`provider`,`model`,`service_tier`,`input_tokens`,`output_tokens`,`cache_creation_input_tokens`,`cache_read_input_tokens`,`ephemeral_1h_input_tokens`,`ephemeral_5m_input_tokens`,`web_search_requests`) WHERE event_type = 'llm_call' AND attributes IS NOT NULL;"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
tag: "0028_activity_session_probe_indexes",
|
|
621
|
+
sql: "CREATE INDEX `idx_audit_session_prompt` ON `audit_events` (`root_session_id`) WHERE event_type = 'prompt';--> statement-breakpoint\nCREATE INDEX `idx_audit_session_share` ON `audit_events` (`root_session_id`) WHERE event_type = 'share';--> statement-breakpoint\nCREATE INDEX `idx_audit_ended_at` ON `audit_events` (`ended_at`,`root_session_id`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\nCREATE INDEX `idx_audit_session_ended` ON `audit_events` (`root_session_id`,`ended_at`) WHERE ended_at IS NOT NULL;--> statement-breakpoint\n-- Expression index for the activity list's turns rollup: a live-captured\n-- session's turns are the DISTINCT `run_key` across its `llm_call` leaves, and\n-- carrying the extracted key in the index answers that count from the index\n-- alone instead of parsing every leaf's attribute bag. Written by hand, as\n-- 0013's `idx_audit_code_change_path` was: drizzle-kit cannot emit an\n-- expression containing a comma, so this index is not declared in sqlite.ts.\nCREATE INDEX `idx_audit_session_run_key` ON `audit_events` (`root_session_id`, json_extract(`attributes`, '$.run_key')) WHERE `event_type` = 'llm_call';\n"
|
|
597
622
|
}
|
|
598
623
|
];
|
|
599
624
|
|
|
@@ -22193,6 +22218,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22193
22218
|
AttachTokenExpired,
|
|
22194
22219
|
external_exports.object({ status: printable(64) })
|
|
22195
22220
|
]);
|
|
22221
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22222
|
+
var DeviceCommand = external_exports.object({
|
|
22223
|
+
id: printable(128).min(1),
|
|
22224
|
+
kind: DeviceCommandKind,
|
|
22225
|
+
issuedAt: printable(64).min(1),
|
|
22226
|
+
expiresAt: printable(64).min(1)
|
|
22227
|
+
}).strict();
|
|
22228
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22229
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22230
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22231
|
+
external_exports.object({
|
|
22232
|
+
outcome: external_exports.literal("reported"),
|
|
22233
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22234
|
+
}).strict(),
|
|
22235
|
+
external_exports.object({
|
|
22236
|
+
outcome: external_exports.literal("failed"),
|
|
22237
|
+
reason: DeviceCommandFailureReason,
|
|
22238
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22239
|
+
}).strict()
|
|
22240
|
+
]);
|
|
22196
22241
|
|
|
22197
22242
|
// ../../packages/schema/src/zod/registry.ts
|
|
22198
22243
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22359,7 +22404,7 @@ var PackManifest = external_exports.object({
|
|
|
22359
22404
|
}).meta({ id: "PackManifest" });
|
|
22360
22405
|
|
|
22361
22406
|
// ../../packages/schema/src/zod/detection.ts
|
|
22362
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22407
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22363
22408
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22364
22409
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22365
22410
|
var DetectionCounts = external_exports.object({
|
|
@@ -22496,14 +22541,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22496
22541
|
function isStringArray(value) {
|
|
22497
22542
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22498
22543
|
}
|
|
22544
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22545
|
+
function resolveOrigin(origin) {
|
|
22546
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22547
|
+
}
|
|
22499
22548
|
function summaryToDetectionListItem(s) {
|
|
22500
22549
|
return {
|
|
22501
22550
|
id: `${s.namespace}/${s.packId}`,
|
|
22502
22551
|
name: s.name,
|
|
22503
22552
|
version: s.version,
|
|
22504
22553
|
enabled: s.enabled,
|
|
22505
|
-
origin:
|
|
22506
|
-
// v1: every installed pack is library origin
|
|
22554
|
+
origin: resolveOrigin(s.origin),
|
|
22507
22555
|
namespace: s.namespace,
|
|
22508
22556
|
packId: s.packId,
|
|
22509
22557
|
ruleCount: s.ruleCount,
|
|
@@ -22555,7 +22603,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22555
22603
|
name: row.name,
|
|
22556
22604
|
version: row.version,
|
|
22557
22605
|
enabled: row.enabled,
|
|
22558
|
-
origin:
|
|
22606
|
+
origin: resolveOrigin(row.origin),
|
|
22559
22607
|
namespace: row.namespace,
|
|
22560
22608
|
packId: row.packId,
|
|
22561
22609
|
ruleCount: row.rules.length,
|
|
@@ -22575,16 +22623,20 @@ function splitDetectionId(id) {
|
|
|
22575
22623
|
}
|
|
22576
22624
|
function buildDetectionsList(summaries, query) {
|
|
22577
22625
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22626
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22578
22627
|
const counts = {
|
|
22579
22628
|
all: summaries.length,
|
|
22580
|
-
library: summaries.length,
|
|
22581
|
-
|
|
22582
|
-
|
|
22629
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22630
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22631
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22632
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22633
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22634
|
+
// OriginEnum.
|
|
22583
22635
|
customized: 0,
|
|
22584
22636
|
updates: withUpdate.length
|
|
22585
22637
|
};
|
|
22586
22638
|
const filter = query.filter;
|
|
22587
|
-
let filtered = filter === "custom"
|
|
22639
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22588
22640
|
if (query.q) {
|
|
22589
22641
|
const q = query.q.toLowerCase();
|
|
22590
22642
|
filtered = filtered.filter(
|
|
@@ -22664,8 +22716,9 @@ var Event = external_exports.object({
|
|
|
22664
22716
|
metadata: EventMetadata.optional()
|
|
22665
22717
|
}).meta({ id: "Event" });
|
|
22666
22718
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22719
|
+
var INGEST_BATCH_MAX = 100;
|
|
22667
22720
|
var IngestBatch = external_exports.object({
|
|
22668
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22721
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22669
22722
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22670
22723
|
// additionally rejects any event whose contentHash the store has already
|
|
22671
22724
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23221,372 +23274,11 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23221
23274
|
message: "At least one field must be provided"
|
|
23222
23275
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23223
23276
|
|
|
23224
|
-
// ../../packages/schema/src/zod/vault.ts
|
|
23225
|
-
var POINTER_FORMAT_VERSION = 2;
|
|
23226
|
-
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23227
|
-
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23228
|
-
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23229
|
-
);
|
|
23230
|
-
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23231
|
-
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23232
|
-
var ParsedPointer = external_exports.object({
|
|
23233
|
-
category: DetectionCategory,
|
|
23234
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23235
|
-
pointerId: external_exports.string(),
|
|
23236
|
-
tag: external_exports.string()
|
|
23237
|
-
});
|
|
23238
|
-
var VaultEntry = external_exports.object({
|
|
23239
|
-
pointerId: external_exports.string(),
|
|
23240
|
-
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23241
|
-
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23242
|
-
// independently of the vault encryption key below.
|
|
23243
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23244
|
-
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23245
|
-
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23246
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23247
|
-
// Fixed at first mint and never updated: the same value detected later under a
|
|
23248
|
-
// different rule's category keeps the category it was minted with, so one
|
|
23249
|
-
// value always produces exactly one wire token.
|
|
23250
|
-
category: DetectionCategory,
|
|
23251
|
-
ruleId: external_exports.string(),
|
|
23252
|
-
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23253
|
-
maskedMatch: external_exports.string(),
|
|
23254
|
-
provider: external_exports.string().optional(),
|
|
23255
|
-
ciphertext: external_exports.string(),
|
|
23256
|
-
nonce: external_exports.string(),
|
|
23257
|
-
authTag: external_exports.string(),
|
|
23258
|
-
// How many times this value has been detected on this machine — the reuse
|
|
23259
|
-
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23260
|
-
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23261
|
-
firstSeen: external_exports.string(),
|
|
23262
|
-
lastSeen: external_exports.string()
|
|
23263
|
-
});
|
|
23264
|
-
var PointerDescriptor = external_exports.object({
|
|
23265
|
-
category: DetectionCategory,
|
|
23266
|
-
provider: external_exports.string().optional(),
|
|
23267
|
-
maskedMatch: external_exports.string(),
|
|
23268
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23269
|
-
firstSeen: external_exports.string(),
|
|
23270
|
-
lastSeen: external_exports.string()
|
|
23271
|
-
});
|
|
23272
|
-
var PointerIdentity = external_exports.object({
|
|
23273
|
-
ruleId: external_exports.string(),
|
|
23274
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23275
|
-
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23276
|
-
});
|
|
23277
|
-
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23278
|
-
var VaultDerefReason = external_exports.enum([
|
|
23279
|
-
"display",
|
|
23280
|
-
"explicit-reveal",
|
|
23281
|
-
"view-render",
|
|
23282
|
-
"model-input",
|
|
23283
|
-
"remediation",
|
|
23284
|
-
"purge"
|
|
23285
|
-
]);
|
|
23286
|
-
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23287
|
-
var VaultDeref = external_exports.object({
|
|
23288
|
-
id: external_exports.guid(),
|
|
23289
|
-
pointerId: external_exports.string(),
|
|
23290
|
-
at: external_exports.string(),
|
|
23291
|
-
target: DetokenizeTarget,
|
|
23292
|
-
reason: VaultDerefReason,
|
|
23293
|
-
outcome: VaultDerefOutcome,
|
|
23294
|
-
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23295
|
-
grantId: external_exports.string().optional(),
|
|
23296
|
-
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23297
|
-
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23298
|
-
pointerCount: external_exports.number().int().positive().default(1)
|
|
23299
|
-
});
|
|
23300
|
-
var VaultSightingKind = external_exports.enum([
|
|
23301
|
-
"prompt",
|
|
23302
|
-
"tool-input",
|
|
23303
|
-
"tool-output",
|
|
23304
|
-
"file",
|
|
23305
|
-
"transcript"
|
|
23306
|
-
]);
|
|
23307
|
-
var VaultSighting = external_exports.object({
|
|
23308
|
-
location: external_exports.string(),
|
|
23309
|
-
kind: VaultSightingKind,
|
|
23310
|
-
firstSeen: external_exports.string(),
|
|
23311
|
-
lastSeen: external_exports.string()
|
|
23312
|
-
});
|
|
23313
|
-
var VaultInventoryEntry = external_exports.object({
|
|
23314
|
-
pointerId: external_exports.string(),
|
|
23315
|
-
category: DetectionCategory,
|
|
23316
|
-
provider: external_exports.string().optional(),
|
|
23317
|
-
maskedMatch: external_exports.string(),
|
|
23318
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23319
|
-
firstSeen: external_exports.string(),
|
|
23320
|
-
lastSeen: external_exports.string(),
|
|
23321
|
-
// The active reveal-to-model grant covering this value, when one exists —
|
|
23322
|
-
// the inventory badges it, the row links to revocation.
|
|
23323
|
-
revealGrantId: external_exports.string().nullable(),
|
|
23324
|
-
sightings: external_exports.array(VaultSighting)
|
|
23325
|
-
});
|
|
23326
|
-
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23327
|
-
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23328
|
-
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23329
|
-
var ListVaultInventoryQuery = external_exports.object({
|
|
23330
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23331
|
-
// Opaque; names the last row of the page just served.
|
|
23332
|
-
cursor: external_exports.string().optional()
|
|
23333
|
-
});
|
|
23334
|
-
var ListVaultInventoryResponse = external_exports.object({
|
|
23335
|
-
// Vaulted values across the whole store, not just this page — cursor-
|
|
23336
|
-
// independent, so paging never changes what the count claims.
|
|
23337
|
-
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23338
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23339
|
-
// `null` once the last page is reached.
|
|
23340
|
-
nextCursor: external_exports.string().nullable()
|
|
23341
|
-
});
|
|
23342
|
-
var ListVaultReuseQuery = external_exports.object({
|
|
23343
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23344
|
-
cursor: external_exports.string().optional()
|
|
23345
|
-
});
|
|
23346
|
-
var ListVaultReuseResponse = external_exports.object({
|
|
23347
|
-
// Reused values across the whole store — the number the section's claim
|
|
23348
|
-
// ("values detected in more than one place") is about.
|
|
23349
|
-
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23350
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23351
|
-
nextCursor: external_exports.string().nullable()
|
|
23352
|
-
});
|
|
23353
|
-
var ListVaultDerefsQuery = external_exports.object({
|
|
23354
|
-
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23355
|
-
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23356
|
-
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23357
|
-
// over a Server Action, which preserves the type, never as a URL param.
|
|
23358
|
-
includeBatched: external_exports.boolean().optional(),
|
|
23359
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23360
|
-
cursor: external_exports.string().optional()
|
|
23361
|
-
});
|
|
23362
|
-
var ListVaultDerefsResponse = external_exports.object({
|
|
23363
|
-
items: external_exports.array(VaultDeref),
|
|
23364
|
-
nextCursor: external_exports.string().nullable(),
|
|
23365
|
-
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23366
|
-
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23367
|
-
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23368
|
-
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23369
|
-
});
|
|
23370
|
-
var VaultKeyCustody = external_exports.string();
|
|
23371
|
-
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23372
|
-
var VAULT_CONSENT_VERSION = 1;
|
|
23373
|
-
var VaultConsent = external_exports.object({
|
|
23374
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23375
|
-
version: external_exports.number().int().positive()
|
|
23376
|
-
});
|
|
23377
|
-
|
|
23378
|
-
// ../../packages/schema/src/zod/local.ts
|
|
23379
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
|
|
23380
|
-
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23381
|
-
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23382
|
-
var ControlPlaneConnection = external_exports.object({
|
|
23383
|
-
endpoint: external_exports.string().min(1),
|
|
23384
|
-
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23385
|
-
label: external_exports.string().min(1).optional(),
|
|
23386
|
-
attachedAt: external_exports.iso.datetime()
|
|
23387
|
-
}).meta({ id: "ControlPlaneConnection" });
|
|
23388
|
-
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23389
|
-
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23390
|
-
var ModelJudgeConsent = external_exports.object({
|
|
23391
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23392
|
-
payloadVersion: external_exports.number().int().positive()
|
|
23393
|
-
});
|
|
23394
|
-
var HistorySyncConsent = external_exports.object({
|
|
23395
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23396
|
-
payloadVersion: external_exports.number().int().positive(),
|
|
23397
|
-
endpoint: external_exports.string()
|
|
23398
|
-
});
|
|
23399
|
-
var WorkspaceSettings = external_exports.object({
|
|
23400
|
-
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23401
|
-
runMode: RunMode.default("standalone"),
|
|
23402
|
-
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23403
|
-
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23404
|
-
controlPlane: ControlPlaneConnection.optional(),
|
|
23405
|
-
policy: SimpleDetectionPolicy.default("redact"),
|
|
23406
|
-
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23407
|
-
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23408
|
-
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23409
|
-
// Shares writes.
|
|
23410
|
-
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23411
|
-
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23412
|
-
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23413
|
-
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23414
|
-
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23415
|
-
// purging the vault is the eraser.
|
|
23416
|
-
vaultConsent: VaultConsent.optional(),
|
|
23417
|
-
// Where the vault master key lives.
|
|
23418
|
-
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23419
|
-
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23420
|
-
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23421
|
-
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23422
|
-
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23423
|
-
// Records that the user consented to sending findings to the model API for
|
|
23424
|
-
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23425
|
-
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23426
|
-
// covers the current payload and must be re-granted.
|
|
23427
|
-
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23428
|
-
// Records that the user consented to sending the activity already recorded on
|
|
23429
|
-
// this machine to the deployment it is attached to, along with the payload
|
|
23430
|
-
// shape and the endpoint they agreed to. Absent until granted, and a grant for
|
|
23431
|
-
// a different endpoint or an older payload no longer counts.
|
|
23432
|
-
historySyncConsent: HistorySyncConsent.optional()
|
|
23433
|
-
});
|
|
23434
|
-
function defaultWorkspaceSettings() {
|
|
23435
|
-
return WorkspaceSettings.parse({});
|
|
23436
|
-
}
|
|
23437
|
-
function isAttached(settings) {
|
|
23438
|
-
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23439
|
-
}
|
|
23440
|
-
function toInventoryRow(input2, id, now) {
|
|
23441
|
-
return {
|
|
23442
|
-
id,
|
|
23443
|
-
objectType: input2.objectType,
|
|
23444
|
-
location: input2.location ?? null,
|
|
23445
|
-
title: input2.title ?? null,
|
|
23446
|
-
hostId: input2.hostId ?? null,
|
|
23447
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23448
|
-
firstSeen: now,
|
|
23449
|
-
lastSeen: now
|
|
23450
|
-
};
|
|
23451
|
-
}
|
|
23452
|
-
function toSourceProjectRow(input2, id, now) {
|
|
23453
|
-
return {
|
|
23454
|
-
id,
|
|
23455
|
-
url: input2.url,
|
|
23456
|
-
name: input2.name ?? null,
|
|
23457
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23458
|
-
firstSeen: now,
|
|
23459
|
-
lastSeen: now
|
|
23460
|
-
};
|
|
23461
|
-
}
|
|
23462
|
-
function toAuditEventRow(input2) {
|
|
23463
|
-
return {
|
|
23464
|
-
id: input2.id,
|
|
23465
|
-
parentId: input2.parentId ?? null,
|
|
23466
|
-
rootSessionId: input2.rootSessionId ?? null,
|
|
23467
|
-
eventType: input2.eventType,
|
|
23468
|
-
hostId: input2.hostId ?? null,
|
|
23469
|
-
harnessId: input2.harnessId ?? null,
|
|
23470
|
-
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23471
|
-
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23472
|
-
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23473
|
-
severity: input2.severity ?? null,
|
|
23474
|
-
priority: input2.priority ?? null,
|
|
23475
|
-
content: input2.content ?? null,
|
|
23476
|
-
contentHash: input2.contentHash ?? null,
|
|
23477
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23478
|
-
};
|
|
23479
|
-
}
|
|
23480
|
-
function toClassifiedDataRow(input2, id) {
|
|
23481
|
-
return {
|
|
23482
|
-
id,
|
|
23483
|
-
class: input2.class,
|
|
23484
|
-
label: input2.label ?? null,
|
|
23485
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23486
|
-
};
|
|
23487
|
-
}
|
|
23488
|
-
function toInspectionDefinitionRow(input2, id) {
|
|
23489
|
-
return {
|
|
23490
|
-
id,
|
|
23491
|
-
ruleId: input2.ruleId,
|
|
23492
|
-
name: input2.name,
|
|
23493
|
-
category: input2.category,
|
|
23494
|
-
severity: input2.severity,
|
|
23495
|
-
definition: input2.definition,
|
|
23496
|
-
version: input2.version
|
|
23497
|
-
};
|
|
23498
|
-
}
|
|
23499
|
-
function toInspectionFindingRow(input2) {
|
|
23500
|
-
return {
|
|
23501
|
-
id: input2.id,
|
|
23502
|
-
auditEventId: input2.auditEventId,
|
|
23503
|
-
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23504
|
-
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23505
|
-
spanStart: input2.span.start,
|
|
23506
|
-
spanEnd: input2.span.end,
|
|
23507
|
-
maskedMatch: input2.maskedMatch,
|
|
23508
|
-
actionTaken: input2.actionTaken,
|
|
23509
|
-
confidence: input2.confidence,
|
|
23510
|
-
findingKey: input2.findingKey ?? null,
|
|
23511
|
-
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23512
|
-
};
|
|
23513
|
-
}
|
|
23514
|
-
function toCaptureAttributes(event) {
|
|
23515
|
-
const metadata = event.metadata;
|
|
23516
|
-
return {
|
|
23517
|
-
source_tool: event.sourceTool,
|
|
23518
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23519
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23520
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23521
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23522
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23523
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23524
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23525
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23526
|
-
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23527
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23528
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
23529
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23530
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
23531
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23532
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23533
|
-
};
|
|
23534
|
-
}
|
|
23535
|
-
function captureDefinitionVersion(finding) {
|
|
23536
|
-
return `capture/${finding.category}/${finding.severity}`;
|
|
23537
|
-
}
|
|
23538
|
-
function toCaptureDefinitionInput(finding) {
|
|
23539
|
-
return {
|
|
23540
|
-
ruleId: finding.ruleId,
|
|
23541
|
-
version: captureDefinitionVersion(finding),
|
|
23542
|
-
name: finding.ruleId,
|
|
23543
|
-
category: finding.category,
|
|
23544
|
-
severity: finding.severity,
|
|
23545
|
-
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23546
|
-
};
|
|
23547
|
-
}
|
|
23548
|
-
|
|
23549
|
-
// ../../packages/schema/src/zod/managed.ts
|
|
23550
|
-
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23551
|
-
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23552
|
-
var ManagedSettingKey = external_exports.enum([
|
|
23553
|
-
"runMode",
|
|
23554
|
-
"historicalAccess",
|
|
23555
|
-
"vaultConsent",
|
|
23556
|
-
"vaultKeyCustody",
|
|
23557
|
-
"vaultInlineReveal",
|
|
23558
|
-
"modelJudgeConsent",
|
|
23559
|
-
"dataSharesInPlace"
|
|
23560
|
-
]).meta({ id: "ManagedSettingKey" });
|
|
23561
|
-
var ManagedSettingsValues = external_exports.object({
|
|
23562
|
-
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23563
|
-
controlPlane: external_exports.object({
|
|
23564
|
-
endpoint: external_exports.string().min(1),
|
|
23565
|
-
label: external_exports.string().min(1).optional()
|
|
23566
|
-
}).optional(),
|
|
23567
|
-
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23568
|
-
vaultConsent: external_exports.boolean().optional(),
|
|
23569
|
-
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23570
|
-
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23571
|
-
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23572
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23573
|
-
}).meta({ id: "ManagedSettingsValues" });
|
|
23574
|
-
var ManagedSettings = external_exports.object({
|
|
23575
|
-
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23576
|
-
// Shown on every locked control, so the user can tell an administrative
|
|
23577
|
-
// decision from a bug. Absent renders as a generic "your organization".
|
|
23578
|
-
organization: external_exports.string().min(1).optional(),
|
|
23579
|
-
// What the administrator pinned.
|
|
23580
|
-
values: ManagedSettingsValues.default({}),
|
|
23581
|
-
// Which of those the user may not change. A key here with no matching value
|
|
23582
|
-
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23583
|
-
// the user may still override. The two are separable on purpose.
|
|
23584
|
-
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23585
|
-
}).meta({ id: "ManagedSettings" });
|
|
23586
|
-
|
|
23587
23277
|
// ../../packages/schema/src/zod/policy.ts
|
|
23588
23278
|
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23589
23279
|
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23280
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23281
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23590
23282
|
var Policy = external_exports.object({
|
|
23591
23283
|
id: external_exports.guid(),
|
|
23592
23284
|
scope: PolicyScope,
|
|
@@ -23596,7 +23288,27 @@ var Policy = external_exports.object({
|
|
|
23596
23288
|
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23597
23289
|
// Display name — optional so older policy rows without name still parse.
|
|
23598
23290
|
// Added for the findings API (policy.name column migration).
|
|
23599
|
-
name: external_exports.string().optional()
|
|
23291
|
+
name: external_exports.string().optional(),
|
|
23292
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23293
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23294
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23295
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23296
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23297
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23298
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23299
|
+
//
|
|
23300
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23301
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23302
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23303
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23304
|
+
// — the CRUD routes neither accept nor set it.
|
|
23305
|
+
//
|
|
23306
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23307
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23308
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23309
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23310
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23311
|
+
provenance: PolicyProvenance.optional()
|
|
23600
23312
|
}).meta({ id: "Policy" });
|
|
23601
23313
|
var PolicyBundle = external_exports.object({
|
|
23602
23314
|
version: external_exports.string(),
|
|
@@ -23648,6 +23360,12 @@ var PolicyBundle = external_exports.object({
|
|
|
23648
23360
|
customKeywords: external_exports.array(external_exports.string()),
|
|
23649
23361
|
fetchedAt: external_exports.iso.datetime()
|
|
23650
23362
|
}).meta({ id: "PolicyBundle" });
|
|
23363
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23364
|
+
...Object.keys(PolicyBundle.shape),
|
|
23365
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23366
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23367
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23368
|
+
].sort().join(",");
|
|
23651
23369
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23652
23370
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23653
23371
|
var CATEGORY_PEAK_SEVERITY = {
|
|
@@ -23668,9 +23386,11 @@ function severityFloorPolicy(category) {
|
|
|
23668
23386
|
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23669
23387
|
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23670
23388
|
}
|
|
23671
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23672
23389
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23673
23390
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23391
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23392
|
+
id: "RedactFallback"
|
|
23393
|
+
});
|
|
23674
23394
|
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23675
23395
|
var BUILTIN_POLICY_SPECS = {
|
|
23676
23396
|
monitor: {
|
|
@@ -23707,6 +23427,42 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
23707
23427
|
function builtinPolicyToAction(id) {
|
|
23708
23428
|
return BUILTIN_POLICY_SPECS[id].action;
|
|
23709
23429
|
}
|
|
23430
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23431
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23432
|
+
];
|
|
23433
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23434
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23435
|
+
);
|
|
23436
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23437
|
+
...BELOW_PALETTE,
|
|
23438
|
+
...PALETTE_WEAKEST_FIRST
|
|
23439
|
+
];
|
|
23440
|
+
function actionRank(action) {
|
|
23441
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23442
|
+
}
|
|
23443
|
+
function isActionAtLeast(action, floor) {
|
|
23444
|
+
return actionRank(action) >= actionRank(floor);
|
|
23445
|
+
}
|
|
23446
|
+
function strongerAction(a, b) {
|
|
23447
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23448
|
+
}
|
|
23449
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23450
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23451
|
+
}
|
|
23452
|
+
var PackPolicyFloor = external_exports.object({
|
|
23453
|
+
/**
|
|
23454
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23455
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23456
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23457
|
+
*/
|
|
23458
|
+
floor: BuiltinPolicyId,
|
|
23459
|
+
/**
|
|
23460
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23461
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23462
|
+
* re-assignable locally in either direction.
|
|
23463
|
+
*/
|
|
23464
|
+
locked: external_exports.boolean()
|
|
23465
|
+
}).describe("PackPolicyFloor");
|
|
23710
23466
|
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23711
23467
|
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23712
23468
|
);
|
|
@@ -23769,6 +23525,394 @@ var PolicyStatsResponse = external_exports.object({
|
|
|
23769
23525
|
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23770
23526
|
}).meta({ id: "PolicyStatsResponse" });
|
|
23771
23527
|
|
|
23528
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
23529
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
23530
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23531
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23532
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23533
|
+
);
|
|
23534
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23535
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23536
|
+
var ParsedPointer = external_exports.object({
|
|
23537
|
+
category: DetectionCategory,
|
|
23538
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23539
|
+
pointerId: external_exports.string(),
|
|
23540
|
+
tag: external_exports.string()
|
|
23541
|
+
});
|
|
23542
|
+
var VaultEntry = external_exports.object({
|
|
23543
|
+
pointerId: external_exports.string(),
|
|
23544
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23545
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23546
|
+
// independently of the vault encryption key below.
|
|
23547
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23548
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23549
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23550
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23551
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
23552
|
+
// different rule's category keeps the category it was minted with, so one
|
|
23553
|
+
// value always produces exactly one wire token.
|
|
23554
|
+
category: DetectionCategory,
|
|
23555
|
+
ruleId: external_exports.string(),
|
|
23556
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23557
|
+
maskedMatch: external_exports.string(),
|
|
23558
|
+
provider: external_exports.string().optional(),
|
|
23559
|
+
ciphertext: external_exports.string(),
|
|
23560
|
+
nonce: external_exports.string(),
|
|
23561
|
+
authTag: external_exports.string(),
|
|
23562
|
+
// How many times this value has been detected on this machine — the reuse
|
|
23563
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23564
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23565
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23566
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23567
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23568
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23569
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23570
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23571
|
+
// the value does not expire.
|
|
23572
|
+
userAuthorized: external_exports.boolean(),
|
|
23573
|
+
firstSeen: external_exports.string(),
|
|
23574
|
+
lastSeen: external_exports.string()
|
|
23575
|
+
});
|
|
23576
|
+
var PointerDescriptor = external_exports.object({
|
|
23577
|
+
category: DetectionCategory,
|
|
23578
|
+
provider: external_exports.string().optional(),
|
|
23579
|
+
maskedMatch: external_exports.string(),
|
|
23580
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23581
|
+
firstSeen: external_exports.string(),
|
|
23582
|
+
lastSeen: external_exports.string()
|
|
23583
|
+
});
|
|
23584
|
+
var PointerIdentity = external_exports.object({
|
|
23585
|
+
ruleId: external_exports.string(),
|
|
23586
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23587
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23588
|
+
});
|
|
23589
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23590
|
+
var VaultDerefReason = external_exports.enum([
|
|
23591
|
+
"display",
|
|
23592
|
+
"explicit-reveal",
|
|
23593
|
+
"view-render",
|
|
23594
|
+
"model-input",
|
|
23595
|
+
"remediation",
|
|
23596
|
+
"purge"
|
|
23597
|
+
]);
|
|
23598
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23599
|
+
var VaultDeref = external_exports.object({
|
|
23600
|
+
id: external_exports.guid(),
|
|
23601
|
+
pointerId: external_exports.string(),
|
|
23602
|
+
at: external_exports.string(),
|
|
23603
|
+
target: DetokenizeTarget,
|
|
23604
|
+
reason: VaultDerefReason,
|
|
23605
|
+
outcome: VaultDerefOutcome,
|
|
23606
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23607
|
+
grantId: external_exports.string().optional(),
|
|
23608
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23609
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23610
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
23611
|
+
});
|
|
23612
|
+
var VaultSightingKind = external_exports.enum([
|
|
23613
|
+
"prompt",
|
|
23614
|
+
"tool-input",
|
|
23615
|
+
"tool-output",
|
|
23616
|
+
"file",
|
|
23617
|
+
"transcript"
|
|
23618
|
+
]);
|
|
23619
|
+
var VaultSighting = external_exports.object({
|
|
23620
|
+
location: external_exports.string(),
|
|
23621
|
+
kind: VaultSightingKind,
|
|
23622
|
+
firstSeen: external_exports.string(),
|
|
23623
|
+
lastSeen: external_exports.string()
|
|
23624
|
+
});
|
|
23625
|
+
var VaultInventoryEntry = external_exports.object({
|
|
23626
|
+
pointerId: external_exports.string(),
|
|
23627
|
+
category: DetectionCategory,
|
|
23628
|
+
provider: external_exports.string().optional(),
|
|
23629
|
+
maskedMatch: external_exports.string(),
|
|
23630
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23631
|
+
firstSeen: external_exports.string(),
|
|
23632
|
+
lastSeen: external_exports.string(),
|
|
23633
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
23634
|
+
// the inventory badges it, the row links to revocation.
|
|
23635
|
+
revealGrantId: external_exports.string().nullable(),
|
|
23636
|
+
sightings: external_exports.array(VaultSighting)
|
|
23637
|
+
});
|
|
23638
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23639
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23640
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23641
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
23642
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23643
|
+
// Opaque; names the last row of the page just served.
|
|
23644
|
+
cursor: external_exports.string().optional()
|
|
23645
|
+
});
|
|
23646
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
23647
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
23648
|
+
// independent, so paging never changes what the count claims.
|
|
23649
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23650
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23651
|
+
// `null` once the last page is reached.
|
|
23652
|
+
nextCursor: external_exports.string().nullable()
|
|
23653
|
+
});
|
|
23654
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
23655
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23656
|
+
cursor: external_exports.string().optional()
|
|
23657
|
+
});
|
|
23658
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
23659
|
+
// Reused values across the whole store — the number the section's claim
|
|
23660
|
+
// ("values detected in more than one place") is about.
|
|
23661
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23662
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23663
|
+
nextCursor: external_exports.string().nullable()
|
|
23664
|
+
});
|
|
23665
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
23666
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23667
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23668
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23669
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
23670
|
+
includeBatched: external_exports.boolean().optional(),
|
|
23671
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23672
|
+
cursor: external_exports.string().optional()
|
|
23673
|
+
});
|
|
23674
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
23675
|
+
items: external_exports.array(VaultDeref),
|
|
23676
|
+
nextCursor: external_exports.string().nullable(),
|
|
23677
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23678
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23679
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23680
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23681
|
+
});
|
|
23682
|
+
var VaultKeyCustody = external_exports.string();
|
|
23683
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23684
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
23685
|
+
var VaultConsent = external_exports.object({
|
|
23686
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23687
|
+
version: external_exports.number().int().positive()
|
|
23688
|
+
});
|
|
23689
|
+
|
|
23690
|
+
// ../../packages/schema/src/zod/local.ts
|
|
23691
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23692
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23693
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23694
|
+
var ControlPlaneConnection = external_exports.object({
|
|
23695
|
+
endpoint: external_exports.string().min(1),
|
|
23696
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23697
|
+
label: external_exports.string().min(1).optional(),
|
|
23698
|
+
attachedAt: external_exports.iso.datetime()
|
|
23699
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
23700
|
+
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23701
|
+
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23702
|
+
var ModelJudgeConsent = external_exports.object({
|
|
23703
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23704
|
+
payloadVersion: external_exports.number().int().positive()
|
|
23705
|
+
});
|
|
23706
|
+
var HistorySyncConsent = external_exports.object({
|
|
23707
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23708
|
+
payloadVersion: external_exports.number().int().positive(),
|
|
23709
|
+
endpoint: external_exports.string()
|
|
23710
|
+
});
|
|
23711
|
+
var WorkspaceSettings = external_exports.object({
|
|
23712
|
+
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23713
|
+
runMode: RunMode.default("standalone"),
|
|
23714
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23715
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23716
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
23717
|
+
policy: SimpleDetectionPolicy.default("redact"),
|
|
23718
|
+
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23719
|
+
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23720
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23721
|
+
// Shares writes.
|
|
23722
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23723
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23724
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23725
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23726
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23727
|
+
// purging the vault is the eraser.
|
|
23728
|
+
vaultConsent: VaultConsent.optional(),
|
|
23729
|
+
// Where the vault master key lives.
|
|
23730
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23731
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23732
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23733
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23734
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23735
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23736
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23737
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23738
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23739
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23740
|
+
//
|
|
23741
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23742
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23743
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23744
|
+
// word and stays out of the stored value.
|
|
23745
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23746
|
+
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23747
|
+
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23748
|
+
// Records that the user consented to sending findings to the model API for
|
|
23749
|
+
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23750
|
+
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23751
|
+
// covers the current payload and must be re-granted.
|
|
23752
|
+
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23753
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23754
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23755
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23756
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23757
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23758
|
+
// payload no longer counts.
|
|
23759
|
+
historySyncConsent: HistorySyncConsent.optional()
|
|
23760
|
+
});
|
|
23761
|
+
function defaultWorkspaceSettings() {
|
|
23762
|
+
return WorkspaceSettings.parse({});
|
|
23763
|
+
}
|
|
23764
|
+
function isAttached(settings) {
|
|
23765
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23766
|
+
}
|
|
23767
|
+
function toInventoryRow(input2, id, now) {
|
|
23768
|
+
return {
|
|
23769
|
+
id,
|
|
23770
|
+
objectType: input2.objectType,
|
|
23771
|
+
location: input2.location ?? null,
|
|
23772
|
+
title: input2.title ?? null,
|
|
23773
|
+
hostId: input2.hostId ?? null,
|
|
23774
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23775
|
+
firstSeen: now,
|
|
23776
|
+
lastSeen: now
|
|
23777
|
+
};
|
|
23778
|
+
}
|
|
23779
|
+
function toSourceProjectRow(input2, id, now) {
|
|
23780
|
+
return {
|
|
23781
|
+
id,
|
|
23782
|
+
url: input2.url,
|
|
23783
|
+
name: input2.name ?? null,
|
|
23784
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23785
|
+
firstSeen: now,
|
|
23786
|
+
lastSeen: now
|
|
23787
|
+
};
|
|
23788
|
+
}
|
|
23789
|
+
function toAuditEventRow(input2) {
|
|
23790
|
+
return {
|
|
23791
|
+
id: input2.id,
|
|
23792
|
+
parentId: input2.parentId ?? null,
|
|
23793
|
+
rootSessionId: input2.rootSessionId ?? null,
|
|
23794
|
+
eventType: input2.eventType,
|
|
23795
|
+
hostId: input2.hostId ?? null,
|
|
23796
|
+
harnessId: input2.harnessId ?? null,
|
|
23797
|
+
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23798
|
+
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23799
|
+
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23800
|
+
severity: input2.severity ?? null,
|
|
23801
|
+
priority: input2.priority ?? null,
|
|
23802
|
+
content: input2.content ?? null,
|
|
23803
|
+
contentHash: input2.contentHash ?? null,
|
|
23804
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23805
|
+
};
|
|
23806
|
+
}
|
|
23807
|
+
function toClassifiedDataRow(input2, id) {
|
|
23808
|
+
return {
|
|
23809
|
+
id,
|
|
23810
|
+
class: input2.class,
|
|
23811
|
+
label: input2.label ?? null,
|
|
23812
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23813
|
+
};
|
|
23814
|
+
}
|
|
23815
|
+
function toInspectionDefinitionRow(input2, id) {
|
|
23816
|
+
return {
|
|
23817
|
+
id,
|
|
23818
|
+
ruleId: input2.ruleId,
|
|
23819
|
+
name: input2.name,
|
|
23820
|
+
category: input2.category,
|
|
23821
|
+
severity: input2.severity,
|
|
23822
|
+
definition: input2.definition,
|
|
23823
|
+
version: input2.version
|
|
23824
|
+
};
|
|
23825
|
+
}
|
|
23826
|
+
function toInspectionFindingRow(input2) {
|
|
23827
|
+
return {
|
|
23828
|
+
id: input2.id,
|
|
23829
|
+
auditEventId: input2.auditEventId,
|
|
23830
|
+
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23831
|
+
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23832
|
+
spanStart: input2.span.start,
|
|
23833
|
+
spanEnd: input2.span.end,
|
|
23834
|
+
maskedMatch: input2.maskedMatch,
|
|
23835
|
+
actionTaken: input2.actionTaken,
|
|
23836
|
+
confidence: input2.confidence,
|
|
23837
|
+
findingKey: input2.findingKey ?? null,
|
|
23838
|
+
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23839
|
+
};
|
|
23840
|
+
}
|
|
23841
|
+
function toCaptureAttributes(event) {
|
|
23842
|
+
const metadata = event.metadata;
|
|
23843
|
+
return {
|
|
23844
|
+
source_tool: event.sourceTool,
|
|
23845
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23846
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23847
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23848
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23849
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23850
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23851
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23852
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23853
|
+
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23854
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23855
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
23856
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23857
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
23858
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23859
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23860
|
+
};
|
|
23861
|
+
}
|
|
23862
|
+
function captureDefinitionVersion(finding) {
|
|
23863
|
+
return `capture/${finding.category}/${finding.severity}`;
|
|
23864
|
+
}
|
|
23865
|
+
function toCaptureDefinitionInput(finding) {
|
|
23866
|
+
return {
|
|
23867
|
+
ruleId: finding.ruleId,
|
|
23868
|
+
version: captureDefinitionVersion(finding),
|
|
23869
|
+
name: finding.ruleId,
|
|
23870
|
+
category: finding.category,
|
|
23871
|
+
severity: finding.severity,
|
|
23872
|
+
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
23873
|
+
};
|
|
23874
|
+
}
|
|
23875
|
+
|
|
23876
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
23877
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23878
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23879
|
+
var ManagedSettingKey = external_exports.enum([
|
|
23880
|
+
"runMode",
|
|
23881
|
+
"historicalAccess",
|
|
23882
|
+
"vaultConsent",
|
|
23883
|
+
"vaultKeyCustody",
|
|
23884
|
+
"vaultInlineReveal",
|
|
23885
|
+
"modelJudgeConsent",
|
|
23886
|
+
"dataSharesInPlace",
|
|
23887
|
+
"redactFallback"
|
|
23888
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
23889
|
+
var ManagedSettingsValues = external_exports.object({
|
|
23890
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23891
|
+
controlPlane: external_exports.object({
|
|
23892
|
+
endpoint: external_exports.string().min(1),
|
|
23893
|
+
label: external_exports.string().min(1).optional()
|
|
23894
|
+
}).optional(),
|
|
23895
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23896
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
23897
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23898
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23899
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23900
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23901
|
+
redactFallback: RedactFallback.optional()
|
|
23902
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
23903
|
+
var ManagedSettings = external_exports.object({
|
|
23904
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23905
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
23906
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
23907
|
+
organization: external_exports.string().min(1).optional(),
|
|
23908
|
+
// What the administrator pinned.
|
|
23909
|
+
values: ManagedSettingsValues.default({}),
|
|
23910
|
+
// Which of those the user may not change. A key here with no matching value
|
|
23911
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23912
|
+
// the user may still override. The two are separable on purpose.
|
|
23913
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23914
|
+
}).meta({ id: "ManagedSettings" });
|
|
23915
|
+
|
|
23772
23916
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23773
23917
|
var ProjectFileInput = external_exports.object({
|
|
23774
23918
|
path: external_exports.string().min(1),
|
|
@@ -24014,10 +24158,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
24014
24158
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
24015
24159
|
|
|
24016
24160
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24161
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24162
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
24017
24163
|
var SaveSettingsInput = external_exports.object({
|
|
24018
24164
|
historicalAccess: external_exports.string(),
|
|
24019
|
-
modelJudgeConsent:
|
|
24020
|
-
historySyncConsent:
|
|
24165
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24166
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
24021
24167
|
vaultConsent: external_exports.string(),
|
|
24022
24168
|
vaultInlineReveal: external_exports.string()
|
|
24023
24169
|
});
|
|
@@ -24167,9 +24313,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24167
24313
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24168
24314
|
return reasons;
|
|
24169
24315
|
}
|
|
24170
|
-
function buildReviewInfo(trust, transports) {
|
|
24316
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24171
24317
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24172
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24318
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24173
24319
|
}
|
|
24174
24320
|
function distinctTransports(transports) {
|
|
24175
24321
|
return Array.from(new Set(transports));
|
|
@@ -24367,8 +24513,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24367
24513
|
}
|
|
24368
24514
|
|
|
24369
24515
|
// ../../packages/persistence/src/database.ts
|
|
24370
|
-
import { randomUUID as
|
|
24371
|
-
import { join as
|
|
24516
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24517
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24372
24518
|
import { DatabaseSync } from "node:sqlite";
|
|
24373
24519
|
|
|
24374
24520
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24623,6 +24769,10 @@ function allRows(stmt, params) {
|
|
|
24623
24769
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24624
24770
|
return stmt.all(params);
|
|
24625
24771
|
}
|
|
24772
|
+
function* iterateRows(stmt, params) {
|
|
24773
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24774
|
+
for (const row of rows) yield row;
|
|
24775
|
+
}
|
|
24626
24776
|
function getRow(stmt, params) {
|
|
24627
24777
|
if (params === void 0) return stmt.get();
|
|
24628
24778
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25091,10 +25241,17 @@ function ensureSyncedAtColumn(db, table2) {
|
|
|
25091
25241
|
if (!columns.includes("sync_claimed_at")) {
|
|
25092
25242
|
db.exec(`ALTER TABLE ${table2} ADD COLUMN sync_claimed_at integer`);
|
|
25093
25243
|
}
|
|
25244
|
+
if (!columns.includes("outbox_owed")) {
|
|
25245
|
+
db.exec(`ALTER TABLE ${table2} ADD COLUMN outbox_owed integer`);
|
|
25246
|
+
}
|
|
25094
25247
|
db.exec(
|
|
25095
25248
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25096
25249
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25097
25250
|
);
|
|
25251
|
+
db.exec(
|
|
25252
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25253
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25254
|
+
);
|
|
25098
25255
|
db.exec(
|
|
25099
25256
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25100
25257
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25199,7 +25356,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25199
25356
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25200
25357
|
var DAY_MS = 864e5;
|
|
25201
25358
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25202
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25203
25359
|
function defaultTimeZone() {
|
|
25204
25360
|
try {
|
|
25205
25361
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25254,6 +25410,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25254
25410
|
error: "error",
|
|
25255
25411
|
active: "active"
|
|
25256
25412
|
};
|
|
25413
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25257
25414
|
function safeParseStringArray(raw) {
|
|
25258
25415
|
if (!raw) return [];
|
|
25259
25416
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25327,6 +25484,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25327
25484
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25328
25485
|
json_extract(attributes, '$.internal') AS internal,
|
|
25329
25486
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25487
|
+
var LLM_USAGE_SELECT = `
|
|
25488
|
+
SELECT root_session_id AS sessionId,
|
|
25489
|
+
provider,
|
|
25490
|
+
model,
|
|
25491
|
+
service_tier AS serviceTier,
|
|
25492
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25493
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25494
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25495
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25496
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25497
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25498
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25499
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25500
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25501
|
+
function usageLeaves(rows) {
|
|
25502
|
+
return rows.map((row) => {
|
|
25503
|
+
const attributes = {
|
|
25504
|
+
input_tokens: row.inputTokens,
|
|
25505
|
+
output_tokens: row.outputTokens,
|
|
25506
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25507
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25508
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25509
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25510
|
+
web_search_requests: row.webSearchRequests
|
|
25511
|
+
};
|
|
25512
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25513
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25514
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25515
|
+
return { sessionId: row.sessionId, attributes };
|
|
25516
|
+
});
|
|
25517
|
+
}
|
|
25330
25518
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25331
25519
|
var HAS_ACTIVITY = `EXISTS (
|
|
25332
25520
|
SELECT 1 FROM audit_events c
|
|
@@ -25352,16 +25540,17 @@ var SqliteActivityRepository = class {
|
|
|
25352
25540
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25353
25541
|
const liveNow = countScalar(
|
|
25354
25542
|
this.db,
|
|
25355
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25543
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25356
25544
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25357
|
-
AND
|
|
25358
|
-
|
|
25359
|
-
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25545
|
+
AND s.id IN (
|
|
25546
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25547
|
+
UNION
|
|
25548
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25549
|
+
WHERE started_at >= ?
|
|
25550
|
+
UNION
|
|
25551
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25552
|
+
WHERE ended_at >= ?)`,
|
|
25553
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25365
25554
|
);
|
|
25366
25555
|
const toolCallsToday = countScalar(
|
|
25367
25556
|
this.db,
|
|
@@ -25491,7 +25680,7 @@ var SqliteActivityRepository = class {
|
|
|
25491
25680
|
this.db.prepare(
|
|
25492
25681
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25493
25682
|
FROM audit_events
|
|
25494
|
-
WHERE id = ? OR root_session_id = ?
|
|
25683
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25495
25684
|
ORDER BY started_at ASC, id ASC`
|
|
25496
25685
|
),
|
|
25497
25686
|
[sessionId, sessionId]
|
|
@@ -25504,14 +25693,14 @@ var SqliteActivityRepository = class {
|
|
|
25504
25693
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25505
25694
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25506
25695
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25507
|
-
FROM audit_events
|
|
25696
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25508
25697
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25509
25698
|
),
|
|
25510
25699
|
[sessionId]
|
|
25511
25700
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25512
25701
|
const primaryModel = getRow(
|
|
25513
25702
|
this.db.prepare(
|
|
25514
|
-
`SELECT model, provider FROM audit_events
|
|
25703
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25515
25704
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25516
25705
|
ORDER BY started_at ASC, id ASC
|
|
25517
25706
|
LIMIT 1`
|
|
@@ -25522,7 +25711,7 @@ var SqliteActivityRepository = class {
|
|
|
25522
25711
|
this.db.prepare(
|
|
25523
25712
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25524
25713
|
count(*) AS n
|
|
25525
|
-
FROM audit_events
|
|
25714
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25526
25715
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25527
25716
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25528
25717
|
),
|
|
@@ -25530,7 +25719,7 @@ var SqliteActivityRepository = class {
|
|
|
25530
25719
|
);
|
|
25531
25720
|
const modelRows = allRows(
|
|
25532
25721
|
this.db.prepare(
|
|
25533
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25722
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25534
25723
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25535
25724
|
ORDER BY model`
|
|
25536
25725
|
),
|
|
@@ -25539,7 +25728,7 @@ var SqliteActivityRepository = class {
|
|
|
25539
25728
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25540
25729
|
const commits = countScalar(
|
|
25541
25730
|
this.db,
|
|
25542
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25731
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25543
25732
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25544
25733
|
[sessionId]
|
|
25545
25734
|
);
|
|
@@ -25575,25 +25764,57 @@ var SqliteActivityRepository = class {
|
|
|
25575
25764
|
return Promise.resolve(session);
|
|
25576
25765
|
}
|
|
25577
25766
|
/**
|
|
25578
|
-
* Cross-session token report — every `llm_call`
|
|
25579
|
-
* `started_at >= fromMs`
|
|
25580
|
-
* USD cost DERIVED at read time via the shared
|
|
25581
|
-
*
|
|
25582
|
-
*
|
|
25583
|
-
*
|
|
25767
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25768
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25769
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25770
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25771
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25772
|
+
*
|
|
25773
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25774
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25775
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25776
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25777
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25778
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25779
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25780
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25781
|
+
* recompute the columns it could have read. The index is one every open
|
|
25782
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25783
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25784
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25785
|
+
* per call, no bag parsed.
|
|
25584
25786
|
*/
|
|
25585
25787
|
tokenReports(fromMs) {
|
|
25586
|
-
const
|
|
25587
|
-
|
|
25788
|
+
const rows = allRows(
|
|
25789
|
+
this.db.prepare(
|
|
25790
|
+
`${LLM_USAGE_SELECT}
|
|
25791
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25792
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25793
|
+
${LLM_USAGE_GROUP}`
|
|
25794
|
+
),
|
|
25795
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25796
|
+
);
|
|
25797
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25588
25798
|
}
|
|
25589
25799
|
/**
|
|
25590
|
-
* One session's token report — its `llm_call`
|
|
25591
|
-
* model) with derived cost, or `null` when the session made no
|
|
25592
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25593
|
-
* breakdown + estimated cost.
|
|
25800
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25801
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25802
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25803
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25804
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25805
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25594
25806
|
*/
|
|
25595
25807
|
tokenReportForSession(sessionId) {
|
|
25596
|
-
const
|
|
25808
|
+
const rows = allRows(
|
|
25809
|
+
this.db.prepare(
|
|
25810
|
+
`${LLM_USAGE_SELECT}
|
|
25811
|
+
FROM audit_events
|
|
25812
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25813
|
+
${LLM_USAGE_GROUP}`
|
|
25814
|
+
),
|
|
25815
|
+
[sessionId]
|
|
25816
|
+
);
|
|
25817
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25597
25818
|
return Promise.resolve(reports[0] ?? null);
|
|
25598
25819
|
}
|
|
25599
25820
|
/**
|
|
@@ -25617,42 +25838,6 @@ var SqliteActivityRepository = class {
|
|
|
25617
25838
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25618
25839
|
return Promise.resolve([...seen]);
|
|
25619
25840
|
}
|
|
25620
|
-
/**
|
|
25621
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25622
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25623
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25624
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25625
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25626
|
-
*/
|
|
25627
|
-
readLlmCallLeaves(opts = {}) {
|
|
25628
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25629
|
-
const params = [];
|
|
25630
|
-
if (opts.sessionId !== void 0) {
|
|
25631
|
-
conditions.push("root_session_id = ?");
|
|
25632
|
-
params.push(opts.sessionId);
|
|
25633
|
-
}
|
|
25634
|
-
if (opts.fromMs !== void 0) {
|
|
25635
|
-
conditions.push("started_at >= ?");
|
|
25636
|
-
params.push(opts.fromMs);
|
|
25637
|
-
}
|
|
25638
|
-
const rows = allRows(
|
|
25639
|
-
this.db.prepare(
|
|
25640
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25641
|
-
FROM audit_events
|
|
25642
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25643
|
-
),
|
|
25644
|
-
params
|
|
25645
|
-
);
|
|
25646
|
-
return mapRowsTolerant(
|
|
25647
|
-
rows.filter(
|
|
25648
|
-
(row) => row.sessionId !== null
|
|
25649
|
-
),
|
|
25650
|
-
(row) => ({
|
|
25651
|
-
sessionId: row.sessionId,
|
|
25652
|
-
attributes: JSON.parse(row.attributes)
|
|
25653
|
-
})
|
|
25654
|
-
);
|
|
25655
|
-
}
|
|
25656
25841
|
/**
|
|
25657
25842
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25658
25843
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25667,20 +25852,23 @@ var SqliteActivityRepository = class {
|
|
|
25667
25852
|
const inClause = placeholders(sessionIds.length);
|
|
25668
25853
|
const lastActivityRows = allRows(
|
|
25669
25854
|
this.db.prepare(
|
|
25670
|
-
`SELECT
|
|
25671
|
-
|
|
25672
|
-
|
|
25855
|
+
`SELECT ids.value AS id,
|
|
25856
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25857
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25858
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25859
|
+
FROM json_each(?) AS ids`
|
|
25673
25860
|
),
|
|
25674
|
-
sessionIds
|
|
25861
|
+
[JSON.stringify(sessionIds)]
|
|
25675
25862
|
);
|
|
25676
25863
|
for (const row of lastActivityRows) {
|
|
25677
|
-
if (row.id === null) continue;
|
|
25678
25864
|
const entry = result.get(row.id);
|
|
25679
|
-
|
|
25865
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25866
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25680
25867
|
}
|
|
25681
25868
|
const turnsRows = allRows(
|
|
25682
25869
|
this.db.prepare(
|
|
25683
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25870
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25871
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25684
25872
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25685
25873
|
GROUP BY root_session_id`
|
|
25686
25874
|
),
|
|
@@ -25695,7 +25883,7 @@ var SqliteActivityRepository = class {
|
|
|
25695
25883
|
this.db.prepare(
|
|
25696
25884
|
`SELECT root_session_id AS id,
|
|
25697
25885
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25698
|
-
FROM audit_events
|
|
25886
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25699
25887
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25700
25888
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25701
25889
|
GROUP BY root_session_id`
|
|
@@ -25725,7 +25913,7 @@ var SqliteActivityRepository = class {
|
|
|
25725
25913
|
this.db.prepare(
|
|
25726
25914
|
`SELECT root_session_id AS id,
|
|
25727
25915
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25728
|
-
FROM audit_events
|
|
25916
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25729
25917
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25730
25918
|
GROUP BY root_session_id`
|
|
25731
25919
|
),
|
|
@@ -26754,7 +26942,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26754
26942
|
|
|
26755
26943
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26756
26944
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26757
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26758
26945
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26759
26946
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26760
26947
|
function compareLocationOrder(a, b) {
|
|
@@ -26783,6 +26970,25 @@ function deriveInstanceStatus(row) {
|
|
|
26783
26970
|
latestResolutionStatus: row.latest_status
|
|
26784
26971
|
});
|
|
26785
26972
|
}
|
|
26973
|
+
function toFlatFindingRow(r) {
|
|
26974
|
+
return {
|
|
26975
|
+
id: r.id,
|
|
26976
|
+
ruleId: r.rule_id,
|
|
26977
|
+
category: r.category,
|
|
26978
|
+
severity: r.severity,
|
|
26979
|
+
maskedMatch: r.masked_match,
|
|
26980
|
+
actionTaken: r.action_taken,
|
|
26981
|
+
confidence: r.confidence,
|
|
26982
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
26983
|
+
sourceTool: r.source_tool,
|
|
26984
|
+
repo: r.repo ?? "",
|
|
26985
|
+
file: r.file ?? "",
|
|
26986
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
26987
|
+
eventId: r.event_id,
|
|
26988
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
26989
|
+
status: deriveInstanceStatus(r)
|
|
26990
|
+
};
|
|
26991
|
+
}
|
|
26786
26992
|
function encodeGroupCursor(group) {
|
|
26787
26993
|
const payload = {
|
|
26788
26994
|
sev: group.severity,
|
|
@@ -26858,7 +27064,7 @@ var SqliteFindingsRepository = class {
|
|
|
26858
27064
|
this.db.prepare(
|
|
26859
27065
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26860
27066
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26861
|
-
|
|
27067
|
+
e.source_tool AS source_tool,
|
|
26862
27068
|
e.event_type AS kind
|
|
26863
27069
|
FROM audit_events e
|
|
26864
27070
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -26966,56 +27172,11 @@ var SqliteFindingsRepository = class {
|
|
|
26966
27172
|
predicate,
|
|
26967
27173
|
params: sessionParams
|
|
26968
27174
|
});
|
|
26969
|
-
const rows =
|
|
26970
|
-
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
26974
|
-
FROM (
|
|
26975
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
26976
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
26977
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
26978
|
-
e.started_at AS occurred_at,
|
|
26979
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
26980
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
26981
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
26982
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
26983
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
26984
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
26985
|
-
latest.status AS latest_status,
|
|
26986
|
-
ROW_NUMBER() OVER (
|
|
26987
|
-
PARTITION BY d.rule_id
|
|
26988
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
26989
|
-
) AS rn
|
|
26990
|
-
FROM inspection_findings f
|
|
26991
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
26992
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
26993
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
26994
|
-
ON latest.finding_key = f.finding_key
|
|
26995
|
-
${predicate}
|
|
26996
|
-
)
|
|
26997
|
-
WHERE rn <= :cap
|
|
26998
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
26999
|
-
),
|
|
27000
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
27001
|
-
);
|
|
27002
|
-
const groupable = rows.map((r) => ({
|
|
27003
|
-
id: r.id,
|
|
27004
|
-
ruleId: r.rule_id,
|
|
27005
|
-
category: r.category,
|
|
27006
|
-
severity: r.severity,
|
|
27007
|
-
maskedMatch: r.masked_match,
|
|
27008
|
-
actionTaken: r.action_taken,
|
|
27009
|
-
confidence: r.confidence,
|
|
27010
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27011
|
-
sourceTool: r.source_tool,
|
|
27012
|
-
repo: r.repo ?? "",
|
|
27013
|
-
file: r.file ?? "",
|
|
27014
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27015
|
-
eventId: r.event_id,
|
|
27016
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27017
|
-
status: deriveInstanceStatus(r)
|
|
27018
|
-
}));
|
|
27175
|
+
const rows = this.previewRows(aggregates, {
|
|
27176
|
+
sessionId: query.sessionId,
|
|
27177
|
+
from: query.from
|
|
27178
|
+
});
|
|
27179
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
27019
27180
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27020
27181
|
const filterOpts = {
|
|
27021
27182
|
severity: query.severity,
|
|
@@ -27101,8 +27262,10 @@ var SqliteFindingsRepository = class {
|
|
|
27101
27262
|
*
|
|
27102
27263
|
* The scan runs from the top of the scope on every request, not from the
|
|
27103
27264
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27104
|
-
* move as the caller pages. Rows
|
|
27105
|
-
*
|
|
27265
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27266
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27267
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27268
|
+
* fetched batches; only the page itself is retained.
|
|
27106
27269
|
*/
|
|
27107
27270
|
listFindingInstances(query) {
|
|
27108
27271
|
const opts = {
|
|
@@ -27118,6 +27281,10 @@ var SqliteFindingsRepository = class {
|
|
|
27118
27281
|
};
|
|
27119
27282
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27120
27283
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27284
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27285
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27286
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27287
|
+
};
|
|
27121
27288
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27122
27289
|
const items = [];
|
|
27123
27290
|
let total = 0;
|
|
@@ -27130,6 +27297,7 @@ var SqliteFindingsRepository = class {
|
|
|
27130
27297
|
accumulator.add(row);
|
|
27131
27298
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27132
27299
|
total += 1;
|
|
27300
|
+
if (!isPastCursor(row)) continue;
|
|
27133
27301
|
if (items.length < limit) {
|
|
27134
27302
|
items.push(toInstanceDetail(row));
|
|
27135
27303
|
last = row;
|
|
@@ -27138,15 +27306,6 @@ var SqliteFindingsRepository = class {
|
|
|
27138
27306
|
}
|
|
27139
27307
|
}
|
|
27140
27308
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27141
|
-
if (cursor !== null) {
|
|
27142
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27143
|
-
return Promise.resolve({
|
|
27144
|
-
totals: { findings: total },
|
|
27145
|
-
facets: accumulator.facets(),
|
|
27146
|
-
items: resumed.items,
|
|
27147
|
-
nextCursor: resumed.nextCursor
|
|
27148
|
-
});
|
|
27149
|
-
}
|
|
27150
27309
|
return Promise.resolve({
|
|
27151
27310
|
totals: { findings: total },
|
|
27152
27311
|
facets: accumulator.facets(),
|
|
@@ -27154,35 +27313,6 @@ var SqliteFindingsRepository = class {
|
|
|
27154
27313
|
nextCursor
|
|
27155
27314
|
});
|
|
27156
27315
|
}
|
|
27157
|
-
/**
|
|
27158
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27159
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27160
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27161
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27162
|
-
*/
|
|
27163
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27164
|
-
const items = [];
|
|
27165
|
-
let last;
|
|
27166
|
-
let hasMore = false;
|
|
27167
|
-
for (const row of this.scanFindingRows({
|
|
27168
|
-
sessionId: query.sessionId,
|
|
27169
|
-
from: query.from,
|
|
27170
|
-
after: cursor
|
|
27171
|
-
})) {
|
|
27172
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27173
|
-
if (items.length < limit) {
|
|
27174
|
-
items.push(toInstanceDetail(row));
|
|
27175
|
-
last = row;
|
|
27176
|
-
} else {
|
|
27177
|
-
hasMore = true;
|
|
27178
|
-
break;
|
|
27179
|
-
}
|
|
27180
|
-
}
|
|
27181
|
-
return {
|
|
27182
|
-
items,
|
|
27183
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27184
|
-
};
|
|
27185
|
-
}
|
|
27186
27316
|
/**
|
|
27187
27317
|
* The same findings folded by location: repository, then file within it.
|
|
27188
27318
|
*
|
|
@@ -27265,25 +27395,111 @@ var SqliteFindingsRepository = class {
|
|
|
27265
27395
|
});
|
|
27266
27396
|
}
|
|
27267
27397
|
/**
|
|
27268
|
-
*
|
|
27398
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27399
|
+
*
|
|
27400
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27401
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27402
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27403
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27404
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27405
|
+
* store while the answer does not.
|
|
27406
|
+
*
|
|
27407
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27408
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27409
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27410
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27411
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27412
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27413
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27414
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27415
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27416
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27417
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27418
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27419
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27420
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27421
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27422
|
+
* sort of the scope, which is still that floor.
|
|
27423
|
+
*
|
|
27424
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27425
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27426
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27427
|
+
* built from.
|
|
27428
|
+
*/
|
|
27429
|
+
previewRows(aggregates, scope) {
|
|
27430
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27431
|
+
let remaining = 0;
|
|
27432
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27433
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27434
|
+
wanted.set(ruleId, n);
|
|
27435
|
+
remaining += n;
|
|
27436
|
+
}
|
|
27437
|
+
const rows = [];
|
|
27438
|
+
if (remaining === 0) return rows;
|
|
27439
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27440
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27441
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27442
|
+
const want = wanted.get(r.rule_id);
|
|
27443
|
+
if (want === void 0) continue;
|
|
27444
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27445
|
+
if (have >= want) continue;
|
|
27446
|
+
taken.set(r.rule_id, have + 1);
|
|
27447
|
+
rows.push(r);
|
|
27448
|
+
remaining -= 1;
|
|
27449
|
+
if (remaining === 0) break;
|
|
27450
|
+
}
|
|
27451
|
+
return rows;
|
|
27452
|
+
}
|
|
27453
|
+
/**
|
|
27454
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27269
27455
|
*
|
|
27270
27456
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27271
27457
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27272
|
-
* store is far more rows than any page.
|
|
27273
|
-
*
|
|
27274
|
-
*
|
|
27275
|
-
*
|
|
27276
|
-
*
|
|
27277
|
-
*
|
|
27278
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27279
|
-
* a window over the whole resolution table once per batch.
|
|
27458
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27459
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27460
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27461
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27462
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27463
|
+
* batch and cost the square of the scope.
|
|
27280
27464
|
*
|
|
27281
|
-
* `
|
|
27282
|
-
* would be missing from its own facet, which is
|
|
27283
|
-
* dimension
|
|
27465
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27466
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27467
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27468
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27469
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27470
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27471
|
+
* page-2+ request would otherwise re-seek for.
|
|
27284
27472
|
*/
|
|
27285
27473
|
*scanFindingRows(scope) {
|
|
27286
|
-
const
|
|
27474
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27475
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27476
|
+
yield toFlatFindingRow(r);
|
|
27477
|
+
}
|
|
27478
|
+
}
|
|
27479
|
+
/**
|
|
27480
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27481
|
+
* joined to its event and definition, newest first.
|
|
27482
|
+
*
|
|
27483
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27484
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27485
|
+
*
|
|
27486
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27487
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27488
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27489
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27490
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27491
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27492
|
+
* ordered within the session — and the order falls out of the index.
|
|
27493
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27494
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27495
|
+
*
|
|
27496
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27497
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27498
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27499
|
+
* table would be materialized before the first row streamed.
|
|
27500
|
+
*/
|
|
27501
|
+
findingScanSql(scope) {
|
|
27502
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27287
27503
|
const params = [];
|
|
27288
27504
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27289
27505
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27297,58 +27513,24 @@ var SqliteFindingsRepository = class {
|
|
|
27297
27513
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27298
27514
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27299
27515
|
e.started_at AS occurred_at,
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27516
|
+
e.source_tool AS source_tool,
|
|
27517
|
+
e.repo AS repo,
|
|
27518
|
+
e.file_path AS file,
|
|
27519
|
+
e.tool_name AS tool_name,
|
|
27304
27520
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27305
27521
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27306
27522
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27307
|
-
FROM
|
|
27308
|
-
JOIN
|
|
27309
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27523
|
+
FROM audit_events e
|
|
27524
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27525
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27310
27526
|
WHERE ${conditions.join(" AND ")}
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
LIMIT ?`;
|
|
27314
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27315
|
-
for (; ; ) {
|
|
27316
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27317
|
-
...params,
|
|
27318
|
-
after.startedAtMs,
|
|
27319
|
-
after.startedAtMs,
|
|
27320
|
-
after.id,
|
|
27321
|
-
SCAN_BATCH_ROWS
|
|
27322
|
-
]);
|
|
27323
|
-
for (const r of rows) {
|
|
27324
|
-
yield {
|
|
27325
|
-
id: r.id,
|
|
27326
|
-
ruleId: r.rule_id,
|
|
27327
|
-
category: r.category,
|
|
27328
|
-
severity: r.severity,
|
|
27329
|
-
maskedMatch: r.masked_match,
|
|
27330
|
-
actionTaken: r.action_taken,
|
|
27331
|
-
confidence: r.confidence,
|
|
27332
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27333
|
-
sourceTool: r.source_tool,
|
|
27334
|
-
repo: r.repo ?? "",
|
|
27335
|
-
file: r.file ?? "",
|
|
27336
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27337
|
-
eventId: r.event_id,
|
|
27338
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27339
|
-
status: deriveInstanceStatus(r)
|
|
27340
|
-
};
|
|
27341
|
-
}
|
|
27342
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27343
|
-
const lastRow = rows[rows.length - 1];
|
|
27344
|
-
if (lastRow === void 0) return;
|
|
27345
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27346
|
-
}
|
|
27527
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27528
|
+
return { sql, params };
|
|
27347
27529
|
}
|
|
27348
27530
|
groupAggregates(withSearchText, scope) {
|
|
27349
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27350
|
-
group_concat(DISTINCT
|
|
27351
|
-
group_concat(DISTINCT 'via ' ||
|
|
27531
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27532
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27533
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27352
27534
|
const rows = this.db.prepare(
|
|
27353
27535
|
`SELECT rule_id,
|
|
27354
27536
|
sum(tuple_count) AS instance_count,
|
|
@@ -27366,7 +27548,7 @@ var SqliteFindingsRepository = class {
|
|
|
27366
27548
|
coalesce(latest.status, '') AS status_tuple,
|
|
27367
27549
|
count(*) AS tuple_count,
|
|
27368
27550
|
max(e.started_at) AS latest_at,
|
|
27369
|
-
group_concat(DISTINCT
|
|
27551
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27370
27552
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27371
27553
|
${innerSearchColumns}
|
|
27372
27554
|
FROM inspection_findings f
|
|
@@ -27497,6 +27679,8 @@ function isoDay(ms) {
|
|
|
27497
27679
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27498
27680
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27499
27681
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27682
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27683
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27500
27684
|
var SKIPPED = -1;
|
|
27501
27685
|
var ROW_COLUMNS = `id,
|
|
27502
27686
|
parent_id AS parentId,
|
|
@@ -27536,6 +27720,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27536
27720
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27537
27721
|
LIMIT :limit`
|
|
27538
27722
|
);
|
|
27723
|
+
this.captureRowsStmt = db.prepare(
|
|
27724
|
+
`SELECT ${ROW_COLUMNS}
|
|
27725
|
+
FROM audit_events
|
|
27726
|
+
WHERE synced_at IS NULL
|
|
27727
|
+
AND sync_claimed_at IS NULL
|
|
27728
|
+
AND outbox_owed = 1
|
|
27729
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27730
|
+
AND started_at < :before
|
|
27731
|
+
ORDER BY started_at
|
|
27732
|
+
LIMIT :limit`
|
|
27733
|
+
);
|
|
27734
|
+
this.markOwedStmt = db.prepare(
|
|
27735
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27736
|
+
);
|
|
27539
27737
|
this.stampStmt = db.prepare(
|
|
27540
27738
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27541
27739
|
);
|
|
@@ -27567,6 +27765,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27567
27765
|
FROM audit_events
|
|
27568
27766
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27569
27767
|
);
|
|
27768
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27769
|
+
`SELECT COUNT(*) AS skipped
|
|
27770
|
+
FROM audit_events
|
|
27771
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27772
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27773
|
+
);
|
|
27570
27774
|
this.fingerprintStmt = db.prepare(
|
|
27571
27775
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27572
27776
|
FROM history_sync WHERE id = 1`
|
|
@@ -27576,6 +27780,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27576
27780
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27577
27781
|
WHERE id = 1`
|
|
27578
27782
|
);
|
|
27783
|
+
this.disownCapturesStmt = db.prepare(
|
|
27784
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27785
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27786
|
+
);
|
|
27579
27787
|
this.rearmStmt = db.prepare(
|
|
27580
27788
|
`UPDATE audit_events SET synced_at = NULL
|
|
27581
27789
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27648,6 +27856,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27648
27856
|
closeWindowStmt;
|
|
27649
27857
|
releaseBoundaryStmt;
|
|
27650
27858
|
freezeBoundaryStmt;
|
|
27859
|
+
captureRowsStmt;
|
|
27860
|
+
markOwedStmt;
|
|
27861
|
+
captureSkipCountStmt;
|
|
27862
|
+
disownCapturesStmt;
|
|
27651
27863
|
partitionStmt;
|
|
27652
27864
|
claimRowStmt;
|
|
27653
27865
|
releaseRowStmt;
|
|
@@ -27681,6 +27893,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27681
27893
|
pendingRows(sessionId, limit, before) {
|
|
27682
27894
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27683
27895
|
}
|
|
27896
|
+
/**
|
|
27897
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27898
|
+
*
|
|
27899
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27900
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27901
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27902
|
+
* the live path.
|
|
27903
|
+
*/
|
|
27904
|
+
pendingCaptureRows(limit, before) {
|
|
27905
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27906
|
+
}
|
|
27907
|
+
/**
|
|
27908
|
+
* Record that a capture is OWED to the deployment.
|
|
27909
|
+
*
|
|
27910
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27911
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27912
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27913
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27914
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27915
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27916
|
+
* offered to anyone.
|
|
27917
|
+
*
|
|
27918
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27919
|
+
* out of the drain's read.
|
|
27920
|
+
*/
|
|
27921
|
+
markCaptureOwed(id) {
|
|
27922
|
+
this.markOwedStmt.run({ id });
|
|
27923
|
+
}
|
|
27684
27924
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27685
27925
|
markSynced(ids, atMs) {
|
|
27686
27926
|
this.stampAll(ids, atMs);
|
|
@@ -27764,10 +28004,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27764
28004
|
this.countsStmt,
|
|
27765
28005
|
{ before }
|
|
27766
28006
|
);
|
|
28007
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27767
28008
|
return {
|
|
27768
28009
|
pending: row?.pending ?? 0,
|
|
27769
28010
|
sent: row?.sent ?? 0,
|
|
27770
|
-
skipped: row?.skipped ?? 0
|
|
28011
|
+
skipped: row?.skipped ?? 0,
|
|
28012
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27771
28013
|
};
|
|
27772
28014
|
}
|
|
27773
28015
|
/**
|
|
@@ -27808,7 +28050,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27808
28050
|
withTransaction(
|
|
27809
28051
|
this.db,
|
|
27810
28052
|
() => {
|
|
28053
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27811
28054
|
this.rearmStmt.run();
|
|
28055
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28056
|
+
this.disownCapturesStmt.run();
|
|
28057
|
+
}
|
|
27812
28058
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27813
28059
|
},
|
|
27814
28060
|
"IMMEDIATE"
|
|
@@ -28005,7 +28251,256 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
28005
28251
|
};
|
|
28006
28252
|
|
|
28007
28253
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
28008
|
-
import { createHash as createHash2, randomUUID as
|
|
28254
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28255
|
+
|
|
28256
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28257
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28258
|
+
import { join as join6 } from "path";
|
|
28259
|
+
|
|
28260
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28261
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28262
|
+
import { mkdir } from "fs/promises";
|
|
28263
|
+
import { homedir } from "os";
|
|
28264
|
+
import { join as join4 } from "path";
|
|
28265
|
+
function defaultDataDir() {
|
|
28266
|
+
return join4(homedir(), ".aka");
|
|
28267
|
+
}
|
|
28268
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28269
|
+
return join4(base, "settings");
|
|
28270
|
+
}
|
|
28271
|
+
function dataDir(base = defaultDataDir()) {
|
|
28272
|
+
return join4(base, "data");
|
|
28273
|
+
}
|
|
28274
|
+
function dbPath(base = defaultDataDir()) {
|
|
28275
|
+
return join4(dataDir(base), "aka.db");
|
|
28276
|
+
}
|
|
28277
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28278
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28279
|
+
tightenDir(dir);
|
|
28280
|
+
}
|
|
28281
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28282
|
+
ensureDataDirSync(dir);
|
|
28283
|
+
}
|
|
28284
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28285
|
+
const moves = [
|
|
28286
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28287
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28288
|
+
];
|
|
28289
|
+
for (const { name, dest } of moves) {
|
|
28290
|
+
try {
|
|
28291
|
+
ensureDataDirSync(dest);
|
|
28292
|
+
const moved = join4(dest, name);
|
|
28293
|
+
renameSync3(join4(base, name), moved);
|
|
28294
|
+
tightenFile(moved);
|
|
28295
|
+
} catch {
|
|
28296
|
+
}
|
|
28297
|
+
}
|
|
28298
|
+
}
|
|
28299
|
+
|
|
28300
|
+
// ../../packages/persistence/src/settings.ts
|
|
28301
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28302
|
+
import { join as join5 } from "path";
|
|
28303
|
+
|
|
28304
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28305
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28306
|
+
import {
|
|
28307
|
+
closeSync,
|
|
28308
|
+
existsSync as existsSync2,
|
|
28309
|
+
openSync,
|
|
28310
|
+
readFileSync as readFileSync2,
|
|
28311
|
+
rmSync as rmSync5,
|
|
28312
|
+
statSync as statSync3,
|
|
28313
|
+
writeFileSync as writeFileSync2
|
|
28314
|
+
} from "fs";
|
|
28315
|
+
import { hostname as hostname3 } from "os";
|
|
28316
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28317
|
+
|
|
28318
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28319
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28320
|
+
import { posix, win32 } from "path";
|
|
28321
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28322
|
+
if (platform2 === "darwin") {
|
|
28323
|
+
return [
|
|
28324
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28325
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28326
|
+
];
|
|
28327
|
+
}
|
|
28328
|
+
if (platform2 === "win32") {
|
|
28329
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28330
|
+
}
|
|
28331
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28332
|
+
}
|
|
28333
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28334
|
+
for (const path of paths) {
|
|
28335
|
+
let text;
|
|
28336
|
+
try {
|
|
28337
|
+
text = readFileSync3(path, "utf8");
|
|
28338
|
+
} catch {
|
|
28339
|
+
continue;
|
|
28340
|
+
}
|
|
28341
|
+
const record2 = parseJsonObject(text);
|
|
28342
|
+
if (!record2) continue;
|
|
28343
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28344
|
+
if (parsed2.success) return parsed2.data;
|
|
28345
|
+
}
|
|
28346
|
+
return null;
|
|
28347
|
+
}
|
|
28348
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28349
|
+
if (!managed) return settings;
|
|
28350
|
+
const { values } = managed;
|
|
28351
|
+
const merged = { ...settings };
|
|
28352
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28353
|
+
if (values.controlPlane !== void 0) {
|
|
28354
|
+
merged.controlPlane = {
|
|
28355
|
+
...values.controlPlane,
|
|
28356
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28357
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28358
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28359
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28360
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28361
|
+
};
|
|
28362
|
+
}
|
|
28363
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28364
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28365
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28366
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28367
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28368
|
+
if (values.vaultConsent !== void 0) {
|
|
28369
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28370
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28371
|
+
// at the current version otherwise.
|
|
28372
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28373
|
+
) : void 0;
|
|
28374
|
+
}
|
|
28375
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28376
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28377
|
+
acknowledgedAt: now().toISOString(),
|
|
28378
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28379
|
+
} : void 0;
|
|
28380
|
+
}
|
|
28381
|
+
return merged;
|
|
28382
|
+
}
|
|
28383
|
+
|
|
28384
|
+
// ../../packages/persistence/src/settings.ts
|
|
28385
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28386
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28387
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28388
|
+
}
|
|
28389
|
+
function readUserSettings(base) {
|
|
28390
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28391
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28392
|
+
try {
|
|
28393
|
+
return WorkspaceSettings.parse(record2);
|
|
28394
|
+
} catch {
|
|
28395
|
+
return defaultWorkspaceSettings();
|
|
28396
|
+
}
|
|
28397
|
+
}
|
|
28398
|
+
function readJson(file2) {
|
|
28399
|
+
let text;
|
|
28400
|
+
try {
|
|
28401
|
+
text = readFileSync4(file2, "utf8");
|
|
28402
|
+
} catch {
|
|
28403
|
+
return null;
|
|
28404
|
+
}
|
|
28405
|
+
return parseJsonObject(text) ?? null;
|
|
28406
|
+
}
|
|
28407
|
+
|
|
28408
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28409
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28410
|
+
switch (refusal) {
|
|
28411
|
+
case "lock":
|
|
28412
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28413
|
+
case "disable":
|
|
28414
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28415
|
+
case "floor":
|
|
28416
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28417
|
+
}
|
|
28418
|
+
}
|
|
28419
|
+
var PolicyFloorError = class extends Error {
|
|
28420
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28421
|
+
pack;
|
|
28422
|
+
/**
|
|
28423
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28424
|
+
* clearing the assignment, or switching the detection off.
|
|
28425
|
+
*/
|
|
28426
|
+
attempted;
|
|
28427
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28428
|
+
floor;
|
|
28429
|
+
refusal;
|
|
28430
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28431
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28432
|
+
this.name = "PolicyFloorError";
|
|
28433
|
+
this.pack = pack;
|
|
28434
|
+
this.attempted = attempted;
|
|
28435
|
+
this.floor = floor;
|
|
28436
|
+
this.refusal = refusal;
|
|
28437
|
+
}
|
|
28438
|
+
};
|
|
28439
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28440
|
+
try {
|
|
28441
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28442
|
+
const parsed2 = JSON.parse(raw);
|
|
28443
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28444
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28445
|
+
} catch {
|
|
28446
|
+
return null;
|
|
28447
|
+
}
|
|
28448
|
+
}
|
|
28449
|
+
function indexEnabled(policies) {
|
|
28450
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28451
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28452
|
+
for (const policy of policies) {
|
|
28453
|
+
if (!policy.enabled) continue;
|
|
28454
|
+
if ("ruleId" in policy.target) {
|
|
28455
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28456
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28457
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28458
|
+
}
|
|
28459
|
+
}
|
|
28460
|
+
return { byRuleId, byCategory };
|
|
28461
|
+
}
|
|
28462
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28463
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28464
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28465
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28466
|
+
return policies.some((policy) => {
|
|
28467
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28468
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28469
|
+
});
|
|
28470
|
+
}
|
|
28471
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28472
|
+
const floors = openControlPlaneFloors(base);
|
|
28473
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28474
|
+
}
|
|
28475
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28476
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28477
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28478
|
+
if (bundle === null) return null;
|
|
28479
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28480
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28481
|
+
}
|
|
28482
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28483
|
+
let action = null;
|
|
28484
|
+
for (const rule of rules) {
|
|
28485
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28486
|
+
if (resolved === void 0) continue;
|
|
28487
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28488
|
+
}
|
|
28489
|
+
if (action === null) return null;
|
|
28490
|
+
return {
|
|
28491
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28492
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28493
|
+
};
|
|
28494
|
+
}
|
|
28495
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28496
|
+
if (floor.locked) return "lock";
|
|
28497
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28498
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28499
|
+
}
|
|
28500
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28501
|
+
if (floor === null || enabled) return null;
|
|
28502
|
+
return "disable";
|
|
28503
|
+
}
|
|
28009
28504
|
|
|
28010
28505
|
// ../../packages/persistence/src/semver.ts
|
|
28011
28506
|
function parse3(version2) {
|
|
@@ -28099,8 +28594,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28099
28594
|
return ids;
|
|
28100
28595
|
}
|
|
28101
28596
|
var SqliteInstalledPacksRepository = class {
|
|
28102
|
-
|
|
28597
|
+
/**
|
|
28598
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28599
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28600
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28601
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28602
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28603
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28604
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28605
|
+
* real `~/.aka`.
|
|
28606
|
+
*/
|
|
28607
|
+
constructor(db, baseDir) {
|
|
28103
28608
|
this.db = db;
|
|
28609
|
+
this.baseDir = baseDir;
|
|
28104
28610
|
this.insertMissingStmt = db.prepare(
|
|
28105
28611
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28106
28612
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28122,11 +28628,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28122
28628
|
this.signatureStmt = db.prepare(
|
|
28123
28629
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28124
28630
|
);
|
|
28631
|
+
this.packRulesStmt = db.prepare(
|
|
28632
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28633
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28634
|
+
);
|
|
28125
28635
|
}
|
|
28126
28636
|
db;
|
|
28637
|
+
baseDir;
|
|
28127
28638
|
insertMissingStmt;
|
|
28128
28639
|
upsertAvailableStmt;
|
|
28129
28640
|
signatureStmt;
|
|
28641
|
+
packRulesStmt;
|
|
28130
28642
|
/**
|
|
28131
28643
|
* Record the running binary's detection inventory. Refreshes the
|
|
28132
28644
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28168,7 +28680,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28168
28680
|
let behind = false;
|
|
28169
28681
|
for (const row of rows) {
|
|
28170
28682
|
const params = {
|
|
28171
|
-
id:
|
|
28683
|
+
id: randomUUID4(),
|
|
28172
28684
|
namespace: row.namespace,
|
|
28173
28685
|
packId: row.packId,
|
|
28174
28686
|
version: row.version,
|
|
@@ -28180,7 +28692,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28180
28692
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28181
28693
|
this.upsertAvailableStmt.run({
|
|
28182
28694
|
...params,
|
|
28183
|
-
id:
|
|
28695
|
+
id: randomUUID4(),
|
|
28184
28696
|
recordedBy: meta4?.recordedBy ?? null
|
|
28185
28697
|
});
|
|
28186
28698
|
} else {
|
|
@@ -28426,9 +28938,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28426
28938
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28427
28939
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28428
28940
|
// caller can tell an edit from a no-such-detection.
|
|
28941
|
+
/**
|
|
28942
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
28943
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
28944
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
28945
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
28946
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
28947
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
28948
|
+
*/
|
|
28949
|
+
packFloorRules(namespace, packId) {
|
|
28950
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
28951
|
+
if (!row) return [];
|
|
28952
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
28953
|
+
}
|
|
28954
|
+
/**
|
|
28955
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
28956
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
28957
|
+
* repository constructed without a layout base).
|
|
28958
|
+
*
|
|
28959
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
28960
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
28961
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
28962
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
28963
|
+
*/
|
|
28964
|
+
policyFloor(namespace, packId) {
|
|
28965
|
+
if (this.baseDir === void 0) return null;
|
|
28966
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
28967
|
+
}
|
|
28968
|
+
/**
|
|
28969
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
28970
|
+
* entry only for a pack the control plane actually governs.
|
|
28971
|
+
*
|
|
28972
|
+
* A surface listing every detection asks per pack, and asking through
|
|
28973
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
28974
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
28975
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
28976
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
28977
|
+
* exactly as the single-pack read returns null for them.
|
|
28978
|
+
*/
|
|
28979
|
+
policyFloors(packs) {
|
|
28980
|
+
const floors = /* @__PURE__ */ new Map();
|
|
28981
|
+
if (this.baseDir === void 0) return floors;
|
|
28982
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
28983
|
+
if (source === null) return floors;
|
|
28984
|
+
for (const pack of packs) {
|
|
28985
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
28986
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
28987
|
+
}
|
|
28988
|
+
return floors;
|
|
28989
|
+
}
|
|
28429
28990
|
/**
|
|
28430
28991
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28431
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
28992
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
28993
|
+
*
|
|
28994
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
28995
|
+
* write below, and a detection the organization has authored a policy for is
|
|
28996
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
28997
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
28998
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
28999
|
+
* surface that offers the choice.
|
|
28432
29000
|
*/
|
|
28433
29001
|
setPolicy(namespace, packId, policyId) {
|
|
28434
29002
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28436,14 +29004,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28436
29004
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28437
29005
|
);
|
|
28438
29006
|
}
|
|
29007
|
+
const requested = policyId;
|
|
29008
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29009
|
+
if (floor !== null) {
|
|
29010
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
29011
|
+
if (refusal !== null) {
|
|
29012
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
29013
|
+
}
|
|
29014
|
+
}
|
|
28439
29015
|
const res = this.db.prepare(
|
|
28440
29016
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28441
29017
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28442
29018
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28443
29019
|
return Number(res.changes) > 0;
|
|
28444
29020
|
}
|
|
28445
|
-
/**
|
|
29021
|
+
/**
|
|
29022
|
+
* Enable or disable one installed pack.
|
|
29023
|
+
*
|
|
29024
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29025
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29026
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29027
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29028
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29029
|
+
* that lived in a page would leave the CLI free.
|
|
29030
|
+
*/
|
|
28446
29031
|
setEnabled(namespace, packId, enabled) {
|
|
29032
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29033
|
+
if (floor !== null) {
|
|
29034
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29035
|
+
if (refusal !== null) {
|
|
29036
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29037
|
+
}
|
|
29038
|
+
}
|
|
28447
29039
|
const res = this.db.prepare(
|
|
28448
29040
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28449
29041
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28529,7 +29121,7 @@ var SqliteInventoryRepository = class {
|
|
|
28529
29121
|
};
|
|
28530
29122
|
|
|
28531
29123
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28532
|
-
import { randomUUID as
|
|
29124
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28533
29125
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28534
29126
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28535
29127
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -29018,7 +29610,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29018
29610
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
29019
29611
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
29020
29612
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
29021
|
-
).run({ id:
|
|
29613
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
29022
29614
|
}
|
|
29023
29615
|
return true;
|
|
29024
29616
|
}
|
|
@@ -29039,7 +29631,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29039
29631
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29040
29632
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29041
29633
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29042
|
-
).run({ id:
|
|
29634
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29043
29635
|
}
|
|
29044
29636
|
this.configRowsCache = void 0;
|
|
29045
29637
|
return "ok";
|
|
@@ -29336,7 +29928,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29336
29928
|
};
|
|
29337
29929
|
|
|
29338
29930
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29339
|
-
import { randomUUID as
|
|
29931
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29340
29932
|
var SqlitePoliciesRepository = class {
|
|
29341
29933
|
constructor(db) {
|
|
29342
29934
|
this.db = db;
|
|
@@ -29371,7 +29963,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29371
29963
|
failOpenTransaction(this.db, () => {
|
|
29372
29964
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29373
29965
|
stmt.run({
|
|
29374
|
-
id:
|
|
29966
|
+
id: randomUUID6(),
|
|
29375
29967
|
target: JSON.stringify({ category }),
|
|
29376
29968
|
action,
|
|
29377
29969
|
now: Date.now()
|
|
@@ -29391,7 +29983,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29391
29983
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29392
29984
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29393
29985
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29394
|
-
).run({ id:
|
|
29986
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29395
29987
|
}
|
|
29396
29988
|
// Caps every global per-category policy currently set to block/redact down
|
|
29397
29989
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29459,7 +30051,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29459
30051
|
};
|
|
29460
30052
|
|
|
29461
30053
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29462
|
-
import { randomUUID as
|
|
30054
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29463
30055
|
var SqliteProjectFilesRepository = class {
|
|
29464
30056
|
constructor(db) {
|
|
29465
30057
|
this.db = db;
|
|
@@ -29491,7 +30083,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29491
30083
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29492
30084
|
for (const file2 of scan2.files) {
|
|
29493
30085
|
this.upsertStmt.run({
|
|
29494
|
-
id:
|
|
30086
|
+
id: randomUUID7(),
|
|
29495
30087
|
projectId,
|
|
29496
30088
|
path: file2.path,
|
|
29497
30089
|
name: file2.name,
|
|
@@ -29505,9 +30097,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29505
30097
|
};
|
|
29506
30098
|
|
|
29507
30099
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29508
|
-
import { randomUUID as
|
|
30100
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29509
30101
|
var SqliteResolutionsRepository = class {
|
|
29510
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30102
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29511
30103
|
this.db = db;
|
|
29512
30104
|
this.now = now;
|
|
29513
30105
|
this.newId = newId;
|
|
@@ -29720,7 +30312,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29720
30312
|
};
|
|
29721
30313
|
|
|
29722
30314
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29723
|
-
import { randomUUID as
|
|
30315
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29724
30316
|
function pageLimit(requested, fallback) {
|
|
29725
30317
|
if (requested === void 0) return fallback;
|
|
29726
30318
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29766,12 +30358,14 @@ var SELECT_COLUMNS = `
|
|
|
29766
30358
|
ciphertext,
|
|
29767
30359
|
nonce,
|
|
29768
30360
|
auth_tag AS authTag,
|
|
30361
|
+
user_authorized AS userAuthorized,
|
|
29769
30362
|
occurrence_count AS occurrenceCount,
|
|
29770
30363
|
first_seen AS firstSeen,
|
|
29771
30364
|
last_seen AS lastSeen`;
|
|
29772
30365
|
function toRow(raw) {
|
|
29773
|
-
const { provider, ...rest } = raw;
|
|
29774
|
-
|
|
30366
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30367
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30368
|
+
return provider === null ? row : { ...row, provider };
|
|
29775
30369
|
}
|
|
29776
30370
|
var SqliteSecretVaultRepository = class {
|
|
29777
30371
|
constructor(db) {
|
|
@@ -29781,17 +30375,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29781
30375
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29782
30376
|
format_version, category, rule_id, masked_match, provider,
|
|
29783
30377
|
ciphertext, nonce, auth_tag,
|
|
29784
|
-
occurrence_count, first_seen, last_seen
|
|
30378
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29785
30379
|
) VALUES (
|
|
29786
30380
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29787
30381
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29788
30382
|
:ciphertext, :nonce, :authTag,
|
|
29789
|
-
1, :now, :now
|
|
30383
|
+
:userAuthorized, 1, :now, :now
|
|
29790
30384
|
)`
|
|
29791
30385
|
);
|
|
29792
30386
|
this.bumpStmt = db.prepare(
|
|
29793
30387
|
`UPDATE secret_vault
|
|
29794
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30388
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30389
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29795
30390
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29796
30391
|
);
|
|
29797
30392
|
this.byPointerStmt = db.prepare(
|
|
@@ -29811,6 +30406,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29811
30406
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29812
30407
|
WHERE pointer_id = :pointerId`
|
|
29813
30408
|
);
|
|
30409
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29814
30410
|
this.derefStmt = db.prepare(
|
|
29815
30411
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29816
30412
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29824,6 +30420,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29824
30420
|
listStmt;
|
|
29825
30421
|
replaceCiphertextStmt;
|
|
29826
30422
|
refreshFingerprintStmt;
|
|
30423
|
+
deleteByPointerStmt;
|
|
29827
30424
|
derefStmt;
|
|
29828
30425
|
/**
|
|
29829
30426
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29832,6 +30429,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29832
30429
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29833
30430
|
* wire token. `minted` is true only when this call created the row.
|
|
29834
30431
|
*
|
|
30432
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30433
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30434
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30435
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30436
|
+
*
|
|
29835
30437
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29836
30438
|
* writers cannot both decide they are minting.
|
|
29837
30439
|
*/
|
|
@@ -29858,13 +30460,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29858
30460
|
ciphertext: input2.ciphertext,
|
|
29859
30461
|
nonce: input2.nonce,
|
|
29860
30462
|
authTag: input2.authTag,
|
|
30463
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29861
30464
|
now
|
|
29862
30465
|
})
|
|
29863
30466
|
);
|
|
29864
30467
|
minted = true;
|
|
29865
30468
|
return;
|
|
29866
30469
|
}
|
|
29867
|
-
this.bumpStmt.run({
|
|
30470
|
+
this.bumpStmt.run({
|
|
30471
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30472
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30473
|
+
now
|
|
30474
|
+
});
|
|
29868
30475
|
},
|
|
29869
30476
|
"IMMEDIATE"
|
|
29870
30477
|
);
|
|
@@ -29924,6 +30531,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29924
30531
|
);
|
|
29925
30532
|
return destroyed;
|
|
29926
30533
|
}
|
|
30534
|
+
/**
|
|
30535
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30536
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30537
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30538
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30539
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30540
|
+
* it.
|
|
30541
|
+
*
|
|
30542
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30543
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30544
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30545
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30546
|
+
* entry still sitting in the vault.
|
|
30547
|
+
*
|
|
30548
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30549
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30550
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30551
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30552
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30553
|
+
*/
|
|
30554
|
+
deleteByPointerIds(pointerIds) {
|
|
30555
|
+
if (pointerIds.length === 0) return [];
|
|
30556
|
+
const deleted = [];
|
|
30557
|
+
withTransaction(
|
|
30558
|
+
this.db,
|
|
30559
|
+
() => {
|
|
30560
|
+
for (const pointerId of pointerIds) {
|
|
30561
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30562
|
+
deleted.push(pointerId);
|
|
30563
|
+
}
|
|
30564
|
+
}
|
|
30565
|
+
},
|
|
30566
|
+
"IMMEDIATE"
|
|
30567
|
+
);
|
|
30568
|
+
return deleted;
|
|
30569
|
+
}
|
|
29927
30570
|
/**
|
|
29928
30571
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
29929
30572
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -29936,7 +30579,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29936
30579
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
29937
30580
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
29938
30581
|
).run({
|
|
29939
|
-
id:
|
|
30582
|
+
id: randomUUID9(),
|
|
29940
30583
|
pointerId: entry.pointerId,
|
|
29941
30584
|
location: entry.location,
|
|
29942
30585
|
kind: entry.kind,
|
|
@@ -30449,15 +31092,15 @@ var SqliteSecurityRepository = class {
|
|
|
30449
31092
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30450
31093
|
const rows = allRows(
|
|
30451
31094
|
this.db.prepare(
|
|
30452
|
-
`SELECT
|
|
31095
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30453
31096
|
FROM inspection_findings f
|
|
30454
31097
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30455
31098
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30456
31099
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30457
|
-
AND
|
|
30458
|
-
AND
|
|
30459
|
-
GROUP BY repo
|
|
30460
|
-
ORDER BY c DESC, repo
|
|
31100
|
+
AND e.repo IS NOT NULL
|
|
31101
|
+
AND e.repo != ''
|
|
31102
|
+
GROUP BY e.repo
|
|
31103
|
+
ORDER BY c DESC, e.repo
|
|
30461
31104
|
LIMIT :limit`
|
|
30462
31105
|
),
|
|
30463
31106
|
{ from, to: now, limit }
|
|
@@ -30519,7 +31162,7 @@ var SqliteSecurityRepository = class {
|
|
|
30519
31162
|
`SELECT f.finding_key AS finding_key,
|
|
30520
31163
|
d.rule_id AS rule_id,
|
|
30521
31164
|
d.severity AS severity,
|
|
30522
|
-
|
|
31165
|
+
e.file_path AS path,
|
|
30523
31166
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30524
31167
|
latest.resolved_at AS latest_resolved_at
|
|
30525
31168
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30572,7 +31215,7 @@ var SqliteSecurityRepository = class {
|
|
|
30572
31215
|
};
|
|
30573
31216
|
|
|
30574
31217
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30575
|
-
import { randomUUID as
|
|
31218
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30576
31219
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30577
31220
|
var IN_CHUNK = 500;
|
|
30578
31221
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30660,7 +31303,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30660
31303
|
callSiteCount,
|
|
30661
31304
|
transports: distinctTransports(transports),
|
|
30662
31305
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30663
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31306
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30664
31307
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30665
31308
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30666
31309
|
};
|
|
@@ -30687,7 +31330,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30687
31330
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30688
31331
|
transports: distinctTransports(transports),
|
|
30689
31332
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30690
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31333
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30691
31334
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30692
31335
|
note: dest.note,
|
|
30693
31336
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30716,7 +31359,11 @@ var SqliteSharesRepository = class {
|
|
|
30716
31359
|
FROM share_destination d
|
|
30717
31360
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30718
31361
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30719
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31362
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31363
|
+
AND NOT EXISTS (
|
|
31364
|
+
SELECT 1 FROM egress_decision_override o
|
|
31365
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31366
|
+
)`
|
|
30720
31367
|
);
|
|
30721
31368
|
const kindCounts = countBy(
|
|
30722
31369
|
this.db,
|
|
@@ -30828,7 +31475,7 @@ var SqliteSharesRepository = class {
|
|
|
30828
31475
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30829
31476
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30830
31477
|
).run({
|
|
30831
|
-
id:
|
|
31478
|
+
id: randomUUID10(),
|
|
30832
31479
|
destinationId,
|
|
30833
31480
|
host: dest.host,
|
|
30834
31481
|
decision,
|
|
@@ -30977,7 +31624,7 @@ var SqliteSharesRepository = class {
|
|
|
30977
31624
|
let destinationId = destIds.get(hit.host);
|
|
30978
31625
|
if (destinationId === void 0) {
|
|
30979
31626
|
destStmt.run({
|
|
30980
|
-
id:
|
|
31627
|
+
id: randomUUID10(),
|
|
30981
31628
|
kind: hit.kind,
|
|
30982
31629
|
name: hit.name,
|
|
30983
31630
|
host: hit.host,
|
|
@@ -30993,7 +31640,7 @@ var SqliteSharesRepository = class {
|
|
|
30993
31640
|
let endpointId = endpointIds.get(endpointKey);
|
|
30994
31641
|
if (endpointId === void 0) {
|
|
30995
31642
|
endpointStmt.run({
|
|
30996
|
-
id:
|
|
31643
|
+
id: randomUUID10(),
|
|
30997
31644
|
destinationId,
|
|
30998
31645
|
method: hit.method,
|
|
30999
31646
|
transport: hit.transport,
|
|
@@ -31006,7 +31653,7 @@ var SqliteSharesRepository = class {
|
|
|
31006
31653
|
endpointIds.set(endpointKey, endpointId);
|
|
31007
31654
|
}
|
|
31008
31655
|
siteStmt.run({
|
|
31009
|
-
id:
|
|
31656
|
+
id: randomUUID10(),
|
|
31010
31657
|
endpointId,
|
|
31011
31658
|
project: input2.project,
|
|
31012
31659
|
projectKey: input2.projectKey,
|
|
@@ -31371,6 +32018,7 @@ function purgeSampleData(db) {
|
|
|
31371
32018
|
}
|
|
31372
32019
|
|
|
31373
32020
|
// ../../packages/persistence/src/database.ts
|
|
32021
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31374
32022
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31375
32023
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31376
32024
|
);
|
|
@@ -31418,7 +32066,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31418
32066
|
discardStore(file2, backup);
|
|
31419
32067
|
return backup;
|
|
31420
32068
|
}
|
|
31421
|
-
function openAndInitialize(file2) {
|
|
32069
|
+
function openAndInitialize(file2, base) {
|
|
31422
32070
|
let db = openWithPragmas(file2);
|
|
31423
32071
|
try {
|
|
31424
32072
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31431,7 +32079,7 @@ function openAndInitialize(file2) {
|
|
|
31431
32079
|
applyMigrations(db, file2);
|
|
31432
32080
|
tightenPerms(file2);
|
|
31433
32081
|
const policies = new SqlitePoliciesRepository(db);
|
|
31434
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32082
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31435
32083
|
const repositories = {
|
|
31436
32084
|
events: new SqliteEventsRepository(db),
|
|
31437
32085
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31467,7 +32115,7 @@ function openAndInitialize(file2) {
|
|
|
31467
32115
|
}
|
|
31468
32116
|
function openLocalDatabase(dir) {
|
|
31469
32117
|
ensureDataDirSync(dir);
|
|
31470
|
-
const file2 =
|
|
32118
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31471
32119
|
reapStalePartials(file2);
|
|
31472
32120
|
const {
|
|
31473
32121
|
db,
|
|
@@ -31495,7 +32143,13 @@ function openLocalDatabase(dir) {
|
|
|
31495
32143
|
inspectionDefinitions,
|
|
31496
32144
|
inspectionFindings,
|
|
31497
32145
|
configInventory
|
|
31498
|
-
} = openAndInitialize(
|
|
32146
|
+
} = openAndInitialize(
|
|
32147
|
+
file2,
|
|
32148
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32149
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32150
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32151
|
+
dirname2(dir)
|
|
32152
|
+
);
|
|
31499
32153
|
function captureRowId(event) {
|
|
31500
32154
|
return captureId(
|
|
31501
32155
|
event.metadata?.sessionId ?? null,
|
|
@@ -31508,6 +32162,21 @@ function openLocalDatabase(dir) {
|
|
|
31508
32162
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31509
32163
|
});
|
|
31510
32164
|
}
|
|
32165
|
+
function markCaptureOwed(event) {
|
|
32166
|
+
failOpenTransaction(db, () => {
|
|
32167
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32168
|
+
});
|
|
32169
|
+
}
|
|
32170
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32171
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32172
|
+
if (stampable.length === 0) return;
|
|
32173
|
+
failOpenTransaction(db, () => {
|
|
32174
|
+
historySync.markSynced(
|
|
32175
|
+
stampable.map((event) => event.id),
|
|
32176
|
+
atMs
|
|
32177
|
+
);
|
|
32178
|
+
});
|
|
32179
|
+
}
|
|
31511
32180
|
function recordCapture(event, detected) {
|
|
31512
32181
|
failOpenTransaction(db, () => {
|
|
31513
32182
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31594,7 +32263,7 @@ function openLocalDatabase(dir) {
|
|
|
31594
32263
|
const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
|
|
31595
32264
|
if (!definitionId) continue;
|
|
31596
32265
|
inspectionFindings.insertFinding({
|
|
31597
|
-
id:
|
|
32266
|
+
id: randomUUID11(),
|
|
31598
32267
|
auditEventId: record2.scanEvent.id,
|
|
31599
32268
|
inspectionDefinitionId: definitionId,
|
|
31600
32269
|
span: finding.span,
|
|
@@ -31690,6 +32359,8 @@ function openLocalDatabase(dir) {
|
|
|
31690
32359
|
inspectionFindings,
|
|
31691
32360
|
recordCapture,
|
|
31692
32361
|
markCaptureDelivered,
|
|
32362
|
+
markCaptureOwed,
|
|
32363
|
+
markAuditEventsDelivered,
|
|
31693
32364
|
ensureInventory,
|
|
31694
32365
|
recordConfigScan,
|
|
31695
32366
|
recordProjectFiles,
|
|
@@ -31708,32 +32379,18 @@ function openLocalDatabase(dir) {
|
|
|
31708
32379
|
};
|
|
31709
32380
|
}
|
|
31710
32381
|
|
|
31711
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31712
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31713
|
-
import {
|
|
31714
|
-
closeSync,
|
|
31715
|
-
existsSync as existsSync2,
|
|
31716
|
-
openSync,
|
|
31717
|
-
readFileSync as readFileSync2,
|
|
31718
|
-
rmSync as rmSync5,
|
|
31719
|
-
statSync as statSync3,
|
|
31720
|
-
writeFileSync as writeFileSync2
|
|
31721
|
-
} from "fs";
|
|
31722
|
-
import { hostname as hostname3 } from "os";
|
|
31723
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31724
|
-
|
|
31725
32382
|
// ../../packages/persistence/src/finding-key.ts
|
|
31726
32383
|
import { createHash as createHash3 } from "crypto";
|
|
31727
32384
|
|
|
31728
32385
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31729
32386
|
import { createHmac, randomBytes } from "crypto";
|
|
31730
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31731
|
-
import { join as
|
|
32387
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32388
|
+
import { join as join8 } from "path";
|
|
31732
32389
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31733
32390
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31734
32391
|
var KEY_MATERIAL_BYTES = 32;
|
|
31735
32392
|
function keyFilePath(dataDir2) {
|
|
31736
|
-
return
|
|
32393
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31737
32394
|
}
|
|
31738
32395
|
function parseKeyFile(raw) {
|
|
31739
32396
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31756,7 +32413,7 @@ function parseKeyFile(raw) {
|
|
|
31756
32413
|
function readFingerprintKey(dataDir2) {
|
|
31757
32414
|
let raw;
|
|
31758
32415
|
try {
|
|
31759
|
-
raw =
|
|
32416
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31760
32417
|
} catch (err) {
|
|
31761
32418
|
if (err.code === "ENOENT") return null;
|
|
31762
32419
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31766,143 +32423,12 @@ function readFingerprintKey(dataDir2) {
|
|
|
31766
32423
|
|
|
31767
32424
|
// ../../packages/persistence/src/history-preview.ts
|
|
31768
32425
|
import { existsSync as existsSync4 } from "fs";
|
|
31769
|
-
import { join as
|
|
32426
|
+
import { join as join9 } from "path";
|
|
31770
32427
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31771
32428
|
|
|
31772
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31773
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31774
|
-
import { mkdir } from "fs/promises";
|
|
31775
|
-
import { homedir } from "os";
|
|
31776
|
-
import { join as join7 } from "path";
|
|
31777
|
-
function defaultDataDir() {
|
|
31778
|
-
return join7(homedir(), ".aka");
|
|
31779
|
-
}
|
|
31780
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31781
|
-
return join7(base, "settings");
|
|
31782
|
-
}
|
|
31783
|
-
function dataDir(base = defaultDataDir()) {
|
|
31784
|
-
return join7(base, "data");
|
|
31785
|
-
}
|
|
31786
|
-
function dbPath(base = defaultDataDir()) {
|
|
31787
|
-
return join7(dataDir(base), "aka.db");
|
|
31788
|
-
}
|
|
31789
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31790
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31791
|
-
tightenDir(dir);
|
|
31792
|
-
}
|
|
31793
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31794
|
-
ensureDataDirSync(dir);
|
|
31795
|
-
}
|
|
31796
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31797
|
-
const moves = [
|
|
31798
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31799
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31800
|
-
];
|
|
31801
|
-
for (const { name, dest } of moves) {
|
|
31802
|
-
try {
|
|
31803
|
-
ensureDataDirSync(dest);
|
|
31804
|
-
const moved = join7(dest, name);
|
|
31805
|
-
renameSync3(join7(base, name), moved);
|
|
31806
|
-
tightenFile(moved);
|
|
31807
|
-
} catch {
|
|
31808
|
-
}
|
|
31809
|
-
}
|
|
31810
|
-
}
|
|
31811
|
-
|
|
31812
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31813
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31814
|
-
import { posix, win32 } from "path";
|
|
31815
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31816
|
-
if (platform2 === "darwin") {
|
|
31817
|
-
return [
|
|
31818
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31819
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31820
|
-
];
|
|
31821
|
-
}
|
|
31822
|
-
if (platform2 === "win32") {
|
|
31823
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31824
|
-
}
|
|
31825
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31826
|
-
}
|
|
31827
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31828
|
-
for (const path of paths) {
|
|
31829
|
-
let text;
|
|
31830
|
-
try {
|
|
31831
|
-
text = readFileSync4(path, "utf8");
|
|
31832
|
-
} catch {
|
|
31833
|
-
continue;
|
|
31834
|
-
}
|
|
31835
|
-
const record2 = parseJsonObject(text);
|
|
31836
|
-
if (!record2) continue;
|
|
31837
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31838
|
-
if (parsed2.success) return parsed2.data;
|
|
31839
|
-
}
|
|
31840
|
-
return null;
|
|
31841
|
-
}
|
|
31842
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31843
|
-
if (!managed) return settings;
|
|
31844
|
-
const { values } = managed;
|
|
31845
|
-
const merged = { ...settings };
|
|
31846
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31847
|
-
if (values.controlPlane !== void 0) {
|
|
31848
|
-
merged.controlPlane = {
|
|
31849
|
-
...values.controlPlane,
|
|
31850
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31851
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31852
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31853
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31854
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31855
|
-
};
|
|
31856
|
-
}
|
|
31857
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31858
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31859
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31860
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31861
|
-
if (values.vaultConsent !== void 0) {
|
|
31862
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31863
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31864
|
-
// at the current version otherwise.
|
|
31865
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31866
|
-
) : void 0;
|
|
31867
|
-
}
|
|
31868
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31869
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31870
|
-
acknowledgedAt: now().toISOString(),
|
|
31871
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31872
|
-
} : void 0;
|
|
31873
|
-
}
|
|
31874
|
-
return merged;
|
|
31875
|
-
}
|
|
31876
|
-
|
|
31877
|
-
// ../../packages/persistence/src/settings.ts
|
|
31878
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31879
|
-
import { join as join8 } from "path";
|
|
31880
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31881
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31882
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31883
|
-
}
|
|
31884
|
-
function readUserSettings(base) {
|
|
31885
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31886
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31887
|
-
try {
|
|
31888
|
-
return WorkspaceSettings.parse(record2);
|
|
31889
|
-
} catch {
|
|
31890
|
-
return defaultWorkspaceSettings();
|
|
31891
|
-
}
|
|
31892
|
-
}
|
|
31893
|
-
function readJson(file2) {
|
|
31894
|
-
let text;
|
|
31895
|
-
try {
|
|
31896
|
-
text = readFileSync5(file2, "utf8");
|
|
31897
|
-
} catch {
|
|
31898
|
-
return null;
|
|
31899
|
-
}
|
|
31900
|
-
return parseJsonObject(text) ?? null;
|
|
31901
|
-
}
|
|
31902
|
-
|
|
31903
32429
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31904
32430
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31905
|
-
import { dirname as
|
|
32431
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31906
32432
|
|
|
31907
32433
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
31908
32434
|
import {
|
|
@@ -31916,19 +32442,19 @@ import {
|
|
|
31916
32442
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
31917
32443
|
import { execFileSync } from "child_process";
|
|
31918
32444
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
31919
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
31920
|
-
import { join as
|
|
32445
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32446
|
+
import { join as join11 } from "path";
|
|
31921
32447
|
|
|
31922
32448
|
// ../../packages/persistence/src/vault/vault.ts
|
|
31923
32449
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
31924
32450
|
|
|
31925
32451
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
31926
32452
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
31927
|
-
import { join as
|
|
32453
|
+
import { join as join12 } from "path";
|
|
31928
32454
|
var MARKER = "warn-era-capped";
|
|
31929
32455
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
31930
32456
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
31931
|
-
const marker =
|
|
32457
|
+
const marker = join12(dataDir2, MARKER);
|
|
31932
32458
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
31933
32459
|
const capped = db.policies.capCategoryActions();
|
|
31934
32460
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -31973,6 +32499,307 @@ function toEgressIngestRequest(input2) {
|
|
|
31973
32499
|
};
|
|
31974
32500
|
}
|
|
31975
32501
|
|
|
32502
|
+
// ../../packages/remote/src/http.ts
|
|
32503
|
+
import { request as httpRequest } from "http";
|
|
32504
|
+
import { request as httpsRequest } from "https";
|
|
32505
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32506
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32507
|
+
var RemoteRequestError = class extends Error {
|
|
32508
|
+
constructor(status) {
|
|
32509
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32510
|
+
this.status = status;
|
|
32511
|
+
this.name = "RemoteRequestError";
|
|
32512
|
+
}
|
|
32513
|
+
status;
|
|
32514
|
+
};
|
|
32515
|
+
var RemoteRouteAbsent = class extends Error {
|
|
32516
|
+
constructor(route) {
|
|
32517
|
+
super(`control plane does not serve ${route}`);
|
|
32518
|
+
this.route = route;
|
|
32519
|
+
this.name = "RemoteRouteAbsent";
|
|
32520
|
+
}
|
|
32521
|
+
route;
|
|
32522
|
+
};
|
|
32523
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32524
|
+
constructor(route, cause) {
|
|
32525
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32526
|
+
this.cause = cause;
|
|
32527
|
+
this.name = "RemoteRequestInvalid";
|
|
32528
|
+
}
|
|
32529
|
+
cause;
|
|
32530
|
+
};
|
|
32531
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32532
|
+
constructor(route, detail) {
|
|
32533
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32534
|
+
this.name = "RemoteResponseInvalid";
|
|
32535
|
+
}
|
|
32536
|
+
};
|
|
32537
|
+
var RemoteTransportError = class extends Error {
|
|
32538
|
+
/**
|
|
32539
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32540
|
+
* refused.
|
|
32541
|
+
*
|
|
32542
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32543
|
+
* all. It exists because two paths reject after a status has already been
|
|
32544
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32545
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32546
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32547
|
+
* credential.
|
|
32548
|
+
*/
|
|
32549
|
+
constructor(reason, status) {
|
|
32550
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32551
|
+
this.status = status;
|
|
32552
|
+
this.name = "RemoteTransportError";
|
|
32553
|
+
}
|
|
32554
|
+
status;
|
|
32555
|
+
};
|
|
32556
|
+
async function send(options) {
|
|
32557
|
+
const url2 = new URL(options.url);
|
|
32558
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32559
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32560
|
+
const requestOptions = {
|
|
32561
|
+
method: options.method,
|
|
32562
|
+
headers: {
|
|
32563
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32564
|
+
// last they win, and two of the values below are ones no caller may
|
|
32565
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32566
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32567
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32568
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32569
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32570
|
+
// conditional GET — is untouched by this order.
|
|
32571
|
+
...options.headers,
|
|
32572
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32573
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32574
|
+
// it can be logged by an intermediary for no gain.
|
|
32575
|
+
//
|
|
32576
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
32577
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
32578
|
+
// key differently from an absent one, and "the header is not there" is
|
|
32579
|
+
// the property the attach flow needs.
|
|
32580
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
32581
|
+
accept: "application/json",
|
|
32582
|
+
...options.body === void 0 ? {} : {
|
|
32583
|
+
"content-type": "application/json",
|
|
32584
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32585
|
+
// character count is truncated by the receiver.
|
|
32586
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32587
|
+
}
|
|
32588
|
+
}
|
|
32589
|
+
};
|
|
32590
|
+
return new Promise((resolve2, reject) => {
|
|
32591
|
+
let settled = false;
|
|
32592
|
+
const fail = (reason, status) => {
|
|
32593
|
+
if (settled) return;
|
|
32594
|
+
settled = true;
|
|
32595
|
+
reject(new RemoteTransportError(reason, status));
|
|
32596
|
+
};
|
|
32597
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32598
|
+
const chunks = [];
|
|
32599
|
+
let size = 0;
|
|
32600
|
+
res.on("data", (chunk) => {
|
|
32601
|
+
size += chunk.length;
|
|
32602
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32603
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32604
|
+
res.destroy();
|
|
32605
|
+
req.destroy();
|
|
32606
|
+
return;
|
|
32607
|
+
}
|
|
32608
|
+
chunks.push(chunk);
|
|
32609
|
+
});
|
|
32610
|
+
res.on("aborted", () => {
|
|
32611
|
+
fail("the response was aborted", res.statusCode);
|
|
32612
|
+
});
|
|
32613
|
+
res.on("end", () => {
|
|
32614
|
+
if (settled) return;
|
|
32615
|
+
settled = true;
|
|
32616
|
+
resolve2({
|
|
32617
|
+
status: res.statusCode ?? 0,
|
|
32618
|
+
headers: res.headers,
|
|
32619
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32620
|
+
});
|
|
32621
|
+
});
|
|
32622
|
+
});
|
|
32623
|
+
const deadline = setTimeout(() => {
|
|
32624
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32625
|
+
req.destroy();
|
|
32626
|
+
}, timeoutMs);
|
|
32627
|
+
deadline.unref();
|
|
32628
|
+
req.on("upgrade", (_res, socket) => {
|
|
32629
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32630
|
+
socket.destroy();
|
|
32631
|
+
});
|
|
32632
|
+
req.on("close", () => {
|
|
32633
|
+
fail("the connection closed before a response was read");
|
|
32634
|
+
clearTimeout(deadline);
|
|
32635
|
+
});
|
|
32636
|
+
req.on("error", (err) => {
|
|
32637
|
+
fail(err.message);
|
|
32638
|
+
});
|
|
32639
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32640
|
+
req.end();
|
|
32641
|
+
});
|
|
32642
|
+
}
|
|
32643
|
+
|
|
32644
|
+
// ../../packages/remote/src/client.ts
|
|
32645
|
+
var ROUTES = {
|
|
32646
|
+
events: "/v1/events",
|
|
32647
|
+
auditEvents: "/v1/audit-events",
|
|
32648
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
32649
|
+
inventory: "/v1/inventory",
|
|
32650
|
+
storePosture: "/v1/store-posture",
|
|
32651
|
+
policyBundle: "/v1/policy-bundle",
|
|
32652
|
+
whoami: "/v1/plugin/whoami",
|
|
32653
|
+
shares: "/v1/shares",
|
|
32654
|
+
commands: "/v1/plugin/commands"
|
|
32655
|
+
};
|
|
32656
|
+
function ackRoute(id) {
|
|
32657
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
32658
|
+
}
|
|
32659
|
+
function headerValue(response, name) {
|
|
32660
|
+
const raw = response.headers[name];
|
|
32661
|
+
if (raw === void 0) return void 0;
|
|
32662
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32663
|
+
}
|
|
32664
|
+
function okBody(response) {
|
|
32665
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32666
|
+
throw new RemoteRequestError(response.status);
|
|
32667
|
+
}
|
|
32668
|
+
return response.body;
|
|
32669
|
+
}
|
|
32670
|
+
function parsed(schema, body, route) {
|
|
32671
|
+
let json2;
|
|
32672
|
+
try {
|
|
32673
|
+
json2 = JSON.parse(body);
|
|
32674
|
+
} catch {
|
|
32675
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32676
|
+
}
|
|
32677
|
+
const result = schema.safeParse(json2);
|
|
32678
|
+
if (!result.success) {
|
|
32679
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32680
|
+
}
|
|
32681
|
+
return result.data;
|
|
32682
|
+
}
|
|
32683
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32684
|
+
let end = endpoint.length;
|
|
32685
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32686
|
+
return endpoint.slice(0, end);
|
|
32687
|
+
}
|
|
32688
|
+
var SLASH = "/".charCodeAt(0);
|
|
32689
|
+
function createRemoteClient(options) {
|
|
32690
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32691
|
+
const url2 = (route) => `${base}${route}`;
|
|
32692
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32693
|
+
const sendOne = async (event) => {
|
|
32694
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32695
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32696
|
+
const response = await send({
|
|
32697
|
+
...common,
|
|
32698
|
+
method: "POST",
|
|
32699
|
+
url: url2(ROUTES.auditEvents),
|
|
32700
|
+
body: JSON.stringify(validated.data)
|
|
32701
|
+
});
|
|
32702
|
+
okBody(response);
|
|
32703
|
+
};
|
|
32704
|
+
return {
|
|
32705
|
+
async ingestEvents(batch) {
|
|
32706
|
+
const response = await send({
|
|
32707
|
+
...common,
|
|
32708
|
+
method: "POST",
|
|
32709
|
+
url: url2(ROUTES.events),
|
|
32710
|
+
body: JSON.stringify(batch)
|
|
32711
|
+
});
|
|
32712
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32713
|
+
},
|
|
32714
|
+
async ingestInventory(context) {
|
|
32715
|
+
const response = await send({
|
|
32716
|
+
...common,
|
|
32717
|
+
method: "POST",
|
|
32718
|
+
url: url2(ROUTES.inventory),
|
|
32719
|
+
body: JSON.stringify(context)
|
|
32720
|
+
});
|
|
32721
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32722
|
+
},
|
|
32723
|
+
async recordAuditEvent(event) {
|
|
32724
|
+
await sendOne(event);
|
|
32725
|
+
},
|
|
32726
|
+
async recordAuditEvents(events, opts) {
|
|
32727
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
32728
|
+
if (!validated.success) {
|
|
32729
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
32730
|
+
}
|
|
32731
|
+
const response = await send({
|
|
32732
|
+
...common,
|
|
32733
|
+
method: "POST",
|
|
32734
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
32735
|
+
body: JSON.stringify(validated.data)
|
|
32736
|
+
});
|
|
32737
|
+
if (response.status === 404) {
|
|
32738
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
32739
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
32740
|
+
}
|
|
32741
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
32742
|
+
return { accepted: validated.data.events.length };
|
|
32743
|
+
}
|
|
32744
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
32745
|
+
},
|
|
32746
|
+
async reportStorePosture(snapshot) {
|
|
32747
|
+
const response = await send({
|
|
32748
|
+
...common,
|
|
32749
|
+
method: "POST",
|
|
32750
|
+
url: url2(ROUTES.storePosture),
|
|
32751
|
+
body: JSON.stringify(snapshot)
|
|
32752
|
+
});
|
|
32753
|
+
okBody(response);
|
|
32754
|
+
},
|
|
32755
|
+
async getPolicyBundle(etag) {
|
|
32756
|
+
const response = await send({
|
|
32757
|
+
...common,
|
|
32758
|
+
method: "GET",
|
|
32759
|
+
url: url2(ROUTES.policyBundle),
|
|
32760
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32761
|
+
});
|
|
32762
|
+
if (response.status === 304) {
|
|
32763
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32764
|
+
}
|
|
32765
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32766
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32767
|
+
},
|
|
32768
|
+
async whoami() {
|
|
32769
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32770
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32771
|
+
},
|
|
32772
|
+
async recordProjectEgress(request) {
|
|
32773
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
32774
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
32775
|
+
const response = await send({
|
|
32776
|
+
...common,
|
|
32777
|
+
method: "POST",
|
|
32778
|
+
url: url2(ROUTES.shares),
|
|
32779
|
+
body: JSON.stringify(validated.data)
|
|
32780
|
+
});
|
|
32781
|
+
okBody(response);
|
|
32782
|
+
},
|
|
32783
|
+
async pollCommand() {
|
|
32784
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
32785
|
+
if (response.status === 404) return null;
|
|
32786
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
32787
|
+
},
|
|
32788
|
+
async ackCommand(id, body) {
|
|
32789
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
32790
|
+
const route = ackRoute(id);
|
|
32791
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
32792
|
+
const response = await send({
|
|
32793
|
+
...common,
|
|
32794
|
+
method: "POST",
|
|
32795
|
+
url: url2(route),
|
|
32796
|
+
body: JSON.stringify(validated.data)
|
|
32797
|
+
});
|
|
32798
|
+
okBody(response);
|
|
32799
|
+
}
|
|
32800
|
+
};
|
|
32801
|
+
}
|
|
32802
|
+
|
|
31976
32803
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
31977
32804
|
function statusOf(err) {
|
|
31978
32805
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -31991,12 +32818,27 @@ function classifyFailure(err) {
|
|
|
31991
32818
|
}
|
|
31992
32819
|
}
|
|
31993
32820
|
|
|
32821
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
32822
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
32823
|
+
function withTimeout(promise2, ms) {
|
|
32824
|
+
let timer;
|
|
32825
|
+
const timeout = new Promise((_, reject) => {
|
|
32826
|
+
timer = setTimeout(() => {
|
|
32827
|
+
reject(new Error("attached gateway request timed out"));
|
|
32828
|
+
}, ms);
|
|
32829
|
+
});
|
|
32830
|
+
promise2.catch(() => void 0);
|
|
32831
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
32832
|
+
clearTimeout(timer);
|
|
32833
|
+
});
|
|
32834
|
+
}
|
|
32835
|
+
|
|
31994
32836
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
31995
|
-
import { readFileSync as
|
|
31996
|
-
import { join as
|
|
32837
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
32838
|
+
import { join as join13 } from "path";
|
|
31997
32839
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
31998
32840
|
function forwardDropsPath(dataDir2) {
|
|
31999
|
-
return
|
|
32841
|
+
return join13(dataDir2, FORWARD_DROPS_FILENAME);
|
|
32000
32842
|
}
|
|
32001
32843
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
32002
32844
|
if (count <= 0) return;
|
|
@@ -32014,7 +32856,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
32014
32856
|
}
|
|
32015
32857
|
function readForwardDrops(dataDir2) {
|
|
32016
32858
|
try {
|
|
32017
|
-
const parsed2 = JSON.parse(
|
|
32859
|
+
const parsed2 = JSON.parse(readFileSync8(forwardDropsPath(dataDir2), "utf8"));
|
|
32018
32860
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32019
32861
|
const record2 = parsed2;
|
|
32020
32862
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -32032,13 +32874,13 @@ function readForwardDrops(dataDir2) {
|
|
|
32032
32874
|
|
|
32033
32875
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
32034
32876
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
32035
|
-
import { readFileSync as
|
|
32877
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
32036
32878
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
32037
|
-
import { join as
|
|
32879
|
+
import { join as join22 } from "path";
|
|
32038
32880
|
|
|
32039
32881
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32040
32882
|
import { existsSync as existsSync7 } from "fs";
|
|
32041
|
-
import { join as
|
|
32883
|
+
import { join as join14 } from "path";
|
|
32042
32884
|
|
|
32043
32885
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32044
32886
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32092,7 +32934,7 @@ function resolveProvider() {
|
|
|
32092
32934
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32093
32935
|
try {
|
|
32094
32936
|
ensureLayoutDirSync(base);
|
|
32095
|
-
const settingsFile =
|
|
32937
|
+
const settingsFile = join14(settingsDir(base), "settings.json");
|
|
32096
32938
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32097
32939
|
} catch {
|
|
32098
32940
|
}
|
|
@@ -32116,9 +32958,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32116
32958
|
}
|
|
32117
32959
|
|
|
32118
32960
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32119
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
32961
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32120
32962
|
import { homedir as homedir2 } from "os";
|
|
32121
|
-
import { basename as basename3, join as
|
|
32963
|
+
import { basename as basename3, join as join16 } from "path";
|
|
32122
32964
|
|
|
32123
32965
|
// ../../packages/detections/src/egress/registry.ts
|
|
32124
32966
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -34901,8 +35743,8 @@ function bundledDetections() {
|
|
|
34901
35743
|
}
|
|
34902
35744
|
|
|
34903
35745
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
34904
|
-
import { existsSync as existsSync8, readFileSync as
|
|
34905
|
-
import { basename as basename2, dirname as
|
|
35746
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
35747
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
|
|
34906
35748
|
|
|
34907
35749
|
// ../../packages/plugin-sdk/src/events.ts
|
|
34908
35750
|
import { createHash as createHash5, randomUUID as randomUUID13 } from "crypto";
|
|
@@ -34914,8 +35756,8 @@ import { Worker } from "worker_threads";
|
|
|
34914
35756
|
|
|
34915
35757
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
34916
35758
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
34917
|
-
import { readFileSync as
|
|
34918
|
-
import { join as
|
|
35759
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
35760
|
+
import { join as join17 } from "path";
|
|
34919
35761
|
|
|
34920
35762
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
34921
35763
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -34926,24 +35768,24 @@ import {
|
|
|
34926
35768
|
fstatSync,
|
|
34927
35769
|
mkdirSync as mkdirSync2,
|
|
34928
35770
|
openSync as openSync2,
|
|
34929
|
-
readFileSync as
|
|
35771
|
+
readFileSync as readFileSync12,
|
|
34930
35772
|
readSync,
|
|
34931
35773
|
writeFileSync as writeFileSync5
|
|
34932
35774
|
} from "fs";
|
|
34933
|
-
import { join as
|
|
35775
|
+
import { join as join18 } from "path";
|
|
34934
35776
|
var TAIL_BYTES = 256 * 1024;
|
|
34935
35777
|
|
|
34936
35778
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
34937
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
34938
|
-
import { join as
|
|
35779
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
35780
|
+
import { join as join19 } from "path";
|
|
34939
35781
|
|
|
34940
35782
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
34941
35783
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
34942
|
-
import { basename as basename4, dirname as
|
|
35784
|
+
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
34943
35785
|
|
|
34944
35786
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
34945
35787
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
34946
|
-
import { basename as basename5, join as
|
|
35788
|
+
import { basename as basename5, join as join20 } from "path";
|
|
34947
35789
|
|
|
34948
35790
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
34949
35791
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -34979,27 +35821,19 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
34979
35821
|
|
|
34980
35822
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
34981
35823
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
34982
|
-
import { join as
|
|
34983
|
-
|
|
34984
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
34985
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
34986
|
-
function withTimeout(promise2, ms) {
|
|
34987
|
-
let timer;
|
|
34988
|
-
const timeout = new Promise((_, reject) => {
|
|
34989
|
-
timer = setTimeout(() => {
|
|
34990
|
-
reject(new Error("attached gateway request timed out"));
|
|
34991
|
-
}, ms);
|
|
34992
|
-
});
|
|
34993
|
-
promise2.catch(() => void 0);
|
|
34994
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
34995
|
-
clearTimeout(timer);
|
|
34996
|
-
});
|
|
34997
|
-
}
|
|
35824
|
+
import { join as join21 } from "path";
|
|
34998
35825
|
|
|
34999
35826
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
35000
35827
|
function isInvalidRequest(err) {
|
|
35001
35828
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
35002
35829
|
}
|
|
35830
|
+
function isRouteAbsent(err) {
|
|
35831
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
35832
|
+
}
|
|
35833
|
+
function isServerRejection(err) {
|
|
35834
|
+
const status = statusOf(err);
|
|
35835
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
35836
|
+
}
|
|
35003
35837
|
var FORWARD_BUDGET_MS = 1500;
|
|
35004
35838
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
35005
35839
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -35027,7 +35861,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
35027
35861
|
}
|
|
35028
35862
|
function createForwardPolicy(deps) {
|
|
35029
35863
|
const now = deps.now ?? (() => Date.now());
|
|
35030
|
-
const file2 =
|
|
35864
|
+
const file2 = join22(deps.dir, STATE_FILENAME);
|
|
35031
35865
|
let state = null;
|
|
35032
35866
|
let loading = null;
|
|
35033
35867
|
async function readState() {
|
|
@@ -35067,6 +35901,20 @@ function createForwardPolicy(deps) {
|
|
|
35067
35901
|
} catch {
|
|
35068
35902
|
current = { ...CLOSED };
|
|
35069
35903
|
}
|
|
35904
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
35905
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
35906
|
+
openedAtMs,
|
|
35907
|
+
lastFailure: current.lastFailure
|
|
35908
|
+
});
|
|
35909
|
+
const recordFailure = (cause) => {
|
|
35910
|
+
const failures = current.consecutiveFailures + 1;
|
|
35911
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
35912
|
+
return persist({
|
|
35913
|
+
consecutiveFailures: failures,
|
|
35914
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
35915
|
+
lastFailure: cause
|
|
35916
|
+
});
|
|
35917
|
+
};
|
|
35070
35918
|
const at = now();
|
|
35071
35919
|
if (current.openedAtMs !== null) {
|
|
35072
35920
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -35085,15 +35933,20 @@ function createForwardPolicy(deps) {
|
|
|
35085
35933
|
}
|
|
35086
35934
|
return { ok: true, value };
|
|
35087
35935
|
} catch (err) {
|
|
35088
|
-
if (isInvalidRequest(err))
|
|
35936
|
+
if (isInvalidRequest(err)) {
|
|
35937
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
35938
|
+
return { ok: false, reason: "invalid-request" };
|
|
35939
|
+
}
|
|
35940
|
+
if (isRouteAbsent(err)) {
|
|
35941
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
35942
|
+
return { ok: false, reason: "route-absent" };
|
|
35943
|
+
}
|
|
35944
|
+
if (isServerRejection(err)) {
|
|
35945
|
+
await recordFailure("unreachable");
|
|
35946
|
+
return { ok: false, reason: "rejected" };
|
|
35947
|
+
}
|
|
35089
35948
|
const reason = classifyFailure(err);
|
|
35090
|
-
|
|
35091
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
35092
|
-
await persist({
|
|
35093
|
-
consecutiveFailures: failures,
|
|
35094
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
35095
|
-
lastFailure: reason
|
|
35096
|
-
});
|
|
35949
|
+
await recordFailure(reason);
|
|
35097
35950
|
return { ok: false, reason };
|
|
35098
35951
|
}
|
|
35099
35952
|
}
|
|
@@ -35101,13 +35954,11 @@ function createForwardPolicy(deps) {
|
|
|
35101
35954
|
}
|
|
35102
35955
|
|
|
35103
35956
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
|
|
35109
|
-
block: 4
|
|
35110
|
-
};
|
|
35957
|
+
function strongerOf(a, b) {
|
|
35958
|
+
if (a === null) return b;
|
|
35959
|
+
if (b === null) return a;
|
|
35960
|
+
return strongerAction(a, b);
|
|
35961
|
+
}
|
|
35111
35962
|
function ruleCategoryMap(wireRules, localRules) {
|
|
35112
35963
|
const map2 = /* @__PURE__ */ new Map();
|
|
35113
35964
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -35117,11 +35968,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
35117
35968
|
}
|
|
35118
35969
|
return map2;
|
|
35119
35970
|
}
|
|
35120
|
-
function strongerOf(a, b) {
|
|
35121
|
-
if (a === null) return b;
|
|
35122
|
-
if (b === null) return a;
|
|
35123
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
35124
|
-
}
|
|
35125
35971
|
function policyKey(policy) {
|
|
35126
35972
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
35127
35973
|
}
|
|
@@ -35140,7 +35986,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
35140
35986
|
const floor = floorFor(policy, categoryByRuleId);
|
|
35141
35987
|
remoteCategoryAction.set(
|
|
35142
35988
|
policy.target.category,
|
|
35143
|
-
floor !== null &&
|
|
35989
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
35144
35990
|
);
|
|
35145
35991
|
}
|
|
35146
35992
|
for (const policy of localPolicies) {
|
|
@@ -35157,7 +36003,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
35157
36003
|
}
|
|
35158
36004
|
merged.set(
|
|
35159
36005
|
key,
|
|
35160
|
-
remoteFloor !== null &&
|
|
36006
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
35161
36007
|
);
|
|
35162
36008
|
}
|
|
35163
36009
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -35177,13 +36023,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
35177
36023
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
35178
36024
|
}
|
|
35179
36025
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
35180
|
-
const clamped = effectiveFloor !== null &&
|
|
36026
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
35181
36027
|
const existing = merged.get(key);
|
|
35182
36028
|
if (existing === void 0) {
|
|
35183
36029
|
merged.set(key, clamped);
|
|
35184
36030
|
continue;
|
|
35185
36031
|
}
|
|
35186
|
-
if (
|
|
36032
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
35187
36033
|
merged.set(key, clamped);
|
|
35188
36034
|
}
|
|
35189
36035
|
}
|
|
@@ -35216,6 +36062,8 @@ var AttachedDataGateway = class {
|
|
|
35216
36062
|
);
|
|
35217
36063
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
35218
36064
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
36065
|
+
} else {
|
|
36066
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
35219
36067
|
}
|
|
35220
36068
|
}
|
|
35221
36069
|
async ensureInventory(ctx) {
|
|
@@ -35252,9 +36100,10 @@ var AttachedDataGateway = class {
|
|
|
35252
36100
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
35253
36101
|
async recordAuditEvent(event) {
|
|
35254
36102
|
await this.deps.local.recordAuditEvent(event);
|
|
35255
|
-
await this.deps.forward.run(
|
|
36103
|
+
const forwarded = await this.deps.forward.run(
|
|
35256
36104
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
35257
36105
|
);
|
|
36106
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
35258
36107
|
}
|
|
35259
36108
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
35260
36109
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -35263,44 +36112,170 @@ var AttachedDataGateway = class {
|
|
|
35263
36112
|
// which would write the event to the local store a second time.
|
|
35264
36113
|
async recordLlmCall(input2) {
|
|
35265
36114
|
await this.deps.local.recordLlmCall(input2);
|
|
35266
|
-
|
|
35267
|
-
|
|
35268
|
-
|
|
35269
|
-
)
|
|
36115
|
+
const event = llmAuditEvent(input2);
|
|
36116
|
+
const forwarded = await this.deps.forward.run(
|
|
36117
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
35270
36118
|
);
|
|
36119
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
35271
36120
|
}
|
|
35272
36121
|
/**
|
|
35273
|
-
* Forward one batch
|
|
36122
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
36123
|
+
*
|
|
36124
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
36125
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
36126
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
36127
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
36128
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
36129
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
36130
|
+
* client-side, so the chunking here is not a convention.
|
|
36131
|
+
*
|
|
36132
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
36133
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
36134
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
36135
|
+
* the fix; more concurrent ones is not.
|
|
35274
36136
|
*
|
|
35275
|
-
*
|
|
35276
|
-
*
|
|
35277
|
-
*
|
|
35278
|
-
*
|
|
35279
|
-
*
|
|
36137
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
36138
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
36139
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
36140
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
36141
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
36142
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
36143
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
36144
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
36145
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
36146
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
36147
|
+
* the same invisibility with a different cause.
|
|
35280
36148
|
*
|
|
35281
|
-
*
|
|
35282
|
-
*
|
|
35283
|
-
*
|
|
36149
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
36150
|
+
* single-event ack and at fifty times the blast radius here:
|
|
36151
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
36152
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
36153
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
36154
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
36155
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
36156
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
36157
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
36158
|
+
* resend by.
|
|
35284
36159
|
*
|
|
35285
|
-
*
|
|
35286
|
-
*
|
|
35287
|
-
*
|
|
35288
|
-
*
|
|
35289
|
-
*
|
|
35290
|
-
*
|
|
36160
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
36161
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
36162
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
36163
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
36164
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
36165
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
36166
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
36167
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
36168
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
36169
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
36170
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
36171
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
36172
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
36173
|
+
* rather than toward the lost row the alternative risks.
|
|
36174
|
+
*
|
|
36175
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
36176
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
36177
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
36178
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
36179
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
36180
|
+
* time:
|
|
36181
|
+
*
|
|
36182
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
36183
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
36184
|
+
* a new way to lose data introduced by the very change
|
|
36185
|
+
* meant to stop losing it.
|
|
36186
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
36187
|
+
* single-event route is the one it serves, and re-sending
|
|
36188
|
+
* here rather than inside the client is what gives each
|
|
36189
|
+
* request its own budget instead of 50 inside one.
|
|
36190
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
36191
|
+
* a 4xx body refusal from schema drift on the other side
|
|
36192
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
36193
|
+
* exactly as on the others, so leaving it out would cost
|
|
36194
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
36195
|
+
* malformed, where the per-item form cost only that one.
|
|
36196
|
+
*
|
|
36197
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
36198
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
36199
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
36200
|
+
* batching.
|
|
35291
36201
|
*/
|
|
35292
36202
|
async forwardBatch(inputs, toEvent) {
|
|
35293
36203
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
35294
|
-
|
|
35295
|
-
|
|
35296
|
-
|
|
35297
|
-
|
|
35298
|
-
|
|
36204
|
+
const delivered = [];
|
|
36205
|
+
try {
|
|
36206
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
36207
|
+
const now = Date.now();
|
|
36208
|
+
if (now >= deadline) {
|
|
36209
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
36210
|
+
return;
|
|
36211
|
+
}
|
|
36212
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
36213
|
+
const forwarded = await this.deps.forward.run(
|
|
36214
|
+
() => this.deps.client.recordAuditEvents(
|
|
36215
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
36216
|
+
)
|
|
36217
|
+
);
|
|
36218
|
+
if (forwarded.ok) {
|
|
36219
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
36220
|
+
delivered.push(...chunk);
|
|
36221
|
+
continue;
|
|
36222
|
+
}
|
|
36223
|
+
} else if (
|
|
36224
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
36225
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
36226
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
36227
|
+
//
|
|
36228
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
36229
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
36230
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
36231
|
+
//
|
|
36232
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
36233
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
36234
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
36235
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
36236
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
36237
|
+
// budget wrapping this call — turning a working older deployment into
|
|
36238
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
36239
|
+
// a silent drop while the status surface called an answering
|
|
36240
|
+
// deployment down.
|
|
36241
|
+
//
|
|
36242
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
36243
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
36244
|
+
// costs one event instead of the whole chunk for a defect the
|
|
36245
|
+
// deployment considers local to one row.
|
|
36246
|
+
//
|
|
36247
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
36248
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
36249
|
+
// budget failing 50 more times.
|
|
36250
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
36251
|
+
) {
|
|
36252
|
+
continue;
|
|
36253
|
+
}
|
|
36254
|
+
for (const [j, event] of chunk.entries()) {
|
|
36255
|
+
const at = Date.now();
|
|
36256
|
+
if (at >= deadline) {
|
|
36257
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
36258
|
+
return;
|
|
36259
|
+
}
|
|
36260
|
+
const single = await this.deps.forward.run(
|
|
36261
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
36262
|
+
);
|
|
36263
|
+
if (single.ok) {
|
|
36264
|
+
delivered.push(event);
|
|
36265
|
+
continue;
|
|
36266
|
+
}
|
|
36267
|
+
if (single.reason === "breaker-open") {
|
|
36268
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
36269
|
+
return;
|
|
36270
|
+
}
|
|
36271
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
36272
|
+
}
|
|
36273
|
+
}
|
|
36274
|
+
} finally {
|
|
36275
|
+
try {
|
|
36276
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
36277
|
+
} catch {
|
|
35299
36278
|
}
|
|
35300
|
-
const input2 = inputs[i];
|
|
35301
|
-
await this.deps.forward.run(
|
|
35302
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
35303
|
-
);
|
|
35304
36279
|
}
|
|
35305
36280
|
}
|
|
35306
36281
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -35343,9 +36318,10 @@ var AttachedDataGateway = class {
|
|
|
35343
36318
|
// local store.
|
|
35344
36319
|
async recordConfigScan(record2) {
|
|
35345
36320
|
await this.deps.local.recordConfigScan(record2);
|
|
35346
|
-
await this.deps.forward.run(
|
|
36321
|
+
const forwarded = await this.deps.forward.run(
|
|
35347
36322
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
35348
36323
|
);
|
|
36324
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
35349
36325
|
}
|
|
35350
36326
|
async recordBlockedDetection(entry) {
|
|
35351
36327
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -35479,6 +36455,18 @@ var AttachedDataGateway = class {
|
|
|
35479
36455
|
// exactly what it did, leaving the whole control inert on every device
|
|
35480
36456
|
// while every test around it stayed green.
|
|
35481
36457
|
prohibitedModels: cached2.prohibitedModels
|
|
36458
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
36459
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
36460
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
36461
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
36462
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
36463
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
36464
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
36465
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
36466
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
36467
|
+
// would be the silent failure rather than the safe one — the action would
|
|
36468
|
+
// still be enforced while the local override the organization authored
|
|
36469
|
+
// away quietly came back.
|
|
35482
36470
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
35483
36471
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
35484
36472
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -35518,10 +36506,10 @@ var AttachedDataGateway = class {
|
|
|
35518
36506
|
//
|
|
35519
36507
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
35520
36508
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
35521
|
-
// any object carrying all
|
|
36509
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
35522
36510
|
// runs maintenance on the device's real store.
|
|
35523
36511
|
//
|
|
35524
|
-
// ⚠
|
|
36512
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
35525
36513
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
35526
36514
|
// return value directly; declaring them `async` here would hand those call
|
|
35527
36515
|
// sites a Promise and silently break both.
|
|
@@ -35544,9 +36532,15 @@ var AttachedDataGateway = class {
|
|
|
35544
36532
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
35545
36533
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
35546
36534
|
// path that has nothing left to await.
|
|
36535
|
+
markCaptureOwed(event) {
|
|
36536
|
+
this.deps.local.markCaptureOwed(event);
|
|
36537
|
+
}
|
|
35547
36538
|
markCaptureDelivered(event, atMs) {
|
|
35548
36539
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
35549
36540
|
}
|
|
36541
|
+
markAuditEventsDelivered(events, atMs) {
|
|
36542
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
36543
|
+
}
|
|
35550
36544
|
};
|
|
35551
36545
|
function reKeyForForward(event, remote) {
|
|
35552
36546
|
if (remote === null) {
|
|
@@ -35589,281 +36583,17 @@ function toolAuditEvent(input2) {
|
|
|
35589
36583
|
}
|
|
35590
36584
|
|
|
35591
36585
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
35592
|
-
import { readFileSync as
|
|
35593
|
-
import { join as
|
|
36586
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
36587
|
+
import { join as join23 } from "path";
|
|
35594
36588
|
|
|
35595
36589
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
35596
36590
|
import { createHash as createHash6 } from "crypto";
|
|
35597
36591
|
import { hostname as hostname5 } from "os";
|
|
35598
36592
|
|
|
35599
|
-
// ../../packages/
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
var
|
|
35603
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
35604
|
-
var RemoteRequestError = class extends Error {
|
|
35605
|
-
constructor(status) {
|
|
35606
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
35607
|
-
this.status = status;
|
|
35608
|
-
this.name = "RemoteRequestError";
|
|
35609
|
-
}
|
|
35610
|
-
status;
|
|
35611
|
-
};
|
|
35612
|
-
var RemoteRequestInvalid = class extends Error {
|
|
35613
|
-
constructor(route, cause) {
|
|
35614
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
35615
|
-
this.cause = cause;
|
|
35616
|
-
this.name = "RemoteRequestInvalid";
|
|
35617
|
-
}
|
|
35618
|
-
cause;
|
|
35619
|
-
};
|
|
35620
|
-
var RemoteResponseInvalid = class extends Error {
|
|
35621
|
-
constructor(route, detail) {
|
|
35622
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
35623
|
-
this.name = "RemoteResponseInvalid";
|
|
35624
|
-
}
|
|
35625
|
-
};
|
|
35626
|
-
var RemoteTransportError = class extends Error {
|
|
35627
|
-
/**
|
|
35628
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
35629
|
-
* refused.
|
|
35630
|
-
*
|
|
35631
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
35632
|
-
* all. It exists because two paths reject after a status has already been
|
|
35633
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
35634
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
35635
|
-
* outage, which sends the reader to look at their network instead of their
|
|
35636
|
-
* credential.
|
|
35637
|
-
*/
|
|
35638
|
-
constructor(reason, status) {
|
|
35639
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
35640
|
-
this.status = status;
|
|
35641
|
-
this.name = "RemoteTransportError";
|
|
35642
|
-
}
|
|
35643
|
-
status;
|
|
35644
|
-
};
|
|
35645
|
-
async function send(options) {
|
|
35646
|
-
const url2 = new URL(options.url);
|
|
35647
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
35648
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
35649
|
-
const requestOptions = {
|
|
35650
|
-
method: options.method,
|
|
35651
|
-
headers: {
|
|
35652
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
35653
|
-
// last they win, and two of the values below are ones no caller may
|
|
35654
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
35655
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
35656
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
35657
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
35658
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
35659
|
-
// conditional GET — is untouched by this order.
|
|
35660
|
-
...options.headers,
|
|
35661
|
-
// The credential. One header, matching what the deployment authenticates
|
|
35662
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
35663
|
-
// it can be logged by an intermediary for no gain.
|
|
35664
|
-
//
|
|
35665
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
35666
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
35667
|
-
// key differently from an absent one, and "the header is not there" is
|
|
35668
|
-
// the property the attach flow needs.
|
|
35669
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
35670
|
-
accept: "application/json",
|
|
35671
|
-
...options.body === void 0 ? {} : {
|
|
35672
|
-
"content-type": "application/json",
|
|
35673
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
35674
|
-
// character count is truncated by the receiver.
|
|
35675
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
35676
|
-
}
|
|
35677
|
-
}
|
|
35678
|
-
};
|
|
35679
|
-
return new Promise((resolve2, reject) => {
|
|
35680
|
-
let settled = false;
|
|
35681
|
-
const fail = (reason, status) => {
|
|
35682
|
-
if (settled) return;
|
|
35683
|
-
settled = true;
|
|
35684
|
-
reject(new RemoteTransportError(reason, status));
|
|
35685
|
-
};
|
|
35686
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
35687
|
-
const chunks = [];
|
|
35688
|
-
let size = 0;
|
|
35689
|
-
res.on("data", (chunk) => {
|
|
35690
|
-
size += chunk.length;
|
|
35691
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
35692
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
35693
|
-
res.destroy();
|
|
35694
|
-
req.destroy();
|
|
35695
|
-
return;
|
|
35696
|
-
}
|
|
35697
|
-
chunks.push(chunk);
|
|
35698
|
-
});
|
|
35699
|
-
res.on("aborted", () => {
|
|
35700
|
-
fail("the response was aborted", res.statusCode);
|
|
35701
|
-
});
|
|
35702
|
-
res.on("end", () => {
|
|
35703
|
-
if (settled) return;
|
|
35704
|
-
settled = true;
|
|
35705
|
-
resolve2({
|
|
35706
|
-
status: res.statusCode ?? 0,
|
|
35707
|
-
headers: res.headers,
|
|
35708
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
35709
|
-
});
|
|
35710
|
-
});
|
|
35711
|
-
});
|
|
35712
|
-
const deadline = setTimeout(() => {
|
|
35713
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
35714
|
-
req.destroy();
|
|
35715
|
-
}, timeoutMs);
|
|
35716
|
-
deadline.unref();
|
|
35717
|
-
req.on("upgrade", (_res, socket) => {
|
|
35718
|
-
fail("the deployment answered with a protocol upgrade");
|
|
35719
|
-
socket.destroy();
|
|
35720
|
-
});
|
|
35721
|
-
req.on("close", () => {
|
|
35722
|
-
fail("the connection closed before a response was read");
|
|
35723
|
-
clearTimeout(deadline);
|
|
35724
|
-
});
|
|
35725
|
-
req.on("error", (err) => {
|
|
35726
|
-
fail(err.message);
|
|
35727
|
-
});
|
|
35728
|
-
if (options.body !== void 0) req.write(options.body);
|
|
35729
|
-
req.end();
|
|
35730
|
-
});
|
|
35731
|
-
}
|
|
35732
|
-
|
|
35733
|
-
// ../../packages/remote/src/client.ts
|
|
35734
|
-
var ROUTES = {
|
|
35735
|
-
events: "/v1/events",
|
|
35736
|
-
auditEvents: "/v1/audit-events",
|
|
35737
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
35738
|
-
inventory: "/v1/inventory",
|
|
35739
|
-
storePosture: "/v1/store-posture",
|
|
35740
|
-
policyBundle: "/v1/policy-bundle",
|
|
35741
|
-
whoami: "/v1/plugin/whoami",
|
|
35742
|
-
shares: "/v1/shares"
|
|
35743
|
-
};
|
|
35744
|
-
function headerValue(response, name) {
|
|
35745
|
-
const raw = response.headers[name];
|
|
35746
|
-
if (raw === void 0) return void 0;
|
|
35747
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
35748
|
-
}
|
|
35749
|
-
function okBody(response) {
|
|
35750
|
-
if (response.status < 200 || response.status >= 300) {
|
|
35751
|
-
throw new RemoteRequestError(response.status);
|
|
35752
|
-
}
|
|
35753
|
-
return response.body;
|
|
35754
|
-
}
|
|
35755
|
-
function parsed(schema, body, route) {
|
|
35756
|
-
let json2;
|
|
35757
|
-
try {
|
|
35758
|
-
json2 = JSON.parse(body);
|
|
35759
|
-
} catch {
|
|
35760
|
-
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
35761
|
-
}
|
|
35762
|
-
const result = schema.safeParse(json2);
|
|
35763
|
-
if (!result.success) {
|
|
35764
|
-
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
35765
|
-
}
|
|
35766
|
-
return result.data;
|
|
35767
|
-
}
|
|
35768
|
-
function withoutTrailingSlashes(endpoint) {
|
|
35769
|
-
let end = endpoint.length;
|
|
35770
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
35771
|
-
return endpoint.slice(0, end);
|
|
35772
|
-
}
|
|
35773
|
-
var SLASH = "/".charCodeAt(0);
|
|
35774
|
-
function createRemoteClient(options) {
|
|
35775
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
35776
|
-
const url2 = (route) => `${base}${route}`;
|
|
35777
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
35778
|
-
const sendOne = async (event) => {
|
|
35779
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
35780
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
35781
|
-
const response = await send({
|
|
35782
|
-
...common,
|
|
35783
|
-
method: "POST",
|
|
35784
|
-
url: url2(ROUTES.auditEvents),
|
|
35785
|
-
body: JSON.stringify(validated.data)
|
|
35786
|
-
});
|
|
35787
|
-
okBody(response);
|
|
35788
|
-
};
|
|
35789
|
-
return {
|
|
35790
|
-
async ingestEvents(batch) {
|
|
35791
|
-
const response = await send({
|
|
35792
|
-
...common,
|
|
35793
|
-
method: "POST",
|
|
35794
|
-
url: url2(ROUTES.events),
|
|
35795
|
-
body: JSON.stringify(batch)
|
|
35796
|
-
});
|
|
35797
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
35798
|
-
},
|
|
35799
|
-
async ingestInventory(context) {
|
|
35800
|
-
const response = await send({
|
|
35801
|
-
...common,
|
|
35802
|
-
method: "POST",
|
|
35803
|
-
url: url2(ROUTES.inventory),
|
|
35804
|
-
body: JSON.stringify(context)
|
|
35805
|
-
});
|
|
35806
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
35807
|
-
},
|
|
35808
|
-
async recordAuditEvent(event) {
|
|
35809
|
-
await sendOne(event);
|
|
35810
|
-
},
|
|
35811
|
-
async recordAuditEvents(events) {
|
|
35812
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
35813
|
-
if (!validated.success) {
|
|
35814
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
35815
|
-
}
|
|
35816
|
-
const response = await send({
|
|
35817
|
-
...common,
|
|
35818
|
-
method: "POST",
|
|
35819
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
35820
|
-
body: JSON.stringify(validated.data)
|
|
35821
|
-
});
|
|
35822
|
-
if (response.status === 404) {
|
|
35823
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
35824
|
-
return { accepted: validated.data.events.length };
|
|
35825
|
-
}
|
|
35826
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
35827
|
-
},
|
|
35828
|
-
async reportStorePosture(snapshot) {
|
|
35829
|
-
const response = await send({
|
|
35830
|
-
...common,
|
|
35831
|
-
method: "POST",
|
|
35832
|
-
url: url2(ROUTES.storePosture),
|
|
35833
|
-
body: JSON.stringify(snapshot)
|
|
35834
|
-
});
|
|
35835
|
-
okBody(response);
|
|
35836
|
-
},
|
|
35837
|
-
async getPolicyBundle(etag) {
|
|
35838
|
-
const response = await send({
|
|
35839
|
-
...common,
|
|
35840
|
-
method: "GET",
|
|
35841
|
-
url: url2(ROUTES.policyBundle),
|
|
35842
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
35843
|
-
});
|
|
35844
|
-
if (response.status === 304) {
|
|
35845
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
35846
|
-
}
|
|
35847
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
35848
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
35849
|
-
},
|
|
35850
|
-
async whoami() {
|
|
35851
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
35852
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
35853
|
-
},
|
|
35854
|
-
async recordProjectEgress(request) {
|
|
35855
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
35856
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
35857
|
-
const response = await send({
|
|
35858
|
-
...common,
|
|
35859
|
-
method: "POST",
|
|
35860
|
-
url: url2(ROUTES.shares),
|
|
35861
|
-
body: JSON.stringify(validated.data)
|
|
35862
|
-
});
|
|
35863
|
-
okBody(response);
|
|
35864
|
-
}
|
|
35865
|
-
};
|
|
35866
|
-
}
|
|
36593
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
36594
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
36595
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
36596
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
35867
36597
|
|
|
35868
36598
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
35869
36599
|
import { spawn } from "child_process";
|
|
@@ -35871,7 +36601,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
35871
36601
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
35872
36602
|
|
|
35873
36603
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
35874
|
-
import { readFileSync as
|
|
36604
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
35875
36605
|
function createPluginBlock(build, policyStore) {
|
|
35876
36606
|
return async () => {
|
|
35877
36607
|
const cached2 = await policyStore.read();
|
|
@@ -35890,7 +36620,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
35890
36620
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
35891
36621
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
35892
36622
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
35893
|
-
import { join as
|
|
36623
|
+
import { join as join24 } from "path";
|
|
35894
36624
|
|
|
35895
36625
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
35896
36626
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -35914,7 +36644,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
35914
36644
|
|
|
35915
36645
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
35916
36646
|
function createPolicyStore(dir = dataDir()) {
|
|
35917
|
-
const file2 =
|
|
36647
|
+
const file2 = join24(dir, "policy-cache.json");
|
|
35918
36648
|
async function read() {
|
|
35919
36649
|
try {
|
|
35920
36650
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -35923,22 +36653,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
35923
36653
|
const record2 = parsed2;
|
|
35924
36654
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
35925
36655
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
35926
|
-
const
|
|
36656
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
36657
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
36658
|
+
const etag = replayable ? stored : void 0;
|
|
35927
36659
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
35928
36660
|
} catch {
|
|
35929
36661
|
return null;
|
|
35930
36662
|
}
|
|
35931
36663
|
}
|
|
35932
|
-
|
|
35933
|
-
|
|
35934
|
-
const
|
|
35935
|
-
|
|
35936
|
-
|
|
35937
|
-
|
|
35938
|
-
|
|
36664
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
36665
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
36666
|
+
const theirs = new Set(shapeId.split(","));
|
|
36667
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
36668
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
36669
|
+
}
|
|
36670
|
+
async function priorRecord() {
|
|
36671
|
+
try {
|
|
36672
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
36673
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
36674
|
+
} catch {
|
|
36675
|
+
return null;
|
|
36676
|
+
}
|
|
36677
|
+
}
|
|
36678
|
+
async function publishRecord(record2) {
|
|
35939
36679
|
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
35940
36680
|
try {
|
|
35941
|
-
await writeFile2(tmp, JSON.stringify(
|
|
36681
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
35942
36682
|
encoding: "utf8",
|
|
35943
36683
|
mode: DATA_FILE_MODE,
|
|
35944
36684
|
flag: "wx"
|
|
@@ -35949,6 +36689,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
35949
36689
|
throw err;
|
|
35950
36690
|
}
|
|
35951
36691
|
}
|
|
36692
|
+
async function write(bundle, etag) {
|
|
36693
|
+
await ensureDataDir(dir);
|
|
36694
|
+
const prior = await priorRecord();
|
|
36695
|
+
const priorVersion = prior?.bundle?.version;
|
|
36696
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
36697
|
+
await publishRecord({
|
|
36698
|
+
...prior,
|
|
36699
|
+
fetchedAtMs: Date.now()
|
|
36700
|
+
});
|
|
36701
|
+
return;
|
|
36702
|
+
}
|
|
36703
|
+
await publishRecord({
|
|
36704
|
+
bundle,
|
|
36705
|
+
fetchedAtMs: Date.now(),
|
|
36706
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
36707
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
36708
|
+
// build that last narrowed those bytes, which is this one.
|
|
36709
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
36710
|
+
...etag === void 0 ? {} : { etag }
|
|
36711
|
+
});
|
|
36712
|
+
}
|
|
35952
36713
|
return { read, write, file: file2 };
|
|
35953
36714
|
}
|
|
35954
36715
|
|
|
@@ -36114,11 +36875,11 @@ function readStorePosture(dbPath2) {
|
|
|
36114
36875
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
36115
36876
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
36116
36877
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
36117
|
-
import { join as
|
|
36878
|
+
import { join as join25 } from "path";
|
|
36118
36879
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
36119
36880
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
36120
|
-
const file2 =
|
|
36121
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
36881
|
+
const file2 = join25(dir, "posture-state.json");
|
|
36882
|
+
const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
|
|
36122
36883
|
async function persist(state) {
|
|
36123
36884
|
await ensureDataDir(dir);
|
|
36124
36885
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -36186,8 +36947,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
36186
36947
|
}
|
|
36187
36948
|
|
|
36188
36949
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
36189
|
-
import { readFileSync as
|
|
36190
|
-
import { join as
|
|
36950
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
36951
|
+
import { join as join26 } from "path";
|
|
36191
36952
|
|
|
36192
36953
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
36193
36954
|
var REFUSAL_LINES = {
|
|
@@ -36505,9 +37266,21 @@ var StandaloneDataGateway = class {
|
|
|
36505
37266
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
36506
37267
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
36507
37268
|
// shape — a standalone machine has nothing delivered to record.
|
|
37269
|
+
markCaptureOwed(event) {
|
|
37270
|
+
this.db.markCaptureOwed(event);
|
|
37271
|
+
}
|
|
36508
37272
|
markCaptureDelivered(event, atMs) {
|
|
36509
37273
|
this.db.markCaptureDelivered(event, atMs);
|
|
36510
37274
|
}
|
|
37275
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
37276
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
37277
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
37278
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
37279
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
37280
|
+
// machine that is merely standalone simply never calls it.
|
|
37281
|
+
markAuditEventsDelivered(events, atMs) {
|
|
37282
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
37283
|
+
}
|
|
36511
37284
|
staleBinaryNotice(currentVersion) {
|
|
36512
37285
|
try {
|
|
36513
37286
|
const newest = this.db.installedPacks.newestRecordedBinary();
|
|
@@ -36763,7 +37536,7 @@ function fenced(body) {
|
|
|
36763
37536
|
|
|
36764
37537
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
36765
37538
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
36766
|
-
import { join as
|
|
37539
|
+
import { join as join27 } from "path";
|
|
36767
37540
|
|
|
36768
37541
|
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
36769
37542
|
var RANK = Object.fromEntries(
|
|
@@ -36771,9 +37544,9 @@ var RANK = Object.fromEntries(
|
|
|
36771
37544
|
);
|
|
36772
37545
|
|
|
36773
37546
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
36774
|
-
import { mkdtempSync, readFileSync as
|
|
37547
|
+
import { mkdtempSync, readFileSync as readFileSync18, rmdirSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
36775
37548
|
import { tmpdir } from "os";
|
|
36776
|
-
import { basename as basename6, dirname as
|
|
37549
|
+
import { basename as basename6, dirname as dirname6, join as join28 } from "path";
|
|
36777
37550
|
var SuppressionEntrySchema = external_exports.object({
|
|
36778
37551
|
ruleId: external_exports.string(),
|
|
36779
37552
|
category: DetectionCategory,
|