@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/session-start.js
CHANGED
|
@@ -492,7 +492,7 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/hooks/session-start.ts
|
|
495
|
-
import { readFileSync as
|
|
495
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
496
496
|
|
|
497
497
|
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
498
498
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -500,6 +500,7 @@ import { createHash as createHash4 } from "crypto";
|
|
|
500
500
|
// ../../packages/persistence/src/attached-derived.ts
|
|
501
501
|
import { rmSync } from "fs";
|
|
502
502
|
import { join } from "path";
|
|
503
|
+
var POLICY_CACHE_FILENAME = "policy-cache.json";
|
|
503
504
|
var ATTACHED_FORWARD_STATE_FILENAME = "attached-state.json";
|
|
504
505
|
var ATTACHED_FORWARD_DROPS_FILENAME = "attached-forward-drops.json";
|
|
505
506
|
|
|
@@ -600,6 +601,30 @@ var SQLITE_MIGRATIONS = [
|
|
|
600
601
|
{
|
|
601
602
|
tag: "0022_audit_inspection_ms",
|
|
602
603
|
sql: "ALTER TABLE `audit_events` ADD `inspection_ms` integer GENERATED ALWAYS AS (json_extract(attributes, '$.inspection_ms')) VIRTUAL;"
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
tag: "0023_secret_vault_user_authorized",
|
|
607
|
+
sql: "ALTER TABLE `secret_vault` ADD `user_authorized` integer DEFAULT 0 NOT NULL;"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
tag: "0024_finding_resolution_key_created_index",
|
|
611
|
+
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`);"
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
tag: "0025_audit_capture_attribute_columns",
|
|
615
|
+
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;"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
tag: "0026_audit_llm_call_usage_columns",
|
|
619
|
+
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;"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
tag: "0027_audit_llm_usage_index",
|
|
623
|
+
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;"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
tag: "0028_activity_session_probe_indexes",
|
|
627
|
+
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"
|
|
603
628
|
}
|
|
604
629
|
];
|
|
605
630
|
|
|
@@ -22256,6 +22281,26 @@ var AttachTokenResponse = external_exports.union([
|
|
|
22256
22281
|
AttachTokenExpired,
|
|
22257
22282
|
external_exports.object({ status: printable(64) })
|
|
22258
22283
|
]);
|
|
22284
|
+
var DeviceCommandKind = external_exports.enum(["shares_rescan"]);
|
|
22285
|
+
var DeviceCommand = external_exports.object({
|
|
22286
|
+
id: printable(128).min(1),
|
|
22287
|
+
kind: DeviceCommandKind,
|
|
22288
|
+
issuedAt: printable(64).min(1),
|
|
22289
|
+
expiresAt: printable(64).min(1)
|
|
22290
|
+
}).strict();
|
|
22291
|
+
var DeviceCommandPollResponse = external_exports.object({ command: DeviceCommand.nullable() });
|
|
22292
|
+
var DeviceCommandFailureReason = external_exports.enum(["scan_failed", "no_projects", "expired"]);
|
|
22293
|
+
var DeviceCommandAckBody = external_exports.discriminatedUnion("outcome", [
|
|
22294
|
+
external_exports.object({
|
|
22295
|
+
outcome: external_exports.literal("reported"),
|
|
22296
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22297
|
+
}).strict(),
|
|
22298
|
+
external_exports.object({
|
|
22299
|
+
outcome: external_exports.literal("failed"),
|
|
22300
|
+
reason: DeviceCommandFailureReason,
|
|
22301
|
+
projectsScanned: external_exports.number().int().nonnegative()
|
|
22302
|
+
}).strict()
|
|
22303
|
+
]);
|
|
22259
22304
|
|
|
22260
22305
|
// ../../packages/schema/src/zod/registry.ts
|
|
22261
22306
|
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
@@ -22422,7 +22467,7 @@ var PackManifest = external_exports.object({
|
|
|
22422
22467
|
}).meta({ id: "PackManifest" });
|
|
22423
22468
|
|
|
22424
22469
|
// ../../packages/schema/src/zod/detection.ts
|
|
22425
|
-
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
22470
|
+
var OriginEnum = external_exports.enum(["library", "custom"]).meta({ id: "OriginEnum" });
|
|
22426
22471
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
22427
22472
|
var LibraryStateEnum = external_exports.enum(["new", "imported", "update"]).meta({ id: "LibraryStateEnum" });
|
|
22428
22473
|
var DetectionCounts = external_exports.object({
|
|
@@ -22559,14 +22604,17 @@ function optional2(key, parsed2, raw) {
|
|
|
22559
22604
|
function isStringArray(value) {
|
|
22560
22605
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
22561
22606
|
}
|
|
22607
|
+
var ORIGIN_VALUES = { library: true, custom: true };
|
|
22608
|
+
function resolveOrigin(origin) {
|
|
22609
|
+
return origin != null && Object.hasOwn(ORIGIN_VALUES, origin) ? origin : "library";
|
|
22610
|
+
}
|
|
22562
22611
|
function summaryToDetectionListItem(s) {
|
|
22563
22612
|
return {
|
|
22564
22613
|
id: `${s.namespace}/${s.packId}`,
|
|
22565
22614
|
name: s.name,
|
|
22566
22615
|
version: s.version,
|
|
22567
22616
|
enabled: s.enabled,
|
|
22568
|
-
origin:
|
|
22569
|
-
// v1: every installed pack is library origin
|
|
22617
|
+
origin: resolveOrigin(s.origin),
|
|
22570
22618
|
namespace: s.namespace,
|
|
22571
22619
|
packId: s.packId,
|
|
22572
22620
|
ruleCount: s.ruleCount,
|
|
@@ -22618,7 +22666,7 @@ function rowToDetectionDetail(row, findingsLast30d, update) {
|
|
|
22618
22666
|
name: row.name,
|
|
22619
22667
|
version: row.version,
|
|
22620
22668
|
enabled: row.enabled,
|
|
22621
|
-
origin:
|
|
22669
|
+
origin: resolveOrigin(row.origin),
|
|
22622
22670
|
namespace: row.namespace,
|
|
22623
22671
|
packId: row.packId,
|
|
22624
22672
|
ruleCount: row.rules.length,
|
|
@@ -22638,16 +22686,20 @@ function splitDetectionId(id) {
|
|
|
22638
22686
|
}
|
|
22639
22687
|
function buildDetectionsList(summaries, query) {
|
|
22640
22688
|
const withUpdate = summaries.filter((s) => s.latestVersion != null);
|
|
22689
|
+
const originOf = (s) => resolveOrigin(s.origin);
|
|
22641
22690
|
const counts = {
|
|
22642
22691
|
all: summaries.length,
|
|
22643
|
-
library: summaries.length,
|
|
22644
|
-
|
|
22645
|
-
|
|
22692
|
+
library: summaries.filter((s) => originOf(s) === "library").length,
|
|
22693
|
+
custom: summaries.filter((s) => originOf(s) === "custom").length,
|
|
22694
|
+
// No origin member produces this, so it is 0 BY CONSTRUCTION rather than by
|
|
22695
|
+
// omission: `customized` would mean a LIBRARY pack whose rules were edited in
|
|
22696
|
+
// place, and that state does not exist — editing a library pack forks it. See
|
|
22697
|
+
// OriginEnum.
|
|
22646
22698
|
customized: 0,
|
|
22647
22699
|
updates: withUpdate.length
|
|
22648
22700
|
};
|
|
22649
22701
|
const filter = query.filter;
|
|
22650
|
-
let filtered = filter === "custom"
|
|
22702
|
+
let filtered = filter === "customized" ? [] : filter === "custom" ? summaries.filter((s) => originOf(s) === "custom") : filter === "library" ? summaries.filter((s) => originOf(s) === "library") : filter === "updates" ? [...withUpdate] : [...summaries];
|
|
22651
22703
|
if (query.q) {
|
|
22652
22704
|
const q = query.q.toLowerCase();
|
|
22653
22705
|
filtered = filtered.filter(
|
|
@@ -22727,8 +22779,9 @@ var Event = external_exports.object({
|
|
|
22727
22779
|
metadata: EventMetadata.optional()
|
|
22728
22780
|
}).meta({ id: "Event" });
|
|
22729
22781
|
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
22782
|
+
var INGEST_BATCH_MAX = 100;
|
|
22730
22783
|
var IngestBatch = external_exports.object({
|
|
22731
|
-
events: external_exports.array(IngestEvent).min(1).max(
|
|
22784
|
+
events: external_exports.array(IngestEvent).min(1).max(INGEST_BATCH_MAX),
|
|
22732
22785
|
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
22733
22786
|
// additionally rejects any event whose contentHash the store has already
|
|
22734
22787
|
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
@@ -23284,383 +23337,11 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
23284
23337
|
message: "At least one field must be provided"
|
|
23285
23338
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
23286
23339
|
|
|
23287
|
-
// ../../packages/schema/src/zod/vault.ts
|
|
23288
|
-
var POINTER_FORMAT_VERSION = 2;
|
|
23289
|
-
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23290
|
-
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23291
|
-
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23292
|
-
);
|
|
23293
|
-
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23294
|
-
function pointerTokenScanner() {
|
|
23295
|
-
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23296
|
-
}
|
|
23297
|
-
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23298
|
-
var ParsedPointer = external_exports.object({
|
|
23299
|
-
category: DetectionCategory,
|
|
23300
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23301
|
-
pointerId: external_exports.string(),
|
|
23302
|
-
tag: external_exports.string()
|
|
23303
|
-
});
|
|
23304
|
-
var VaultEntry = external_exports.object({
|
|
23305
|
-
pointerId: external_exports.string(),
|
|
23306
|
-
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23307
|
-
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23308
|
-
// independently of the vault encryption key below.
|
|
23309
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23310
|
-
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23311
|
-
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23312
|
-
keyVersion: external_exports.number().int().positive(),
|
|
23313
|
-
// Fixed at first mint and never updated: the same value detected later under a
|
|
23314
|
-
// different rule's category keeps the category it was minted with, so one
|
|
23315
|
-
// value always produces exactly one wire token.
|
|
23316
|
-
category: DetectionCategory,
|
|
23317
|
-
ruleId: external_exports.string(),
|
|
23318
|
-
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23319
|
-
maskedMatch: external_exports.string(),
|
|
23320
|
-
provider: external_exports.string().optional(),
|
|
23321
|
-
ciphertext: external_exports.string(),
|
|
23322
|
-
nonce: external_exports.string(),
|
|
23323
|
-
authTag: external_exports.string(),
|
|
23324
|
-
// How many times this value has been detected on this machine — the reuse
|
|
23325
|
-
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23326
|
-
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23327
|
-
firstSeen: external_exports.string(),
|
|
23328
|
-
lastSeen: external_exports.string()
|
|
23329
|
-
});
|
|
23330
|
-
var PointerDescriptor = external_exports.object({
|
|
23331
|
-
category: DetectionCategory,
|
|
23332
|
-
provider: external_exports.string().optional(),
|
|
23333
|
-
maskedMatch: external_exports.string(),
|
|
23334
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23335
|
-
firstSeen: external_exports.string(),
|
|
23336
|
-
lastSeen: external_exports.string()
|
|
23337
|
-
});
|
|
23338
|
-
var PointerIdentity = external_exports.object({
|
|
23339
|
-
ruleId: external_exports.string(),
|
|
23340
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23341
|
-
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23342
|
-
});
|
|
23343
|
-
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23344
|
-
var VaultDerefReason = external_exports.enum([
|
|
23345
|
-
"display",
|
|
23346
|
-
"explicit-reveal",
|
|
23347
|
-
"view-render",
|
|
23348
|
-
"model-input",
|
|
23349
|
-
"remediation",
|
|
23350
|
-
"purge"
|
|
23351
|
-
]);
|
|
23352
|
-
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23353
|
-
var VaultDeref = external_exports.object({
|
|
23354
|
-
id: external_exports.guid(),
|
|
23355
|
-
pointerId: external_exports.string(),
|
|
23356
|
-
at: external_exports.string(),
|
|
23357
|
-
target: DetokenizeTarget,
|
|
23358
|
-
reason: VaultDerefReason,
|
|
23359
|
-
outcome: VaultDerefOutcome,
|
|
23360
|
-
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23361
|
-
grantId: external_exports.string().optional(),
|
|
23362
|
-
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23363
|
-
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23364
|
-
pointerCount: external_exports.number().int().positive().default(1)
|
|
23365
|
-
});
|
|
23366
|
-
var VaultSightingKind = external_exports.enum([
|
|
23367
|
-
"prompt",
|
|
23368
|
-
"tool-input",
|
|
23369
|
-
"tool-output",
|
|
23370
|
-
"file",
|
|
23371
|
-
"transcript"
|
|
23372
|
-
]);
|
|
23373
|
-
var VaultSighting = external_exports.object({
|
|
23374
|
-
location: external_exports.string(),
|
|
23375
|
-
kind: VaultSightingKind,
|
|
23376
|
-
firstSeen: external_exports.string(),
|
|
23377
|
-
lastSeen: external_exports.string()
|
|
23378
|
-
});
|
|
23379
|
-
var VaultInventoryEntry = external_exports.object({
|
|
23380
|
-
pointerId: external_exports.string(),
|
|
23381
|
-
category: DetectionCategory,
|
|
23382
|
-
provider: external_exports.string().optional(),
|
|
23383
|
-
maskedMatch: external_exports.string(),
|
|
23384
|
-
occurrences: external_exports.number().int().nonnegative(),
|
|
23385
|
-
firstSeen: external_exports.string(),
|
|
23386
|
-
lastSeen: external_exports.string(),
|
|
23387
|
-
// The active reveal-to-model grant covering this value, when one exists —
|
|
23388
|
-
// the inventory badges it, the row links to revocation.
|
|
23389
|
-
revealGrantId: external_exports.string().nullable(),
|
|
23390
|
-
sightings: external_exports.array(VaultSighting)
|
|
23391
|
-
});
|
|
23392
|
-
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23393
|
-
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23394
|
-
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23395
|
-
var ListVaultInventoryQuery = external_exports.object({
|
|
23396
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23397
|
-
// Opaque; names the last row of the page just served.
|
|
23398
|
-
cursor: external_exports.string().optional()
|
|
23399
|
-
});
|
|
23400
|
-
var ListVaultInventoryResponse = external_exports.object({
|
|
23401
|
-
// Vaulted values across the whole store, not just this page — cursor-
|
|
23402
|
-
// independent, so paging never changes what the count claims.
|
|
23403
|
-
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23404
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23405
|
-
// `null` once the last page is reached.
|
|
23406
|
-
nextCursor: external_exports.string().nullable()
|
|
23407
|
-
});
|
|
23408
|
-
var ListVaultReuseQuery = external_exports.object({
|
|
23409
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23410
|
-
cursor: external_exports.string().optional()
|
|
23411
|
-
});
|
|
23412
|
-
var ListVaultReuseResponse = external_exports.object({
|
|
23413
|
-
// Reused values across the whole store — the number the section's claim
|
|
23414
|
-
// ("values detected in more than one place") is about.
|
|
23415
|
-
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23416
|
-
items: external_exports.array(VaultInventoryEntry),
|
|
23417
|
-
nextCursor: external_exports.string().nullable()
|
|
23418
|
-
});
|
|
23419
|
-
var ListVaultDerefsQuery = external_exports.object({
|
|
23420
|
-
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23421
|
-
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23422
|
-
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23423
|
-
// over a Server Action, which preserves the type, never as a URL param.
|
|
23424
|
-
includeBatched: external_exports.boolean().optional(),
|
|
23425
|
-
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23426
|
-
cursor: external_exports.string().optional()
|
|
23427
|
-
});
|
|
23428
|
-
var ListVaultDerefsResponse = external_exports.object({
|
|
23429
|
-
items: external_exports.array(VaultDeref),
|
|
23430
|
-
nextCursor: external_exports.string().nullable(),
|
|
23431
|
-
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23432
|
-
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23433
|
-
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23434
|
-
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23435
|
-
});
|
|
23436
|
-
var VaultKeyCustody = external_exports.string();
|
|
23437
|
-
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23438
|
-
var VAULT_CONSENT_VERSION = 1;
|
|
23439
|
-
var VaultConsent = external_exports.object({
|
|
23440
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23441
|
-
version: external_exports.number().int().positive()
|
|
23442
|
-
});
|
|
23443
|
-
function isVaultConsentValid(consent) {
|
|
23444
|
-
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23445
|
-
}
|
|
23446
|
-
|
|
23447
|
-
// ../../packages/schema/src/zod/local.ts
|
|
23448
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION = 6;
|
|
23449
|
-
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23450
|
-
var HISTORY_SYNC_PAYLOAD_VERSION = 1;
|
|
23451
|
-
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23452
|
-
var ControlPlaneConnection = external_exports.object({
|
|
23453
|
-
endpoint: external_exports.string().min(1),
|
|
23454
|
-
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23455
|
-
label: external_exports.string().min(1).optional(),
|
|
23456
|
-
attachedAt: external_exports.iso.datetime()
|
|
23457
|
-
}).meta({ id: "ControlPlaneConnection" });
|
|
23458
|
-
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23459
|
-
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23460
|
-
var ModelJudgeConsent = external_exports.object({
|
|
23461
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23462
|
-
payloadVersion: external_exports.number().int().positive()
|
|
23463
|
-
});
|
|
23464
|
-
var HistorySyncConsent = external_exports.object({
|
|
23465
|
-
acknowledgedAt: external_exports.iso.datetime(),
|
|
23466
|
-
payloadVersion: external_exports.number().int().positive(),
|
|
23467
|
-
endpoint: external_exports.string()
|
|
23468
|
-
});
|
|
23469
|
-
function isHistorySyncConsentValid(consent, endpoint) {
|
|
23470
|
-
if (consent === void 0 || endpoint === void 0) return false;
|
|
23471
|
-
return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
|
|
23472
|
-
}
|
|
23473
|
-
var WorkspaceSettings = external_exports.object({
|
|
23474
|
-
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23475
|
-
runMode: RunMode.default("standalone"),
|
|
23476
|
-
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23477
|
-
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23478
|
-
controlPlane: ControlPlaneConnection.optional(),
|
|
23479
|
-
policy: SimpleDetectionPolicy.default("redact"),
|
|
23480
|
-
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23481
|
-
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23482
|
-
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23483
|
-
// Shares writes.
|
|
23484
|
-
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23485
|
-
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23486
|
-
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23487
|
-
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23488
|
-
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23489
|
-
// purging the vault is the eraser.
|
|
23490
|
-
vaultConsent: VaultConsent.optional(),
|
|
23491
|
-
// Where the vault master key lives.
|
|
23492
|
-
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23493
|
-
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23494
|
-
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23495
|
-
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23496
|
-
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23497
|
-
// Records that the user consented to sending findings to the model API for
|
|
23498
|
-
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23499
|
-
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23500
|
-
// covers the current payload and must be re-granted.
|
|
23501
|
-
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23502
|
-
// Records that the user consented to sending the activity already recorded on
|
|
23503
|
-
// this machine to the deployment it is attached to, along with the payload
|
|
23504
|
-
// shape and the endpoint they agreed to. Absent until granted, and a grant for
|
|
23505
|
-
// a different endpoint or an older payload no longer counts.
|
|
23506
|
-
historySyncConsent: HistorySyncConsent.optional()
|
|
23507
|
-
});
|
|
23508
|
-
function defaultWorkspaceSettings() {
|
|
23509
|
-
return WorkspaceSettings.parse({});
|
|
23510
|
-
}
|
|
23511
|
-
function isAttached(settings) {
|
|
23512
|
-
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23513
|
-
}
|
|
23514
|
-
function toInventoryRow(input2, id, now) {
|
|
23515
|
-
return {
|
|
23516
|
-
id,
|
|
23517
|
-
objectType: input2.objectType,
|
|
23518
|
-
location: input2.location ?? null,
|
|
23519
|
-
title: input2.title ?? null,
|
|
23520
|
-
hostId: input2.hostId ?? null,
|
|
23521
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23522
|
-
firstSeen: now,
|
|
23523
|
-
lastSeen: now
|
|
23524
|
-
};
|
|
23525
|
-
}
|
|
23526
|
-
function toSourceProjectRow(input2, id, now) {
|
|
23527
|
-
return {
|
|
23528
|
-
id,
|
|
23529
|
-
url: input2.url,
|
|
23530
|
-
name: input2.name ?? null,
|
|
23531
|
-
attributes: JSON.stringify(input2.attributes),
|
|
23532
|
-
firstSeen: now,
|
|
23533
|
-
lastSeen: now
|
|
23534
|
-
};
|
|
23535
|
-
}
|
|
23536
|
-
function toAuditEventRow(input2) {
|
|
23537
|
-
return {
|
|
23538
|
-
id: input2.id,
|
|
23539
|
-
parentId: input2.parentId ?? null,
|
|
23540
|
-
rootSessionId: input2.rootSessionId ?? null,
|
|
23541
|
-
eventType: input2.eventType,
|
|
23542
|
-
hostId: input2.hostId ?? null,
|
|
23543
|
-
harnessId: input2.harnessId ?? null,
|
|
23544
|
-
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23545
|
-
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23546
|
-
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23547
|
-
severity: input2.severity ?? null,
|
|
23548
|
-
priority: input2.priority ?? null,
|
|
23549
|
-
content: input2.content ?? null,
|
|
23550
|
-
contentHash: input2.contentHash ?? null,
|
|
23551
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23552
|
-
};
|
|
23553
|
-
}
|
|
23554
|
-
function toClassifiedDataRow(input2, id) {
|
|
23555
|
-
return {
|
|
23556
|
-
id,
|
|
23557
|
-
class: input2.class,
|
|
23558
|
-
label: input2.label ?? null,
|
|
23559
|
-
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23560
|
-
};
|
|
23561
|
-
}
|
|
23562
|
-
function toInspectionDefinitionRow(input2, id) {
|
|
23563
|
-
return {
|
|
23564
|
-
id,
|
|
23565
|
-
ruleId: input2.ruleId,
|
|
23566
|
-
name: input2.name,
|
|
23567
|
-
category: input2.category,
|
|
23568
|
-
severity: input2.severity,
|
|
23569
|
-
definition: input2.definition,
|
|
23570
|
-
version: input2.version
|
|
23571
|
-
};
|
|
23572
|
-
}
|
|
23573
|
-
function toInspectionFindingRow(input2) {
|
|
23574
|
-
return {
|
|
23575
|
-
id: input2.id,
|
|
23576
|
-
auditEventId: input2.auditEventId,
|
|
23577
|
-
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23578
|
-
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23579
|
-
spanStart: input2.span.start,
|
|
23580
|
-
spanEnd: input2.span.end,
|
|
23581
|
-
maskedMatch: input2.maskedMatch,
|
|
23582
|
-
actionTaken: input2.actionTaken,
|
|
23583
|
-
confidence: input2.confidence,
|
|
23584
|
-
findingKey: input2.findingKey ?? null,
|
|
23585
|
-
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23586
|
-
};
|
|
23587
|
-
}
|
|
23588
|
-
function toCaptureAttributes(event) {
|
|
23589
|
-
const metadata = event.metadata;
|
|
23590
|
-
return {
|
|
23591
|
-
source_tool: event.sourceTool,
|
|
23592
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23593
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23594
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23595
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23596
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23597
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23598
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23599
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23600
|
-
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23601
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23602
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
23603
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23604
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
23605
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23606
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23607
|
-
};
|
|
23608
|
-
}
|
|
23609
|
-
function captureDefinitionVersion(finding2) {
|
|
23610
|
-
return `capture/${finding2.category}/${finding2.severity}`;
|
|
23611
|
-
}
|
|
23612
|
-
function toCaptureDefinitionInput(finding2) {
|
|
23613
|
-
return {
|
|
23614
|
-
ruleId: finding2.ruleId,
|
|
23615
|
-
version: captureDefinitionVersion(finding2),
|
|
23616
|
-
name: finding2.ruleId,
|
|
23617
|
-
category: finding2.category,
|
|
23618
|
-
severity: finding2.severity,
|
|
23619
|
-
definition: JSON.stringify({ ruleId: finding2.ruleId })
|
|
23620
|
-
};
|
|
23621
|
-
}
|
|
23622
|
-
|
|
23623
|
-
// ../../packages/schema/src/zod/managed.ts
|
|
23624
|
-
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23625
|
-
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23626
|
-
var ManagedSettingKey = external_exports.enum([
|
|
23627
|
-
"runMode",
|
|
23628
|
-
"historicalAccess",
|
|
23629
|
-
"vaultConsent",
|
|
23630
|
-
"vaultKeyCustody",
|
|
23631
|
-
"vaultInlineReveal",
|
|
23632
|
-
"modelJudgeConsent",
|
|
23633
|
-
"dataSharesInPlace"
|
|
23634
|
-
]).meta({ id: "ManagedSettingKey" });
|
|
23635
|
-
var ManagedSettingsValues = external_exports.object({
|
|
23636
|
-
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23637
|
-
controlPlane: external_exports.object({
|
|
23638
|
-
endpoint: external_exports.string().min(1),
|
|
23639
|
-
label: external_exports.string().min(1).optional()
|
|
23640
|
-
}).optional(),
|
|
23641
|
-
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23642
|
-
vaultConsent: external_exports.boolean().optional(),
|
|
23643
|
-
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23644
|
-
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23645
|
-
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23646
|
-
dataSharesInPlace: external_exports.boolean().optional()
|
|
23647
|
-
}).meta({ id: "ManagedSettingsValues" });
|
|
23648
|
-
var ManagedSettings = external_exports.object({
|
|
23649
|
-
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23650
|
-
// Shown on every locked control, so the user can tell an administrative
|
|
23651
|
-
// decision from a bug. Absent renders as a generic "your organization".
|
|
23652
|
-
organization: external_exports.string().min(1).optional(),
|
|
23653
|
-
// What the administrator pinned.
|
|
23654
|
-
values: ManagedSettingsValues.default({}),
|
|
23655
|
-
// Which of those the user may not change. A key here with no matching value
|
|
23656
|
-
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23657
|
-
// the user may still override. The two are separable on purpose.
|
|
23658
|
-
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23659
|
-
}).meta({ id: "ManagedSettings" });
|
|
23660
|
-
|
|
23661
23340
|
// ../../packages/schema/src/zod/policy.ts
|
|
23662
23341
|
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
23663
23342
|
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
23343
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23344
|
+
var PolicyProvenance = external_exports.enum(["builtin", "authored"]).meta({ id: "PolicyProvenance" });
|
|
23664
23345
|
var Policy = external_exports.object({
|
|
23665
23346
|
id: external_exports.guid(),
|
|
23666
23347
|
scope: PolicyScope,
|
|
@@ -23670,7 +23351,27 @@ var Policy = external_exports.object({
|
|
|
23670
23351
|
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
23671
23352
|
// Display name — optional so older policy rows without name still parse.
|
|
23672
23353
|
// Added for the findings API (policy.name column migration).
|
|
23673
|
-
name: external_exports.string().optional()
|
|
23354
|
+
name: external_exports.string().optional(),
|
|
23355
|
+
// Whether an AUTHORED policy governs this row's target — not a claim about
|
|
23356
|
+
// which row this is. A producer that collapses several rows onto one target
|
|
23357
|
+
// must carry the marker onto whichever row survives, or the collapse decides
|
|
23358
|
+
// the answer; a survivor may therefore be a built-in expansion still marked
|
|
23359
|
+
// 'authored' because an authored sibling targeted the same thing.
|
|
23360
|
+
// Optional so an older producer — and an older on-disk cache — still parses;
|
|
23361
|
+
// absent reads as 'builtin', which is the behaviour that predates the field.
|
|
23362
|
+
//
|
|
23363
|
+
// Deliberately NOT `kind`/PolicyKind: that name and that enum answer which
|
|
23364
|
+
// built-in archetype catalog entry a policy is, which every catalog surface
|
|
23365
|
+
// reads and which a caller may state. This one is a statement the PRODUCER
|
|
23366
|
+
// of a bundle makes about a row, and only the bundle builder ever stamps it
|
|
23367
|
+
// — the CRUD routes neither accept nor set it.
|
|
23368
|
+
//
|
|
23369
|
+
// A device consumes this in exactly one direction: an 'authored' policy
|
|
23370
|
+
// arriving from a control plane marks the rules it targets as not
|
|
23371
|
+
// locally re-assignable. That can only ever ADD a refusal, never relax one,
|
|
23372
|
+
// which is what makes it safe to honour from an unsigned cache — the same
|
|
23373
|
+
// test `prohibitedModels` passes and `reversibleRuleIds` fails.
|
|
23374
|
+
provenance: PolicyProvenance.optional()
|
|
23674
23375
|
}).meta({ id: "Policy" });
|
|
23675
23376
|
var PolicyBundle = external_exports.object({
|
|
23676
23377
|
version: external_exports.string(),
|
|
@@ -23722,6 +23423,12 @@ var PolicyBundle = external_exports.object({
|
|
|
23722
23423
|
customKeywords: external_exports.array(external_exports.string()),
|
|
23723
23424
|
fetchedAt: external_exports.iso.datetime()
|
|
23724
23425
|
}).meta({ id: "PolicyBundle" });
|
|
23426
|
+
var POLICY_BUNDLE_SHAPE_ID = [
|
|
23427
|
+
...Object.keys(PolicyBundle.shape),
|
|
23428
|
+
...Object.keys(Policy.shape).map((key) => `policies.${key}`),
|
|
23429
|
+
...PolicyTarget.options.flatMap((member) => "shape" in member ? Object.keys(member.shape) : []).map((key) => `policies.target.${key}`),
|
|
23430
|
+
...Object.keys(ExceptionBundleEntry.shape).map((key) => `exceptions.${key}`)
|
|
23431
|
+
].sort().join(",");
|
|
23725
23432
|
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
23726
23433
|
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
23727
23434
|
var CATEGORY_PEAK_SEVERITY = {
|
|
@@ -23742,9 +23449,11 @@ function severityFloorPolicy(category) {
|
|
|
23742
23449
|
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
23743
23450
|
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
23744
23451
|
}
|
|
23745
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
23746
23452
|
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
23747
23453
|
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
23454
|
+
var RedactFallback = BuiltinPolicyId.extract(["monitor", "warn", "block"]).meta({
|
|
23455
|
+
id: "RedactFallback"
|
|
23456
|
+
});
|
|
23748
23457
|
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
23749
23458
|
var BUILTIN_POLICY_SPECS = {
|
|
23750
23459
|
monitor: {
|
|
@@ -23781,6 +23490,42 @@ var BUILTIN_POLICY_SPECS = {
|
|
|
23781
23490
|
function builtinPolicyToAction(id) {
|
|
23782
23491
|
return BUILTIN_POLICY_SPECS[id].action;
|
|
23783
23492
|
}
|
|
23493
|
+
var PALETTE_WEAKEST_FIRST = [
|
|
23494
|
+
...new Set(KNOWN_BUILTIN_IDS.map(builtinPolicyToAction))
|
|
23495
|
+
];
|
|
23496
|
+
var BELOW_PALETTE = ACTION_TAKEN_KEYS.filter(
|
|
23497
|
+
(action) => !PALETTE_WEAKEST_FIRST.includes(action)
|
|
23498
|
+
);
|
|
23499
|
+
var ACTION_STRENGTH_ORDER = [
|
|
23500
|
+
...BELOW_PALETTE,
|
|
23501
|
+
...PALETTE_WEAKEST_FIRST
|
|
23502
|
+
];
|
|
23503
|
+
function actionRank(action) {
|
|
23504
|
+
return ACTION_STRENGTH_ORDER.indexOf(action);
|
|
23505
|
+
}
|
|
23506
|
+
function isActionAtLeast(action, floor) {
|
|
23507
|
+
return actionRank(action) >= actionRank(floor);
|
|
23508
|
+
}
|
|
23509
|
+
function strongerAction(a, b) {
|
|
23510
|
+
return actionRank(a) >= actionRank(b) ? a : b;
|
|
23511
|
+
}
|
|
23512
|
+
function weakestBuiltinAtLeast(floor) {
|
|
23513
|
+
return KNOWN_BUILTIN_IDS.find((id) => isActionAtLeast(builtinPolicyToAction(id), floor)) ?? "block";
|
|
23514
|
+
}
|
|
23515
|
+
var PackPolicyFloor = external_exports.object({
|
|
23516
|
+
/**
|
|
23517
|
+
* The weakest archetype the device may assign. Stated as a BuiltinPolicyId
|
|
23518
|
+
* rather than a raw ActionTaken because that is the vocabulary the user
|
|
23519
|
+
* picks from — a floor a UI cannot name is one it cannot explain.
|
|
23520
|
+
*/
|
|
23521
|
+
floor: BuiltinPolicyId,
|
|
23522
|
+
/**
|
|
23523
|
+
* True when the organization AUTHORED a policy governing this pack rather
|
|
23524
|
+
* than stating a minimum: it gave the answer, so the pack is not
|
|
23525
|
+
* re-assignable locally in either direction.
|
|
23526
|
+
*/
|
|
23527
|
+
locked: external_exports.boolean()
|
|
23528
|
+
}).describe("PackPolicyFloor");
|
|
23784
23529
|
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
23785
23530
|
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
23786
23531
|
);
|
|
@@ -23838,6 +23583,405 @@ var PolicyStatsResponse = external_exports.object({
|
|
|
23838
23583
|
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
23839
23584
|
}).meta({ id: "PolicyStatsResponse" });
|
|
23840
23585
|
|
|
23586
|
+
// ../../packages/schema/src/zod/vault.ts
|
|
23587
|
+
var POINTER_FORMAT_VERSION = 2;
|
|
23588
|
+
var CATEGORY_ALTERNATION = DetectionCategory.options.join("|");
|
|
23589
|
+
var POINTER_TOKEN_PATTERN = new RegExp(
|
|
23590
|
+
`\\[\\[aka:(?:${CATEGORY_ALTERNATION}):[A-Z2-7]{2,7}\\.[A-Z2-7]{26}\\.[A-Z2-7]{16}\\]\\]`
|
|
23591
|
+
);
|
|
23592
|
+
var POINTER_TOKEN_ANCHORED = new RegExp(`^${POINTER_TOKEN_PATTERN.source}$`);
|
|
23593
|
+
function pointerTokenScanner() {
|
|
23594
|
+
return new RegExp(POINTER_TOKEN_PATTERN.source, "g");
|
|
23595
|
+
}
|
|
23596
|
+
var PointerToken = external_exports.string().regex(POINTER_TOKEN_ANCHORED);
|
|
23597
|
+
var ParsedPointer = external_exports.object({
|
|
23598
|
+
category: DetectionCategory,
|
|
23599
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23600
|
+
pointerId: external_exports.string(),
|
|
23601
|
+
tag: external_exports.string()
|
|
23602
|
+
});
|
|
23603
|
+
var VaultEntry = external_exports.object({
|
|
23604
|
+
pointerId: external_exports.string(),
|
|
23605
|
+
// The keyed HMAC of the raw value under `exception.key`, and the epoch it was
|
|
23606
|
+
// derived under. This is what a reveal-to-model grant matches on, and it rotates
|
|
23607
|
+
// independently of the vault encryption key below.
|
|
23608
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23609
|
+
fingerprintKeyVersion: external_exports.number().int().positive(),
|
|
23610
|
+
// The vault-key epoch this row's ciphertext was sealed under.
|
|
23611
|
+
keyVersion: external_exports.number().int().positive(),
|
|
23612
|
+
// Fixed at first mint and never updated: the same value detected later under a
|
|
23613
|
+
// different rule's category keeps the category it was minted with, so one
|
|
23614
|
+
// value always produces exactly one wire token.
|
|
23615
|
+
category: DetectionCategory,
|
|
23616
|
+
ruleId: external_exports.string(),
|
|
23617
|
+
// Partial-reveal preview for badges and listings. Never the raw value.
|
|
23618
|
+
maskedMatch: external_exports.string(),
|
|
23619
|
+
provider: external_exports.string().optional(),
|
|
23620
|
+
ciphertext: external_exports.string(),
|
|
23621
|
+
nonce: external_exports.string(),
|
|
23622
|
+
authTag: external_exports.string(),
|
|
23623
|
+
// How many times this value has been detected on this machine — the reuse
|
|
23624
|
+
// signal the vault dashboard reads. Distinct from VaultDeref.pointerCount.
|
|
23625
|
+
occurrenceCount: external_exports.number().int().nonnegative(),
|
|
23626
|
+
// True when a PERSON asked for this value to be replaced — the surfaced-
|
|
23627
|
+
// secrets strike — rather than a pack enforcing its assignment. One value is
|
|
23628
|
+
// one row however many paths vault it, so this is what tells a policy sweep
|
|
23629
|
+
// that the row carries somebody's own instruction and not just an assignment
|
|
23630
|
+
// that has since been lowered. STICKY and MONOTONIC: a later automatic
|
|
23631
|
+
// vaulting of the same value must never clear it — what the user said about
|
|
23632
|
+
// the value does not expire.
|
|
23633
|
+
userAuthorized: external_exports.boolean(),
|
|
23634
|
+
firstSeen: external_exports.string(),
|
|
23635
|
+
lastSeen: external_exports.string()
|
|
23636
|
+
});
|
|
23637
|
+
var PointerDescriptor = external_exports.object({
|
|
23638
|
+
category: DetectionCategory,
|
|
23639
|
+
provider: external_exports.string().optional(),
|
|
23640
|
+
maskedMatch: external_exports.string(),
|
|
23641
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23642
|
+
firstSeen: external_exports.string(),
|
|
23643
|
+
lastSeen: external_exports.string()
|
|
23644
|
+
});
|
|
23645
|
+
var PointerIdentity = external_exports.object({
|
|
23646
|
+
ruleId: external_exports.string(),
|
|
23647
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
23648
|
+
fingerprintKeyVersion: external_exports.number().int().positive()
|
|
23649
|
+
});
|
|
23650
|
+
var DetokenizeTarget = external_exports.enum(["human", "model"]);
|
|
23651
|
+
var VaultDerefReason = external_exports.enum([
|
|
23652
|
+
"display",
|
|
23653
|
+
"explicit-reveal",
|
|
23654
|
+
"view-render",
|
|
23655
|
+
"model-input",
|
|
23656
|
+
"remediation",
|
|
23657
|
+
"purge"
|
|
23658
|
+
]);
|
|
23659
|
+
var VaultDerefOutcome = external_exports.enum(["revealed", "refused", "unavailable"]);
|
|
23660
|
+
var VaultDeref = external_exports.object({
|
|
23661
|
+
id: external_exports.guid(),
|
|
23662
|
+
pointerId: external_exports.string(),
|
|
23663
|
+
at: external_exports.string(),
|
|
23664
|
+
target: DetokenizeTarget,
|
|
23665
|
+
reason: VaultDerefReason,
|
|
23666
|
+
outcome: VaultDerefOutcome,
|
|
23667
|
+
// Present only on a model-target crossing that a reveal grant authorized.
|
|
23668
|
+
grantId: external_exports.string().optional(),
|
|
23669
|
+
// How many pointers ONE batched render resolved. 1 for unbatched rows. Named
|
|
23670
|
+
// apart from VaultEntry.occurrenceCount, which counts detections of a value.
|
|
23671
|
+
pointerCount: external_exports.number().int().positive().default(1)
|
|
23672
|
+
});
|
|
23673
|
+
var VaultSightingKind = external_exports.enum([
|
|
23674
|
+
"prompt",
|
|
23675
|
+
"tool-input",
|
|
23676
|
+
"tool-output",
|
|
23677
|
+
"file",
|
|
23678
|
+
"transcript"
|
|
23679
|
+
]);
|
|
23680
|
+
var VaultSighting = external_exports.object({
|
|
23681
|
+
location: external_exports.string(),
|
|
23682
|
+
kind: VaultSightingKind,
|
|
23683
|
+
firstSeen: external_exports.string(),
|
|
23684
|
+
lastSeen: external_exports.string()
|
|
23685
|
+
});
|
|
23686
|
+
var VaultInventoryEntry = external_exports.object({
|
|
23687
|
+
pointerId: external_exports.string(),
|
|
23688
|
+
category: DetectionCategory,
|
|
23689
|
+
provider: external_exports.string().optional(),
|
|
23690
|
+
maskedMatch: external_exports.string(),
|
|
23691
|
+
occurrences: external_exports.number().int().nonnegative(),
|
|
23692
|
+
firstSeen: external_exports.string(),
|
|
23693
|
+
lastSeen: external_exports.string(),
|
|
23694
|
+
// The active reveal-to-model grant covering this value, when one exists —
|
|
23695
|
+
// the inventory badges it, the row links to revocation.
|
|
23696
|
+
revealGrantId: external_exports.string().nullable(),
|
|
23697
|
+
sightings: external_exports.array(VaultSighting)
|
|
23698
|
+
});
|
|
23699
|
+
var DEFAULT_VAULT_INVENTORY_LIMIT = 50;
|
|
23700
|
+
var DEFAULT_VAULT_DEREFS_LIMIT = 50;
|
|
23701
|
+
var MAX_VAULT_PAGE_LIMIT = 200;
|
|
23702
|
+
var ListVaultInventoryQuery = external_exports.object({
|
|
23703
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23704
|
+
// Opaque; names the last row of the page just served.
|
|
23705
|
+
cursor: external_exports.string().optional()
|
|
23706
|
+
});
|
|
23707
|
+
var ListVaultInventoryResponse = external_exports.object({
|
|
23708
|
+
// Vaulted values across the whole store, not just this page — cursor-
|
|
23709
|
+
// independent, so paging never changes what the count claims.
|
|
23710
|
+
totals: external_exports.object({ values: external_exports.number().int().nonnegative() }),
|
|
23711
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23712
|
+
// `null` once the last page is reached.
|
|
23713
|
+
nextCursor: external_exports.string().nullable()
|
|
23714
|
+
});
|
|
23715
|
+
var ListVaultReuseQuery = external_exports.object({
|
|
23716
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23717
|
+
cursor: external_exports.string().optional()
|
|
23718
|
+
});
|
|
23719
|
+
var ListVaultReuseResponse = external_exports.object({
|
|
23720
|
+
// Reused values across the whole store — the number the section's claim
|
|
23721
|
+
// ("values detected in more than one place") is about.
|
|
23722
|
+
totals: external_exports.object({ reused: external_exports.number().int().nonnegative() }),
|
|
23723
|
+
items: external_exports.array(VaultInventoryEntry),
|
|
23724
|
+
nextCursor: external_exports.string().nullable()
|
|
23725
|
+
});
|
|
23726
|
+
var ListVaultDerefsQuery = external_exports.object({
|
|
23727
|
+
// Include the batched, high-volume reasons (display, view-render). Omitted
|
|
23728
|
+
// hides them and counts them into `hiddenBatched` instead, so the model
|
|
23729
|
+
// crossings stay visible. A real boolean, not `z.stringbool()`: this arrives
|
|
23730
|
+
// over a Server Action, which preserves the type, never as a URL param.
|
|
23731
|
+
includeBatched: external_exports.boolean().optional(),
|
|
23732
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_VAULT_PAGE_LIMIT).optional(),
|
|
23733
|
+
cursor: external_exports.string().optional()
|
|
23734
|
+
});
|
|
23735
|
+
var ListVaultDerefsResponse = external_exports.object({
|
|
23736
|
+
items: external_exports.array(VaultDeref),
|
|
23737
|
+
nextCursor: external_exports.string().nullable(),
|
|
23738
|
+
// Display/view-render rows the query hid, over the WHOLE trail rather than
|
|
23739
|
+
// this page — it is the count the "N hidden" line and its toggle speak for.
|
|
23740
|
+
// Always 0 when `includeBatched` was set, since nothing was hidden.
|
|
23741
|
+
hiddenBatched: external_exports.number().int().nonnegative()
|
|
23742
|
+
});
|
|
23743
|
+
var VaultKeyCustody = external_exports.string();
|
|
23744
|
+
var VaultInlineReveal = external_exports.enum(["masked", "full", "off"]);
|
|
23745
|
+
var VAULT_CONSENT_VERSION = 1;
|
|
23746
|
+
var VaultConsent = external_exports.object({
|
|
23747
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23748
|
+
version: external_exports.number().int().positive()
|
|
23749
|
+
});
|
|
23750
|
+
function isVaultConsentValid(consent) {
|
|
23751
|
+
return consent?.version === VAULT_CONSENT_VERSION;
|
|
23752
|
+
}
|
|
23753
|
+
|
|
23754
|
+
// ../../packages/schema/src/zod/local.ts
|
|
23755
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 7;
|
|
23756
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
23757
|
+
var HISTORY_SYNC_PAYLOAD_VERSION = 2;
|
|
23758
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
23759
|
+
var ControlPlaneConnection = external_exports.object({
|
|
23760
|
+
endpoint: external_exports.string().min(1),
|
|
23761
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
23762
|
+
label: external_exports.string().min(1).optional(),
|
|
23763
|
+
attachedAt: external_exports.iso.datetime()
|
|
23764
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
23765
|
+
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
23766
|
+
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
23767
|
+
var ModelJudgeConsent = external_exports.object({
|
|
23768
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23769
|
+
payloadVersion: external_exports.number().int().positive()
|
|
23770
|
+
});
|
|
23771
|
+
var HistorySyncConsent = external_exports.object({
|
|
23772
|
+
acknowledgedAt: external_exports.iso.datetime(),
|
|
23773
|
+
payloadVersion: external_exports.number().int().positive(),
|
|
23774
|
+
endpoint: external_exports.string()
|
|
23775
|
+
});
|
|
23776
|
+
function isHistorySyncConsentValid(consent, endpoint) {
|
|
23777
|
+
if (consent === void 0 || endpoint === void 0) return false;
|
|
23778
|
+
return consent.payloadVersion === HISTORY_SYNC_PAYLOAD_VERSION && consent.endpoint === endpoint;
|
|
23779
|
+
}
|
|
23780
|
+
var WorkspaceSettings = external_exports.object({
|
|
23781
|
+
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
23782
|
+
runMode: RunMode.default("standalone"),
|
|
23783
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
23784
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
23785
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
23786
|
+
policy: SimpleDetectionPolicy.default("redact"),
|
|
23787
|
+
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
23788
|
+
historicalAccess: HistoricalAccess.default("session-only"),
|
|
23789
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
23790
|
+
// Shares writes.
|
|
23791
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
23792
|
+
// Consent to keep a RECOVERABLE encrypted copy of detected values in the local
|
|
23793
|
+
// vault, instead of destroying them. Absent by default: this is a custody
|
|
23794
|
+
// change from one-way redaction, so it is never an assumed grant on upgrade.
|
|
23795
|
+
// Revoking stops future vaulting; it does not erase what is already stored —
|
|
23796
|
+
// purging the vault is the eraser.
|
|
23797
|
+
vaultConsent: VaultConsent.optional(),
|
|
23798
|
+
// Where the vault master key lives.
|
|
23799
|
+
vaultKeyCustody: VaultKeyCustody.default("file"),
|
|
23800
|
+
// How a pointer renders in assistant prose on screen (see VaultInlineReveal).
|
|
23801
|
+
vaultInlineReveal: VaultInlineReveal.default("masked"),
|
|
23802
|
+
// What a `redact` policy degrades to on a FIELD the host cannot rewrite in
|
|
23803
|
+
// place. Not a handling policy: the policy has already resolved to redact,
|
|
23804
|
+
// and this only says what happens when the host offers no channel to carry it
|
|
23805
|
+
// out — Antigravity's PreToolUse has no updatedInput at all, and Codex and
|
|
23806
|
+
// Claude Code decline to mask a field that EXECUTES because masking would
|
|
23807
|
+
// change what runs. Per FIELD rather than per host, so a host that can
|
|
23808
|
+
// rewrite some inputs keeps true redaction on those.
|
|
23809
|
+
//
|
|
23810
|
+
// Spelled in the built-in policy vocabulary rather than as a fresh enum, so
|
|
23811
|
+
// an attached machine's merge is `strongerAction` over the one action ladder
|
|
23812
|
+
// and no second rank order exists to drift from it. 'deny' is a host wire
|
|
23813
|
+
// word and stays out of the stored value.
|
|
23814
|
+
redactFallback: RedactFallback.default("warn"),
|
|
23815
|
+
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
23816
|
+
onboardedAt: external_exports.iso.datetime().optional(),
|
|
23817
|
+
// Records that the user consented to sending findings to the model API for
|
|
23818
|
+
// the /aka:setup judge, along with the payload-shape version they agreed to.
|
|
23819
|
+
// Absent until granted; a stale payloadVersion means the consent no longer
|
|
23820
|
+
// covers the current payload and must be re-granted.
|
|
23821
|
+
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23822
|
+
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23823
|
+
// with the payload shape and the endpoint they agreed to. Since payload v2
|
|
23824
|
+
// that covers both the pre-attach backlog and undelivered captures (which
|
|
23825
|
+
// carry prompt/reply text in `content`); the key name predates the widening.
|
|
23826
|
+
// Absent until granted, and a grant for a different endpoint or an older
|
|
23827
|
+
// payload no longer counts.
|
|
23828
|
+
historySyncConsent: HistorySyncConsent.optional()
|
|
23829
|
+
});
|
|
23830
|
+
function defaultWorkspaceSettings() {
|
|
23831
|
+
return WorkspaceSettings.parse({});
|
|
23832
|
+
}
|
|
23833
|
+
function isAttached(settings) {
|
|
23834
|
+
return settings.runMode === "attached" && settings.controlPlane !== void 0;
|
|
23835
|
+
}
|
|
23836
|
+
function toInventoryRow(input2, id, now) {
|
|
23837
|
+
return {
|
|
23838
|
+
id,
|
|
23839
|
+
objectType: input2.objectType,
|
|
23840
|
+
location: input2.location ?? null,
|
|
23841
|
+
title: input2.title ?? null,
|
|
23842
|
+
hostId: input2.hostId ?? null,
|
|
23843
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23844
|
+
firstSeen: now,
|
|
23845
|
+
lastSeen: now
|
|
23846
|
+
};
|
|
23847
|
+
}
|
|
23848
|
+
function toSourceProjectRow(input2, id, now) {
|
|
23849
|
+
return {
|
|
23850
|
+
id,
|
|
23851
|
+
url: input2.url,
|
|
23852
|
+
name: input2.name ?? null,
|
|
23853
|
+
attributes: JSON.stringify(input2.attributes),
|
|
23854
|
+
firstSeen: now,
|
|
23855
|
+
lastSeen: now
|
|
23856
|
+
};
|
|
23857
|
+
}
|
|
23858
|
+
function toAuditEventRow(input2) {
|
|
23859
|
+
return {
|
|
23860
|
+
id: input2.id,
|
|
23861
|
+
parentId: input2.parentId ?? null,
|
|
23862
|
+
rootSessionId: input2.rootSessionId ?? null,
|
|
23863
|
+
eventType: input2.eventType,
|
|
23864
|
+
hostId: input2.hostId ?? null,
|
|
23865
|
+
harnessId: input2.harnessId ?? null,
|
|
23866
|
+
sourceProjectId: input2.sourceProjectId ?? null,
|
|
23867
|
+
startedAt: isoToEpochMillis(input2.startedAt),
|
|
23868
|
+
endedAt: input2.endedAt ? isoToEpochMillis(input2.endedAt) : null,
|
|
23869
|
+
severity: input2.severity ?? null,
|
|
23870
|
+
priority: input2.priority ?? null,
|
|
23871
|
+
content: input2.content ?? null,
|
|
23872
|
+
contentHash: input2.contentHash ?? null,
|
|
23873
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23874
|
+
};
|
|
23875
|
+
}
|
|
23876
|
+
function toClassifiedDataRow(input2, id) {
|
|
23877
|
+
return {
|
|
23878
|
+
id,
|
|
23879
|
+
class: input2.class,
|
|
23880
|
+
label: input2.label ?? null,
|
|
23881
|
+
attributes: input2.attributes ? JSON.stringify(input2.attributes) : null
|
|
23882
|
+
};
|
|
23883
|
+
}
|
|
23884
|
+
function toInspectionDefinitionRow(input2, id) {
|
|
23885
|
+
return {
|
|
23886
|
+
id,
|
|
23887
|
+
ruleId: input2.ruleId,
|
|
23888
|
+
name: input2.name,
|
|
23889
|
+
category: input2.category,
|
|
23890
|
+
severity: input2.severity,
|
|
23891
|
+
definition: input2.definition,
|
|
23892
|
+
version: input2.version
|
|
23893
|
+
};
|
|
23894
|
+
}
|
|
23895
|
+
function toInspectionFindingRow(input2) {
|
|
23896
|
+
return {
|
|
23897
|
+
id: input2.id,
|
|
23898
|
+
auditEventId: input2.auditEventId,
|
|
23899
|
+
inspectionDefinitionId: input2.inspectionDefinitionId,
|
|
23900
|
+
classifiedDataId: input2.classifiedDataId ?? null,
|
|
23901
|
+
spanStart: input2.span.start,
|
|
23902
|
+
spanEnd: input2.span.end,
|
|
23903
|
+
maskedMatch: input2.maskedMatch,
|
|
23904
|
+
actionTaken: input2.actionTaken,
|
|
23905
|
+
confidence: input2.confidence,
|
|
23906
|
+
findingKey: input2.findingKey ?? null,
|
|
23907
|
+
firstDetectedAt: input2.firstDetectedAt ? isoToEpochMillis(input2.firstDetectedAt) : null
|
|
23908
|
+
};
|
|
23909
|
+
}
|
|
23910
|
+
function toCaptureAttributes(event) {
|
|
23911
|
+
const metadata = event.metadata;
|
|
23912
|
+
return {
|
|
23913
|
+
source_tool: event.sourceTool,
|
|
23914
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
23915
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
23916
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
23917
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
23918
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
23919
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
23920
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
23921
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
23922
|
+
...metadata?.inspectionMs !== void 0 ? { inspection_ms: metadata.inspectionMs } : {},
|
|
23923
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
23924
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
23925
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
23926
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
23927
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
23928
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
23929
|
+
};
|
|
23930
|
+
}
|
|
23931
|
+
function captureDefinitionVersion(finding2) {
|
|
23932
|
+
return `capture/${finding2.category}/${finding2.severity}`;
|
|
23933
|
+
}
|
|
23934
|
+
function toCaptureDefinitionInput(finding2) {
|
|
23935
|
+
return {
|
|
23936
|
+
ruleId: finding2.ruleId,
|
|
23937
|
+
version: captureDefinitionVersion(finding2),
|
|
23938
|
+
name: finding2.ruleId,
|
|
23939
|
+
category: finding2.category,
|
|
23940
|
+
severity: finding2.severity,
|
|
23941
|
+
definition: JSON.stringify({ ruleId: finding2.ruleId })
|
|
23942
|
+
};
|
|
23943
|
+
}
|
|
23944
|
+
|
|
23945
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
23946
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
23947
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
23948
|
+
var ManagedSettingKey = external_exports.enum([
|
|
23949
|
+
"runMode",
|
|
23950
|
+
"historicalAccess",
|
|
23951
|
+
"vaultConsent",
|
|
23952
|
+
"vaultKeyCustody",
|
|
23953
|
+
"vaultInlineReveal",
|
|
23954
|
+
"modelJudgeConsent",
|
|
23955
|
+
"dataSharesInPlace",
|
|
23956
|
+
"redactFallback"
|
|
23957
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
23958
|
+
var ManagedSettingsValues = external_exports.object({
|
|
23959
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23960
|
+
controlPlane: external_exports.object({
|
|
23961
|
+
endpoint: external_exports.string().min(1),
|
|
23962
|
+
label: external_exports.string().min(1).optional()
|
|
23963
|
+
}).optional(),
|
|
23964
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
23965
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
23966
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
23967
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
23968
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
23969
|
+
dataSharesInPlace: external_exports.boolean().optional(),
|
|
23970
|
+
redactFallback: RedactFallback.optional()
|
|
23971
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
23972
|
+
var ManagedSettings = external_exports.object({
|
|
23973
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
23974
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
23975
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
23976
|
+
organization: external_exports.string().min(1).optional(),
|
|
23977
|
+
// What the administrator pinned.
|
|
23978
|
+
values: ManagedSettingsValues.default({}),
|
|
23979
|
+
// Which of those the user may not change. A key here with no matching value
|
|
23980
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23981
|
+
// the user may still override. The two are separable on purpose.
|
|
23982
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
23983
|
+
}).meta({ id: "ManagedSettings" });
|
|
23984
|
+
|
|
23841
23985
|
// ../../packages/schema/src/zod/project-files.ts
|
|
23842
23986
|
var ProjectFileInput = external_exports.object({
|
|
23843
23987
|
path: external_exports.string().min(1),
|
|
@@ -24083,10 +24227,12 @@ var DismissRecommendedActionResponse = external_exports.object({ id: external_ex
|
|
|
24083
24227
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
24084
24228
|
|
|
24085
24229
|
// ../../packages/schema/src/zod/settings-action.ts
|
|
24230
|
+
var HistorySyncConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "HistorySyncConsentChoice" });
|
|
24231
|
+
var ModelJudgeConsentChoice = external_exports.enum(["granted", "revoked", "unchanged"]).meta({ id: "ModelJudgeConsentChoice" });
|
|
24086
24232
|
var SaveSettingsInput = external_exports.object({
|
|
24087
24233
|
historicalAccess: external_exports.string(),
|
|
24088
|
-
modelJudgeConsent:
|
|
24089
|
-
historySyncConsent:
|
|
24234
|
+
modelJudgeConsent: ModelJudgeConsentChoice,
|
|
24235
|
+
historySyncConsent: HistorySyncConsentChoice,
|
|
24090
24236
|
vaultConsent: external_exports.string(),
|
|
24091
24237
|
vaultInlineReveal: external_exports.string()
|
|
24092
24238
|
});
|
|
@@ -24236,9 +24382,9 @@ function deriveReviewReasons(trust, transports) {
|
|
|
24236
24382
|
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
24237
24383
|
return reasons;
|
|
24238
24384
|
}
|
|
24239
|
-
function buildReviewInfo(trust, transports) {
|
|
24385
|
+
function buildReviewInfo(trust, transports, decided) {
|
|
24240
24386
|
const reasons = deriveReviewReasons(trust, transports);
|
|
24241
|
-
return { needsReview: reasons.length > 0, reasons };
|
|
24387
|
+
return { needsReview: reasons.length > 0 && !decided, reasons };
|
|
24242
24388
|
}
|
|
24243
24389
|
function distinctTransports(transports) {
|
|
24244
24390
|
return Array.from(new Set(transports));
|
|
@@ -24440,8 +24586,8 @@ function readControlPlaneCredential(settingsDir2, connection) {
|
|
|
24440
24586
|
}
|
|
24441
24587
|
|
|
24442
24588
|
// ../../packages/persistence/src/database.ts
|
|
24443
|
-
import { randomUUID as
|
|
24444
|
-
import { join as
|
|
24589
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
24590
|
+
import { dirname as dirname2, join as join7, sep } from "path";
|
|
24445
24591
|
import { DatabaseSync } from "node:sqlite";
|
|
24446
24592
|
|
|
24447
24593
|
// ../../packages/persistence/src/ids.ts
|
|
@@ -24696,6 +24842,10 @@ function allRows(stmt, params) {
|
|
|
24696
24842
|
if (Array.isArray(params)) return stmt.all(...params);
|
|
24697
24843
|
return stmt.all(params);
|
|
24698
24844
|
}
|
|
24845
|
+
function* iterateRows(stmt, params) {
|
|
24846
|
+
const rows = params === void 0 ? stmt.iterate() : Array.isArray(params) ? stmt.iterate(...params) : stmt.iterate(params);
|
|
24847
|
+
for (const row of rows) yield row;
|
|
24848
|
+
}
|
|
24699
24849
|
function getRow(stmt, params) {
|
|
24700
24850
|
if (params === void 0) return stmt.get();
|
|
24701
24851
|
if (Array.isArray(params)) return stmt.get(...params);
|
|
@@ -25164,10 +25314,17 @@ function ensureSyncedAtColumn(db, table) {
|
|
|
25164
25314
|
if (!columns.includes("sync_claimed_at")) {
|
|
25165
25315
|
db.exec(`ALTER TABLE ${table} ADD COLUMN sync_claimed_at integer`);
|
|
25166
25316
|
}
|
|
25317
|
+
if (!columns.includes("outbox_owed")) {
|
|
25318
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN outbox_owed integer`);
|
|
25319
|
+
}
|
|
25167
25320
|
db.exec(
|
|
25168
25321
|
`CREATE INDEX IF NOT EXISTS idx_audit_events_sync
|
|
25169
25322
|
ON audit_events (event_type, synced_at, sync_claimed_at, started_at)`
|
|
25170
25323
|
);
|
|
25324
|
+
db.exec(
|
|
25325
|
+
`CREATE INDEX IF NOT EXISTS idx_audit_outbox_owed
|
|
25326
|
+
ON audit_events (event_type, synced_at, sync_claimed_at, started_at) WHERE outbox_owed = 1`
|
|
25327
|
+
);
|
|
25171
25328
|
db.exec(
|
|
25172
25329
|
`CREATE INDEX IF NOT EXISTS idx_audit_claimed
|
|
25173
25330
|
ON audit_events (sync_claimed_at) WHERE sync_claimed_at IS NOT NULL`
|
|
@@ -25272,7 +25429,6 @@ function decodeKeysetCursor(cursor) {
|
|
|
25272
25429
|
// ../../packages/persistence/src/repositories/activity.ts
|
|
25273
25430
|
var DAY_MS = 864e5;
|
|
25274
25431
|
var LIVE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
25275
|
-
var LAST_ACTIVITY_EXPR = `max(started_at, coalesce(ended_at, started_at))`;
|
|
25276
25432
|
function defaultTimeZone() {
|
|
25277
25433
|
try {
|
|
25278
25434
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -25327,6 +25483,7 @@ var DB_EVENT_TYPE_TO_KIND = {
|
|
|
25327
25483
|
error: "error",
|
|
25328
25484
|
active: "active"
|
|
25329
25485
|
};
|
|
25486
|
+
var TIMELINE_EVENT_TYPES = Object.keys(DB_EVENT_TYPE_TO_KIND).map((kind) => `'${kind}'`).join(", ");
|
|
25330
25487
|
function safeParseStringArray(raw) {
|
|
25331
25488
|
if (!raw) return [];
|
|
25332
25489
|
const parsed2 = safeJson(raw, null);
|
|
@@ -25400,6 +25557,37 @@ var TIMELINE_COLUMNS = `
|
|
|
25400
25557
|
json_extract(attributes, '$.targetId') AS target_id,
|
|
25401
25558
|
json_extract(attributes, '$.internal') AS internal,
|
|
25402
25559
|
json_extract(attributes, '$.flagged') AS flagged`;
|
|
25560
|
+
var LLM_USAGE_SELECT = `
|
|
25561
|
+
SELECT root_session_id AS sessionId,
|
|
25562
|
+
provider,
|
|
25563
|
+
model,
|
|
25564
|
+
service_tier AS serviceTier,
|
|
25565
|
+
coalesce(sum(input_tokens), 0) AS inputTokens,
|
|
25566
|
+
coalesce(sum(output_tokens), 0) AS outputTokens,
|
|
25567
|
+
coalesce(sum(cache_creation_input_tokens), 0) AS cacheCreationTokens,
|
|
25568
|
+
coalesce(sum(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
25569
|
+
coalesce(sum(ephemeral_1h_input_tokens), 0) AS ephemeral1hTokens,
|
|
25570
|
+
coalesce(sum(ephemeral_5m_input_tokens), 0) AS ephemeral5mTokens,
|
|
25571
|
+
coalesce(sum(web_search_requests), 0) AS webSearchRequests`;
|
|
25572
|
+
var LLM_USAGE_SCOPE = `event_type = 'llm_call' AND attributes IS NOT NULL AND root_session_id IS NOT NULL`;
|
|
25573
|
+
var LLM_USAGE_GROUP = `GROUP BY root_session_id, provider, model, service_tier`;
|
|
25574
|
+
function usageLeaves(rows) {
|
|
25575
|
+
return rows.map((row) => {
|
|
25576
|
+
const attributes = {
|
|
25577
|
+
input_tokens: row.inputTokens,
|
|
25578
|
+
output_tokens: row.outputTokens,
|
|
25579
|
+
cache_creation_input_tokens: row.cacheCreationTokens,
|
|
25580
|
+
cache_read_input_tokens: row.cacheReadTokens,
|
|
25581
|
+
ephemeral_1h_input_tokens: row.ephemeral1hTokens,
|
|
25582
|
+
ephemeral_5m_input_tokens: row.ephemeral5mTokens,
|
|
25583
|
+
web_search_requests: row.webSearchRequests
|
|
25584
|
+
};
|
|
25585
|
+
if (row.provider !== null) attributes.provider = row.provider;
|
|
25586
|
+
if (row.model !== null) attributes.model = row.model;
|
|
25587
|
+
if (row.serviceTier !== null) attributes.service_tier = row.serviceTier;
|
|
25588
|
+
return { sessionId: row.sessionId, attributes };
|
|
25589
|
+
});
|
|
25590
|
+
}
|
|
25403
25591
|
var SESSION_ROOT = `event_type = 'session'`;
|
|
25404
25592
|
var HAS_ACTIVITY = `EXISTS (
|
|
25405
25593
|
SELECT 1 FROM audit_events c
|
|
@@ -25425,16 +25613,17 @@ var SqliteActivityRepository = class {
|
|
|
25425
25613
|
const liveThreshold = this.now() - LIVE_ACTIVITY_WINDOW_MS;
|
|
25426
25614
|
const liveNow = countScalar(
|
|
25427
25615
|
this.db,
|
|
25428
|
-
`SELECT count(*) AS n FROM audit_events s
|
|
25616
|
+
`SELECT count(*) AS n FROM audit_events s INDEXED BY sqlite_autoindex_audit_events_1
|
|
25429
25617
|
WHERE s.event_type = 'session' AND s.ended_at IS NULL
|
|
25430
|
-
AND
|
|
25431
|
-
|
|
25432
|
-
|
|
25433
|
-
|
|
25434
|
-
|
|
25435
|
-
|
|
25436
|
-
|
|
25437
|
-
|
|
25618
|
+
AND s.id IN (
|
|
25619
|
+
SELECT id FROM audit_events WHERE event_type = 'session' AND started_at >= ?
|
|
25620
|
+
UNION
|
|
25621
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_started_at
|
|
25622
|
+
WHERE started_at >= ?
|
|
25623
|
+
UNION
|
|
25624
|
+
SELECT root_session_id FROM audit_events INDEXED BY idx_audit_ended_at
|
|
25625
|
+
WHERE ended_at >= ?)`,
|
|
25626
|
+
[liveThreshold, liveThreshold, liveThreshold]
|
|
25438
25627
|
);
|
|
25439
25628
|
const toolCallsToday = countScalar(
|
|
25440
25629
|
this.db,
|
|
@@ -25564,7 +25753,7 @@ var SqliteActivityRepository = class {
|
|
|
25564
25753
|
this.db.prepare(
|
|
25565
25754
|
`SELECT ${TIMELINE_COLUMNS}
|
|
25566
25755
|
FROM audit_events
|
|
25567
|
-
WHERE id = ? OR root_session_id = ?
|
|
25756
|
+
WHERE (id = ? OR root_session_id = ?) AND event_type IN (${TIMELINE_EVENT_TYPES})
|
|
25568
25757
|
ORDER BY started_at ASC, id ASC`
|
|
25569
25758
|
),
|
|
25570
25759
|
[sessionId, sessionId]
|
|
@@ -25577,14 +25766,14 @@ var SqliteActivityRepository = class {
|
|
|
25577
25766
|
coalesce(sum(output_tokens), 0) AS output,
|
|
25578
25767
|
coalesce(sum(cache_creation_input_tokens), 0) AS cache_creation,
|
|
25579
25768
|
coalesce(sum(cache_read_input_tokens), 0) AS cache_read
|
|
25580
|
-
FROM audit_events
|
|
25769
|
+
FROM audit_events INDEXED BY idx_audit_session_type
|
|
25581
25770
|
WHERE root_session_id = ? AND event_type = 'llm_call'`
|
|
25582
25771
|
),
|
|
25583
25772
|
[sessionId]
|
|
25584
25773
|
) ?? { input: 0, output: 0, cache_creation: 0, cache_read: 0 };
|
|
25585
25774
|
const primaryModel = getRow(
|
|
25586
25775
|
this.db.prepare(
|
|
25587
|
-
`SELECT model, provider FROM audit_events
|
|
25776
|
+
`SELECT model, provider FROM audit_events INDEXED BY idx_audit_session_type
|
|
25588
25777
|
WHERE root_session_id = ? AND event_type = 'llm_call'
|
|
25589
25778
|
ORDER BY started_at ASC, id ASC
|
|
25590
25779
|
LIMIT 1`
|
|
@@ -25595,7 +25784,7 @@ var SqliteActivityRepository = class {
|
|
|
25595
25784
|
this.db.prepare(
|
|
25596
25785
|
`SELECT coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool')) AS tool,
|
|
25597
25786
|
count(*) AS n
|
|
25598
|
-
FROM audit_events
|
|
25787
|
+
FROM audit_events INDEXED BY idx_audit_session
|
|
25599
25788
|
WHERE root_session_id = ? AND event_type = 'tool_call'
|
|
25600
25789
|
GROUP BY coalesce(json_extract(attributes, '$.tool_name'), json_extract(attributes, '$.tool'))`
|
|
25601
25790
|
),
|
|
@@ -25603,7 +25792,7 @@ var SqliteActivityRepository = class {
|
|
|
25603
25792
|
);
|
|
25604
25793
|
const modelRows = allRows(
|
|
25605
25794
|
this.db.prepare(
|
|
25606
|
-
`SELECT DISTINCT model FROM audit_events
|
|
25795
|
+
`SELECT DISTINCT model FROM audit_events INDEXED BY idx_audit_session_type
|
|
25607
25796
|
WHERE root_session_id = ? AND event_type = 'llm_call' AND model IS NOT NULL AND model <> ''
|
|
25608
25797
|
ORDER BY model`
|
|
25609
25798
|
),
|
|
@@ -25612,7 +25801,7 @@ var SqliteActivityRepository = class {
|
|
|
25612
25801
|
const derivedModels = modelRows.map((r) => r.model);
|
|
25613
25802
|
const commits = countScalar(
|
|
25614
25803
|
this.db,
|
|
25615
|
-
`SELECT count(*) AS n FROM audit_events
|
|
25804
|
+
`SELECT count(*) AS n FROM audit_events INDEXED BY idx_audit_session
|
|
25616
25805
|
WHERE root_session_id = ? AND event_type = 'commit'`,
|
|
25617
25806
|
[sessionId]
|
|
25618
25807
|
);
|
|
@@ -25648,25 +25837,57 @@ var SqliteActivityRepository = class {
|
|
|
25648
25837
|
return Promise.resolve(session);
|
|
25649
25838
|
}
|
|
25650
25839
|
/**
|
|
25651
|
-
* Cross-session token report — every `llm_call`
|
|
25652
|
-
* `started_at >= fromMs`
|
|
25653
|
-
* USD cost DERIVED at read time via the shared
|
|
25654
|
-
*
|
|
25655
|
-
*
|
|
25656
|
-
*
|
|
25840
|
+
* Cross-session token report — every `llm_call` in the store (or in a
|
|
25841
|
+
* `started_at >= fromMs` window, the Activity page's range) grouped per
|
|
25842
|
+
* session, with USD cost DERIVED at read time via the shared
|
|
25843
|
+
* `defaultCostModel` (never stored). The caller collapses these onto
|
|
25844
|
+
* per-model rows with `aggregateTokenUsage`.
|
|
25845
|
+
*
|
|
25846
|
+
* Grouped in SQL over `idx_audit_llm_usage` — one entry per call carrying
|
|
25847
|
+
* the members the rollup sums — and priced once per group, which is exact
|
|
25848
|
+
* (see LlmUsageRow). Reading the bags and folding them in JS measured 31 ms
|
|
25849
|
+
* for a seven-day window at 50k calls, and naming the VIRTUAL columns
|
|
25850
|
+
* against the table 40 ms, since each is a json_extract recomputed per row;
|
|
25851
|
+
* the index stores the values once, at write, and answers the same window in
|
|
25852
|
+
* 8.5 ms. `INDEXED BY` is deliberate: with or without ANALYZE statistics the
|
|
25853
|
+
* planner prefers the general event-type index and fetches every row to
|
|
25854
|
+
* recompute the columns it could have read. The index is one every open
|
|
25855
|
+
* store carries, since opening runs the migrations, so the hard requirement
|
|
25856
|
+
* `INDEXED BY` introduces is already met; token-rollup-plans.test.ts pins
|
|
25857
|
+
* the plan. All-time is a scan of the whole index — still one narrow entry
|
|
25858
|
+
* per call, no bag parsed.
|
|
25657
25859
|
*/
|
|
25658
25860
|
tokenReports(fromMs) {
|
|
25659
|
-
const
|
|
25660
|
-
|
|
25861
|
+
const rows = allRows(
|
|
25862
|
+
this.db.prepare(
|
|
25863
|
+
`${LLM_USAGE_SELECT}
|
|
25864
|
+
FROM audit_events INDEXED BY idx_audit_llm_usage
|
|
25865
|
+
WHERE ${LLM_USAGE_SCOPE}${fromMs === void 0 ? "" : " AND started_at >= ?"}
|
|
25866
|
+
${LLM_USAGE_GROUP}`
|
|
25867
|
+
),
|
|
25868
|
+
fromMs === void 0 ? void 0 : [fromMs]
|
|
25869
|
+
);
|
|
25870
|
+
return Promise.resolve(buildTokenReports(usageLeaves(rows), defaultCostModel));
|
|
25661
25871
|
}
|
|
25662
25872
|
/**
|
|
25663
|
-
* One session's token report — its `llm_call`
|
|
25664
|
-
* model) with derived cost, or `null` when the session made no
|
|
25665
|
-
* (an empty/tool-only session). Feeds the session-detail pane's
|
|
25666
|
-
* breakdown + estimated cost.
|
|
25873
|
+
* One session's token report — its `llm_call`s grouped per (provider,
|
|
25874
|
+
* model, tier) with derived cost, or `null` when the session made no
|
|
25875
|
+
* `llm_call`s (an empty/tool-only session). Feeds the session-detail pane's
|
|
25876
|
+
* per-model breakdown + estimated cost. The same rollup as `tokenReports`,
|
|
25877
|
+
* seeking one root through a root-led `llm_call` index; the bag-reading fold
|
|
25878
|
+
* it replaces walked every `llm_call` in the store to find one session's.
|
|
25667
25879
|
*/
|
|
25668
25880
|
tokenReportForSession(sessionId) {
|
|
25669
|
-
const
|
|
25881
|
+
const rows = allRows(
|
|
25882
|
+
this.db.prepare(
|
|
25883
|
+
`${LLM_USAGE_SELECT}
|
|
25884
|
+
FROM audit_events
|
|
25885
|
+
WHERE ${LLM_USAGE_SCOPE} AND root_session_id = ?
|
|
25886
|
+
${LLM_USAGE_GROUP}`
|
|
25887
|
+
),
|
|
25888
|
+
[sessionId]
|
|
25889
|
+
);
|
|
25890
|
+
const reports = buildTokenReports(usageLeaves(rows), defaultCostModel);
|
|
25670
25891
|
return Promise.resolve(reports[0] ?? null);
|
|
25671
25892
|
}
|
|
25672
25893
|
/**
|
|
@@ -25690,42 +25911,6 @@ var SqliteActivityRepository = class {
|
|
|
25690
25911
|
for (const row of rows) seen.add(toHarness(row.harness));
|
|
25691
25912
|
return Promise.resolve([...seen]);
|
|
25692
25913
|
}
|
|
25693
|
-
/**
|
|
25694
|
-
* The raw `llm_call` leaves (session id + parsed attribute bag) for the token
|
|
25695
|
-
* rollups, optionally narrowed to one session and/or a `started_at >= fromMs`
|
|
25696
|
-
* window. A leaf whose attributes blob is NULL or unparseable is skipped
|
|
25697
|
-
* (best-effort read — a corrupt bag never breaks the report). `root_session_id`
|
|
25698
|
-
* is the leaf's session (the reconciler sets parent_id = root_session_id).
|
|
25699
|
-
*/
|
|
25700
|
-
readLlmCallLeaves(opts = {}) {
|
|
25701
|
-
const conditions = ["event_type = 'llm_call'", "attributes IS NOT NULL"];
|
|
25702
|
-
const params = [];
|
|
25703
|
-
if (opts.sessionId !== void 0) {
|
|
25704
|
-
conditions.push("root_session_id = ?");
|
|
25705
|
-
params.push(opts.sessionId);
|
|
25706
|
-
}
|
|
25707
|
-
if (opts.fromMs !== void 0) {
|
|
25708
|
-
conditions.push("started_at >= ?");
|
|
25709
|
-
params.push(opts.fromMs);
|
|
25710
|
-
}
|
|
25711
|
-
const rows = allRows(
|
|
25712
|
-
this.db.prepare(
|
|
25713
|
-
`SELECT root_session_id AS sessionId, attributes
|
|
25714
|
-
FROM audit_events
|
|
25715
|
-
WHERE ${conditions.join(" AND ")}`
|
|
25716
|
-
),
|
|
25717
|
-
params
|
|
25718
|
-
);
|
|
25719
|
-
return mapRowsTolerant(
|
|
25720
|
-
rows.filter(
|
|
25721
|
-
(row) => row.sessionId !== null
|
|
25722
|
-
),
|
|
25723
|
-
(row) => ({
|
|
25724
|
-
sessionId: row.sessionId,
|
|
25725
|
-
attributes: JSON.parse(row.attributes)
|
|
25726
|
-
})
|
|
25727
|
-
);
|
|
25728
|
-
}
|
|
25729
25914
|
/**
|
|
25730
25915
|
* Per-session turns/findings/shares + last-activity for a page of session ids,
|
|
25731
25916
|
* in grouped queries (not one per row). An id with no matching rows still
|
|
@@ -25740,20 +25925,23 @@ var SqliteActivityRepository = class {
|
|
|
25740
25925
|
const inClause = placeholders(sessionIds.length);
|
|
25741
25926
|
const lastActivityRows = allRows(
|
|
25742
25927
|
this.db.prepare(
|
|
25743
|
-
`SELECT
|
|
25744
|
-
|
|
25745
|
-
|
|
25928
|
+
`SELECT ids.value AS id,
|
|
25929
|
+
(SELECT max(started_at) FROM audit_events e WHERE e.root_session_id = ids.value) AS ms,
|
|
25930
|
+
(SELECT max(ended_at) FROM audit_events e
|
|
25931
|
+
WHERE e.root_session_id = ids.value AND e.ended_at IS NOT NULL) AS me
|
|
25932
|
+
FROM json_each(?) AS ids`
|
|
25746
25933
|
),
|
|
25747
|
-
sessionIds
|
|
25934
|
+
[JSON.stringify(sessionIds)]
|
|
25748
25935
|
);
|
|
25749
25936
|
for (const row of lastActivityRows) {
|
|
25750
|
-
if (row.id === null) continue;
|
|
25751
25937
|
const entry = result.get(row.id);
|
|
25752
|
-
|
|
25938
|
+
const last = Math.max(row.ms ?? 0, row.me ?? 0);
|
|
25939
|
+
if (entry && last > 0) entry.lastActivityMs = last;
|
|
25753
25940
|
}
|
|
25754
25941
|
const turnsRows = allRows(
|
|
25755
25942
|
this.db.prepare(
|
|
25756
|
-
`SELECT root_session_id AS id, count(*) AS n
|
|
25943
|
+
`SELECT root_session_id AS id, count(*) AS n
|
|
25944
|
+
FROM audit_events INDEXED BY idx_audit_session_prompt
|
|
25757
25945
|
WHERE root_session_id IN (${inClause}) AND event_type = 'prompt'
|
|
25758
25946
|
GROUP BY root_session_id`
|
|
25759
25947
|
),
|
|
@@ -25768,7 +25956,7 @@ var SqliteActivityRepository = class {
|
|
|
25768
25956
|
this.db.prepare(
|
|
25769
25957
|
`SELECT root_session_id AS id,
|
|
25770
25958
|
count(DISTINCT json_extract(attributes, '$.run_key')) AS n
|
|
25771
|
-
FROM audit_events
|
|
25959
|
+
FROM audit_events INDEXED BY idx_audit_session_run_key
|
|
25772
25960
|
WHERE root_session_id IN (${inClause}) AND event_type = 'llm_call'
|
|
25773
25961
|
AND json_extract(attributes, '$.run_key') IS NOT NULL
|
|
25774
25962
|
GROUP BY root_session_id`
|
|
@@ -25798,7 +25986,7 @@ var SqliteActivityRepository = class {
|
|
|
25798
25986
|
this.db.prepare(
|
|
25799
25987
|
`SELECT root_session_id AS id,
|
|
25800
25988
|
count(DISTINCT json_extract(attributes, '$.destination')) AS n
|
|
25801
|
-
FROM audit_events
|
|
25989
|
+
FROM audit_events INDEXED BY idx_audit_session_share
|
|
25802
25990
|
WHERE root_session_id IN (${inClause}) AND event_type = 'share'
|
|
25803
25991
|
GROUP BY root_session_id`
|
|
25804
25992
|
),
|
|
@@ -26827,7 +27015,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26827
27015
|
|
|
26828
27016
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26829
27017
|
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26830
|
-
var SCAN_BATCH_ROWS = 1e3;
|
|
26831
27018
|
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26832
27019
|
var LOCATION_RULE_IDS_CAP = 20;
|
|
26833
27020
|
function compareLocationOrder(a, b) {
|
|
@@ -26856,6 +27043,25 @@ function deriveInstanceStatus(row) {
|
|
|
26856
27043
|
latestResolutionStatus: row.latest_status
|
|
26857
27044
|
});
|
|
26858
27045
|
}
|
|
27046
|
+
function toFlatFindingRow(r) {
|
|
27047
|
+
return {
|
|
27048
|
+
id: r.id,
|
|
27049
|
+
ruleId: r.rule_id,
|
|
27050
|
+
category: r.category,
|
|
27051
|
+
severity: r.severity,
|
|
27052
|
+
maskedMatch: r.masked_match,
|
|
27053
|
+
actionTaken: r.action_taken,
|
|
27054
|
+
confidence: r.confidence,
|
|
27055
|
+
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27056
|
+
sourceTool: r.source_tool,
|
|
27057
|
+
repo: r.repo ?? "",
|
|
27058
|
+
file: r.file ?? "",
|
|
27059
|
+
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27060
|
+
eventId: r.event_id,
|
|
27061
|
+
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27062
|
+
status: deriveInstanceStatus(r)
|
|
27063
|
+
};
|
|
27064
|
+
}
|
|
26859
27065
|
function encodeGroupCursor(group) {
|
|
26860
27066
|
const payload = {
|
|
26861
27067
|
sev: group.severity,
|
|
@@ -26931,7 +27137,7 @@ var SqliteFindingsRepository = class {
|
|
|
26931
27137
|
this.db.prepare(
|
|
26932
27138
|
`SELECT f.id, f.audit_event_id AS event_id, d.rule_id, d.category, d.severity,
|
|
26933
27139
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
26934
|
-
|
|
27140
|
+
e.source_tool AS source_tool,
|
|
26935
27141
|
e.event_type AS kind
|
|
26936
27142
|
FROM audit_events e
|
|
26937
27143
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
@@ -27039,56 +27245,11 @@ var SqliteFindingsRepository = class {
|
|
|
27039
27245
|
predicate,
|
|
27040
27246
|
params: sessionParams
|
|
27041
27247
|
});
|
|
27042
|
-
const rows =
|
|
27043
|
-
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
|
|
27047
|
-
FROM (
|
|
27048
|
-
SELECT f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
27049
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
27050
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27051
|
-
e.started_at AS occurred_at,
|
|
27052
|
-
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
27053
|
-
json_extract(e.attributes, '$.repo') AS repo,
|
|
27054
|
-
json_extract(e.attributes, '$.file_path') AS file,
|
|
27055
|
-
json_extract(e.attributes, '$.tool_name') AS tool_name,
|
|
27056
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27057
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27058
|
-
latest.status AS latest_status,
|
|
27059
|
-
ROW_NUMBER() OVER (
|
|
27060
|
-
PARTITION BY d.rule_id
|
|
27061
|
-
ORDER BY e.started_at DESC, f.id DESC
|
|
27062
|
-
) AS rn
|
|
27063
|
-
FROM inspection_findings f
|
|
27064
|
-
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27065
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27066
|
-
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
27067
|
-
ON latest.finding_key = f.finding_key
|
|
27068
|
-
${predicate}
|
|
27069
|
-
)
|
|
27070
|
-
WHERE rn <= :cap
|
|
27071
|
-
ORDER BY occurred_at DESC, id DESC`
|
|
27072
|
-
),
|
|
27073
|
-
{ cap: PREVIEW_INSTANCES_PER_GROUP, ...sessionParams }
|
|
27074
|
-
);
|
|
27075
|
-
const groupable = rows.map((r) => ({
|
|
27076
|
-
id: r.id,
|
|
27077
|
-
ruleId: r.rule_id,
|
|
27078
|
-
category: r.category,
|
|
27079
|
-
severity: r.severity,
|
|
27080
|
-
maskedMatch: r.masked_match,
|
|
27081
|
-
actionTaken: r.action_taken,
|
|
27082
|
-
confidence: r.confidence,
|
|
27083
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27084
|
-
sourceTool: r.source_tool,
|
|
27085
|
-
repo: r.repo ?? "",
|
|
27086
|
-
file: r.file ?? "",
|
|
27087
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27088
|
-
eventId: r.event_id,
|
|
27089
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27090
|
-
status: deriveInstanceStatus(r)
|
|
27091
|
-
}));
|
|
27248
|
+
const rows = this.previewRows(aggregates, {
|
|
27249
|
+
sessionId: query.sessionId,
|
|
27250
|
+
from: query.from
|
|
27251
|
+
});
|
|
27252
|
+
const groupable = rows.map(toFlatFindingRow);
|
|
27092
27253
|
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27093
27254
|
const filterOpts = {
|
|
27094
27255
|
severity: query.severity,
|
|
@@ -27174,8 +27335,10 @@ var SqliteFindingsRepository = class {
|
|
|
27174
27335
|
*
|
|
27175
27336
|
* The scan runs from the top of the scope on every request, not from the
|
|
27176
27337
|
* cursor: `totals` and `facets` describe the whole filtered scope and must not
|
|
27177
|
-
* move as the caller pages. Rows
|
|
27178
|
-
*
|
|
27338
|
+
* move as the caller pages. Rows come off ONE statement, iterated rather
|
|
27339
|
+
* than materialized (`scanFindingRows`), so memory stays flat while the
|
|
27340
|
+
* counting runs — a generator streaming the index order, not a sequence of
|
|
27341
|
+
* fetched batches; only the page itself is retained.
|
|
27179
27342
|
*/
|
|
27180
27343
|
listFindingInstances(query) {
|
|
27181
27344
|
const opts = {
|
|
@@ -27191,6 +27354,10 @@ var SqliteFindingsRepository = class {
|
|
|
27191
27354
|
};
|
|
27192
27355
|
const limit = query.limit ?? DEFAULT_FLAT_FINDINGS_LIMIT;
|
|
27193
27356
|
const cursor = query.cursor === void 0 ? null : decodeKeysetCursor(query.cursor);
|
|
27357
|
+
const isPastCursor = cursor === null ? () => true : (row) => {
|
|
27358
|
+
const rowMs = isoToEpochMillis(row.occurredAt);
|
|
27359
|
+
return rowMs <= cursor.startedAtMs && (rowMs < cursor.startedAtMs || row.id < cursor.id);
|
|
27360
|
+
};
|
|
27194
27361
|
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27195
27362
|
const items = [];
|
|
27196
27363
|
let total = 0;
|
|
@@ -27203,6 +27370,7 @@ var SqliteFindingsRepository = class {
|
|
|
27203
27370
|
accumulator.add(row);
|
|
27204
27371
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27205
27372
|
total += 1;
|
|
27373
|
+
if (!isPastCursor(row)) continue;
|
|
27206
27374
|
if (items.length < limit) {
|
|
27207
27375
|
items.push(toInstanceDetail(row));
|
|
27208
27376
|
last = row;
|
|
@@ -27211,15 +27379,6 @@ var SqliteFindingsRepository = class {
|
|
|
27211
27379
|
}
|
|
27212
27380
|
}
|
|
27213
27381
|
const nextCursor = hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null;
|
|
27214
|
-
if (cursor !== null) {
|
|
27215
|
-
const resumed = this.pageAfter(cursor, opts, limit, query);
|
|
27216
|
-
return Promise.resolve({
|
|
27217
|
-
totals: { findings: total },
|
|
27218
|
-
facets: accumulator.facets(),
|
|
27219
|
-
items: resumed.items,
|
|
27220
|
-
nextCursor: resumed.nextCursor
|
|
27221
|
-
});
|
|
27222
|
-
}
|
|
27223
27382
|
return Promise.resolve({
|
|
27224
27383
|
totals: { findings: total },
|
|
27225
27384
|
facets: accumulator.facets(),
|
|
@@ -27227,35 +27386,6 @@ var SqliteFindingsRepository = class {
|
|
|
27227
27386
|
nextCursor
|
|
27228
27387
|
});
|
|
27229
27388
|
}
|
|
27230
|
-
/**
|
|
27231
|
-
* The page of matching rows strictly after `cursor`. Separate from the
|
|
27232
|
-
* counting pass because that one starts at the top of the scope by design;
|
|
27233
|
-
* this one narrows the scan with the same keyset predicate the activity list
|
|
27234
|
-
* uses, so a later page costs less than the first rather than more.
|
|
27235
|
-
*/
|
|
27236
|
-
pageAfter(cursor, opts, limit, query) {
|
|
27237
|
-
const items = [];
|
|
27238
|
-
let last;
|
|
27239
|
-
let hasMore = false;
|
|
27240
|
-
for (const row of this.scanFindingRows({
|
|
27241
|
-
sessionId: query.sessionId,
|
|
27242
|
-
from: query.from,
|
|
27243
|
-
after: cursor
|
|
27244
|
-
})) {
|
|
27245
|
-
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27246
|
-
if (items.length < limit) {
|
|
27247
|
-
items.push(toInstanceDetail(row));
|
|
27248
|
-
last = row;
|
|
27249
|
-
} else {
|
|
27250
|
-
hasMore = true;
|
|
27251
|
-
break;
|
|
27252
|
-
}
|
|
27253
|
-
}
|
|
27254
|
-
return {
|
|
27255
|
-
items,
|
|
27256
|
-
nextCursor: hasMore && last ? encodeKeysetCursor({ startedAtMs: isoToEpochMillis(last.occurredAt), id: last.id }) : null
|
|
27257
|
-
};
|
|
27258
|
-
}
|
|
27259
27389
|
/**
|
|
27260
27390
|
* The same findings folded by location: repository, then file within it.
|
|
27261
27391
|
*
|
|
@@ -27338,25 +27468,111 @@ var SqliteFindingsRepository = class {
|
|
|
27338
27468
|
});
|
|
27339
27469
|
}
|
|
27340
27470
|
/**
|
|
27341
|
-
*
|
|
27471
|
+
* Each group's newest instances, for the table's expanded rows.
|
|
27472
|
+
*
|
|
27473
|
+
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27474
|
+
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27475
|
+
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27476
|
+
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27477
|
+
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27478
|
+
* store while the answer does not.
|
|
27479
|
+
*
|
|
27480
|
+
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27481
|
+
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27482
|
+
* which is already the order the page wants, and keeps rows per rule until
|
|
27483
|
+
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27484
|
+
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27485
|
+
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27486
|
+
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27487
|
+
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27488
|
+
* store with many firing rules widens it. The bound that DOES hold
|
|
27489
|
+
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27490
|
+
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27491
|
+
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27492
|
+
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27493
|
+
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27494
|
+
* everything in scope with a block sort of the id tie-break only, never a
|
|
27495
|
+
* sort of the scope, which is still that floor.
|
|
27496
|
+
*
|
|
27497
|
+
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27498
|
+
* run without a shared snapshot, so a capture landing between them can add a
|
|
27499
|
+
* rule here that has no counts there, and the counts are what the group is
|
|
27500
|
+
* built from.
|
|
27501
|
+
*/
|
|
27502
|
+
previewRows(aggregates, scope) {
|
|
27503
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
27504
|
+
let remaining = 0;
|
|
27505
|
+
for (const [ruleId, agg] of aggregates) {
|
|
27506
|
+
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27507
|
+
wanted.set(ruleId, n);
|
|
27508
|
+
remaining += n;
|
|
27509
|
+
}
|
|
27510
|
+
const rows = [];
|
|
27511
|
+
if (remaining === 0) return rows;
|
|
27512
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27513
|
+
const taken = /* @__PURE__ */ new Map();
|
|
27514
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27515
|
+
const want = wanted.get(r.rule_id);
|
|
27516
|
+
if (want === void 0) continue;
|
|
27517
|
+
const have = taken.get(r.rule_id) ?? 0;
|
|
27518
|
+
if (have >= want) continue;
|
|
27519
|
+
taken.set(r.rule_id, have + 1);
|
|
27520
|
+
rows.push(r);
|
|
27521
|
+
remaining -= 1;
|
|
27522
|
+
if (remaining === 0) break;
|
|
27523
|
+
}
|
|
27524
|
+
return rows;
|
|
27525
|
+
}
|
|
27526
|
+
/**
|
|
27527
|
+
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27342
27528
|
*
|
|
27343
27529
|
* A generator so a caller streams the scope without it ever being an array:
|
|
27344
27530
|
* the flat list counts and facets the whole filtered scope, which on a large
|
|
27345
|
-
* store is far more rows than any page.
|
|
27346
|
-
*
|
|
27347
|
-
*
|
|
27348
|
-
*
|
|
27349
|
-
*
|
|
27350
|
-
*
|
|
27351
|
-
* makes it a point lookup per row, and the derived table would re-materialize
|
|
27352
|
-
* a window over the whole resolution table once per batch.
|
|
27531
|
+
* store is far more rows than any page. The rows come off ONE statement,
|
|
27532
|
+
* iterated rather than materialized, in the index order `findingScanSql`
|
|
27533
|
+
* arranges — so the scan is a single pass with a block sort of the id
|
|
27534
|
+
* tie-break only, never a sort of the scope, where a sequence of
|
|
27535
|
+
* keyset-bounded batches re-sorted everything below the cursor on every
|
|
27536
|
+
* batch and cost the square of the scope.
|
|
27353
27537
|
*
|
|
27354
|
-
* `
|
|
27355
|
-
* would be missing from its own facet, which is
|
|
27356
|
-
* dimension
|
|
27538
|
+
* `sessionId` and `from` carry ONLY what no facet counts — a filter
|
|
27539
|
+
* dimension narrowed here would be missing from its own facet, which is
|
|
27540
|
+
* computed by excluding that dimension (see listFindingInstances). There is
|
|
27541
|
+
* no `after`/cursor parameter: a keyset page is collected inline from this
|
|
27542
|
+
* same pass (`listFindingInstances`' `isPastCursor`) rather than by a second,
|
|
27543
|
+
* narrower statement, since the counting pass already visits every row a
|
|
27544
|
+
* page-2+ request would otherwise re-seek for.
|
|
27357
27545
|
*/
|
|
27358
27546
|
*scanFindingRows(scope) {
|
|
27359
|
-
const
|
|
27547
|
+
const { sql, params } = this.findingScanSql(scope);
|
|
27548
|
+
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27549
|
+
yield toFlatFindingRow(r);
|
|
27550
|
+
}
|
|
27551
|
+
}
|
|
27552
|
+
/**
|
|
27553
|
+
* The one statement both instance-level scans run: every finding in scope,
|
|
27554
|
+
* joined to its event and definition, newest first.
|
|
27555
|
+
*
|
|
27556
|
+
* THE PLAN IS THE POINT, and two things in the SQL exist only to pin it —
|
|
27557
|
+
* the same two `recentFindings` documents at length, for the same reason:
|
|
27558
|
+
*
|
|
27559
|
+
* - **`+e.event_type`** makes the capture-kind predicate non-indexable, so
|
|
27560
|
+
* the planner cannot pick `idx_audit_type_t` and then sort. That index
|
|
27561
|
+
* yields `started_at` order per event type, not across the four, so
|
|
27562
|
+
* satisfying the ORDER BY from it would need a merge SQLite does not do.
|
|
27563
|
+
* Freed of it, the planner walks `idx_audit_started_at` backwards — or
|
|
27564
|
+
* `idx_audit_session` for a session scope, which is also `started_at`
|
|
27565
|
+
* ordered within the session — and the order falls out of the index.
|
|
27566
|
+
* - **`CROSS JOIN`** pins `audit_events` as the driving table. With plain
|
|
27567
|
+
* JOINs the planner drives from the findings and sorts everything.
|
|
27568
|
+
*
|
|
27569
|
+
* The latest-resolution lookup is the CORRELATED form: only `status` is
|
|
27570
|
+
* needed, `idx_finding_resolution_key_created` answers it with one backward
|
|
27571
|
+
* index probe per keyed row, and a derived table over the whole resolution
|
|
27572
|
+
* table would be materialized before the first row streamed.
|
|
27573
|
+
*/
|
|
27574
|
+
findingScanSql(scope) {
|
|
27575
|
+
const conditions = [`+e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`];
|
|
27360
27576
|
const params = [];
|
|
27361
27577
|
if (scope.sessionId !== void 0 && scope.sessionId !== "") {
|
|
27362
27578
|
conditions.push("e.root_session_id = ?");
|
|
@@ -27370,58 +27586,24 @@ var SqliteFindingsRepository = class {
|
|
|
27370
27586
|
d.severity AS severity, f.masked_match AS masked_match,
|
|
27371
27587
|
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27372
27588
|
e.started_at AS occurred_at,
|
|
27373
|
-
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
|
|
27589
|
+
e.source_tool AS source_tool,
|
|
27590
|
+
e.repo AS repo,
|
|
27591
|
+
e.file_path AS file,
|
|
27592
|
+
e.tool_name AS tool_name,
|
|
27377
27593
|
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27378
27594
|
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27379
27595
|
${latestResolutionStatusSql("f")} AS latest_status
|
|
27380
|
-
FROM
|
|
27381
|
-
JOIN
|
|
27382
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27596
|
+
FROM audit_events e
|
|
27597
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27598
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27383
27599
|
WHERE ${conditions.join(" AND ")}
|
|
27384
|
-
|
|
27385
|
-
|
|
27386
|
-
LIMIT ?`;
|
|
27387
|
-
let after = scope.after ?? { startedAtMs: Number.MAX_SAFE_INTEGER, id: "\uFFFF" };
|
|
27388
|
-
for (; ; ) {
|
|
27389
|
-
const rows = allRows(this.db.prepare(sql), [
|
|
27390
|
-
...params,
|
|
27391
|
-
after.startedAtMs,
|
|
27392
|
-
after.startedAtMs,
|
|
27393
|
-
after.id,
|
|
27394
|
-
SCAN_BATCH_ROWS
|
|
27395
|
-
]);
|
|
27396
|
-
for (const r of rows) {
|
|
27397
|
-
yield {
|
|
27398
|
-
id: r.id,
|
|
27399
|
-
ruleId: r.rule_id,
|
|
27400
|
-
category: r.category,
|
|
27401
|
-
severity: r.severity,
|
|
27402
|
-
maskedMatch: r.masked_match,
|
|
27403
|
-
actionTaken: r.action_taken,
|
|
27404
|
-
confidence: r.confidence,
|
|
27405
|
-
occurredAt: epochMillisToIso(r.occurred_at),
|
|
27406
|
-
sourceTool: r.source_tool,
|
|
27407
|
-
repo: r.repo ?? "",
|
|
27408
|
-
file: r.file ?? "",
|
|
27409
|
-
...r.tool_name === null ? {} : { toolName: r.tool_name },
|
|
27410
|
-
eventId: r.event_id,
|
|
27411
|
-
...r.session_id === null ? {} : { sessionId: r.session_id },
|
|
27412
|
-
status: deriveInstanceStatus(r)
|
|
27413
|
-
};
|
|
27414
|
-
}
|
|
27415
|
-
if (rows.length < SCAN_BATCH_ROWS) return;
|
|
27416
|
-
const lastRow = rows[rows.length - 1];
|
|
27417
|
-
if (lastRow === void 0) return;
|
|
27418
|
-
after = { startedAtMs: lastRow.occurred_at, id: lastRow.id };
|
|
27419
|
-
}
|
|
27600
|
+
ORDER BY e.started_at DESC, f.id DESC`;
|
|
27601
|
+
return { sql, params };
|
|
27420
27602
|
}
|
|
27421
27603
|
groupAggregates(withSearchText, scope) {
|
|
27422
|
-
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT
|
|
27423
|
-
group_concat(DISTINCT
|
|
27424
|
-
group_concat(DISTINCT 'via ' ||
|
|
27604
|
+
const innerSearchColumns = withSearchText ? `, group_concat(DISTINCT e.repo) AS repos,
|
|
27605
|
+
group_concat(DISTINCT e.file_path) AS files,
|
|
27606
|
+
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27425
27607
|
const rows = this.db.prepare(
|
|
27426
27608
|
`SELECT rule_id,
|
|
27427
27609
|
sum(tuple_count) AS instance_count,
|
|
@@ -27439,7 +27621,7 @@ var SqliteFindingsRepository = class {
|
|
|
27439
27621
|
coalesce(latest.status, '') AS status_tuple,
|
|
27440
27622
|
count(*) AS tuple_count,
|
|
27441
27623
|
max(e.started_at) AS latest_at,
|
|
27442
|
-
group_concat(DISTINCT
|
|
27624
|
+
group_concat(DISTINCT e.source_tool) AS source_tools,
|
|
27443
27625
|
group_concat(DISTINCT f.action_taken) AS actions_taken
|
|
27444
27626
|
${innerSearchColumns}
|
|
27445
27627
|
FROM inspection_findings f
|
|
@@ -27570,6 +27752,8 @@ function isoDay(ms) {
|
|
|
27570
27752
|
// ../../packages/persistence/src/repositories/history-sync.ts
|
|
27571
27753
|
var STRUCTURAL_EVENT_TYPES = ["session", "llm_call", "tool_call"];
|
|
27572
27754
|
var TYPE_LIST = STRUCTURAL_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27755
|
+
var OUTBOX_CAPTURE_EVENT_TYPES = ["prompt", "response", "tool_use"];
|
|
27756
|
+
var CAPTURE_TYPE_LIST = OUTBOX_CAPTURE_EVENT_TYPES.map((t) => `'${t}'`).join(", ");
|
|
27573
27757
|
var SKIPPED = -1;
|
|
27574
27758
|
var ROW_COLUMNS = `id,
|
|
27575
27759
|
parent_id AS parentId,
|
|
@@ -27609,6 +27793,20 @@ var SqliteHistorySyncRepository = class {
|
|
|
27609
27793
|
ORDER BY (event_type = 'session') DESC, started_at
|
|
27610
27794
|
LIMIT :limit`
|
|
27611
27795
|
);
|
|
27796
|
+
this.captureRowsStmt = db.prepare(
|
|
27797
|
+
`SELECT ${ROW_COLUMNS}
|
|
27798
|
+
FROM audit_events
|
|
27799
|
+
WHERE synced_at IS NULL
|
|
27800
|
+
AND sync_claimed_at IS NULL
|
|
27801
|
+
AND outbox_owed = 1
|
|
27802
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27803
|
+
AND started_at < :before
|
|
27804
|
+
ORDER BY started_at
|
|
27805
|
+
LIMIT :limit`
|
|
27806
|
+
);
|
|
27807
|
+
this.markOwedStmt = db.prepare(
|
|
27808
|
+
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27809
|
+
);
|
|
27612
27810
|
this.stampStmt = db.prepare(
|
|
27613
27811
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27614
27812
|
);
|
|
@@ -27640,6 +27838,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27640
27838
|
FROM audit_events
|
|
27641
27839
|
WHERE event_type IN (${TYPE_LIST})`
|
|
27642
27840
|
);
|
|
27841
|
+
this.captureSkipCountStmt = db.prepare(
|
|
27842
|
+
`SELECT COUNT(*) AS skipped
|
|
27843
|
+
FROM audit_events
|
|
27844
|
+
WHERE synced_at = ${String(SKIPPED)}
|
|
27845
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27846
|
+
);
|
|
27643
27847
|
this.fingerprintStmt = db.prepare(
|
|
27644
27848
|
`SELECT endpoint_fingerprint AS fingerprint, backlog_before AS backlogBefore
|
|
27645
27849
|
FROM history_sync WHERE id = 1`
|
|
@@ -27649,6 +27853,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27649
27853
|
SET endpoint_fingerprint = :fingerprint, backlog_before = :backlogBefore
|
|
27650
27854
|
WHERE id = 1`
|
|
27651
27855
|
);
|
|
27856
|
+
this.disownCapturesStmt = db.prepare(
|
|
27857
|
+
`UPDATE audit_events SET outbox_owed = NULL
|
|
27858
|
+
WHERE outbox_owed IS NOT NULL AND event_type IN (${CAPTURE_TYPE_LIST})`
|
|
27859
|
+
);
|
|
27652
27860
|
this.rearmStmt = db.prepare(
|
|
27653
27861
|
`UPDATE audit_events SET synced_at = NULL
|
|
27654
27862
|
WHERE synced_at > 0 AND event_type IN (${TYPE_LIST})`
|
|
@@ -27721,6 +27929,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27721
27929
|
closeWindowStmt;
|
|
27722
27930
|
releaseBoundaryStmt;
|
|
27723
27931
|
freezeBoundaryStmt;
|
|
27932
|
+
captureRowsStmt;
|
|
27933
|
+
markOwedStmt;
|
|
27934
|
+
captureSkipCountStmt;
|
|
27935
|
+
disownCapturesStmt;
|
|
27724
27936
|
partitionStmt;
|
|
27725
27937
|
claimRowStmt;
|
|
27726
27938
|
releaseRowStmt;
|
|
@@ -27754,6 +27966,34 @@ var SqliteHistorySyncRepository = class {
|
|
|
27754
27966
|
pendingRows(sessionId, limit, before) {
|
|
27755
27967
|
return allRows(this.rowsStmt, { sessionId, limit, before });
|
|
27756
27968
|
}
|
|
27969
|
+
/**
|
|
27970
|
+
* Captures this machine still owes the deployment, oldest first.
|
|
27971
|
+
*
|
|
27972
|
+
* Selected by the `outbox_owed` marker the attached forward path writes, not
|
|
27973
|
+
* by a time window — see captureRowsStmt for why a window could not express
|
|
27974
|
+
* this. `before` is the grace window that leaves a just-recorded capture to
|
|
27975
|
+
* the live path.
|
|
27976
|
+
*/
|
|
27977
|
+
pendingCaptureRows(limit, before) {
|
|
27978
|
+
return allRows(this.captureRowsStmt, { limit, before });
|
|
27979
|
+
}
|
|
27980
|
+
/**
|
|
27981
|
+
* Record that a capture is OWED to the deployment.
|
|
27982
|
+
*
|
|
27983
|
+
* Written by the attached forward path when a live send did not confirm
|
|
27984
|
+
* delivery, and read by the drain as the whole of its eligibility test. It is
|
|
27985
|
+
* a fact rather than an inference: the machine was attached, the send did not
|
|
27986
|
+
* land, so the row is owed — which no time window can state, because the same
|
|
27987
|
+
* window that holds the rows a past attachment left owed also holds every
|
|
27988
|
+
* capture recorded while the machine was DETACHED, and those were never
|
|
27989
|
+
* offered to anyone.
|
|
27990
|
+
*
|
|
27991
|
+
* Idempotent, and never un-set: `markSynced` settling the row is what takes it
|
|
27992
|
+
* out of the drain's read.
|
|
27993
|
+
*/
|
|
27994
|
+
markCaptureOwed(id) {
|
|
27995
|
+
this.markOwedStmt.run({ id });
|
|
27996
|
+
}
|
|
27757
27997
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27758
27998
|
markSynced(ids, atMs) {
|
|
27759
27999
|
this.stampAll(ids, atMs);
|
|
@@ -27837,10 +28077,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27837
28077
|
this.countsStmt,
|
|
27838
28078
|
{ before }
|
|
27839
28079
|
);
|
|
28080
|
+
const captures = getRow(this.captureSkipCountStmt);
|
|
27840
28081
|
return {
|
|
27841
28082
|
pending: row?.pending ?? 0,
|
|
27842
28083
|
sent: row?.sent ?? 0,
|
|
27843
|
-
skipped: row?.skipped ?? 0
|
|
28084
|
+
skipped: row?.skipped ?? 0,
|
|
28085
|
+
capturesSkipped: captures?.skipped ?? 0
|
|
27844
28086
|
};
|
|
27845
28087
|
}
|
|
27846
28088
|
/**
|
|
@@ -27881,7 +28123,11 @@ var SqliteHistorySyncRepository = class {
|
|
|
27881
28123
|
withTransaction(
|
|
27882
28124
|
this.db,
|
|
27883
28125
|
() => {
|
|
28126
|
+
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27884
28127
|
this.rearmStmt.run();
|
|
28128
|
+
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28129
|
+
this.disownCapturesStmt.run();
|
|
28130
|
+
}
|
|
27885
28131
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27886
28132
|
},
|
|
27887
28133
|
"IMMEDIATE"
|
|
@@ -28078,7 +28324,259 @@ var SqliteInspectionFindingsRepository = class {
|
|
|
28078
28324
|
};
|
|
28079
28325
|
|
|
28080
28326
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
28081
|
-
import { createHash as createHash2, randomUUID as
|
|
28327
|
+
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
28328
|
+
|
|
28329
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28330
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
28331
|
+
import { join as join6 } from "path";
|
|
28332
|
+
|
|
28333
|
+
// ../../packages/persistence/src/local-layout.ts
|
|
28334
|
+
import { renameSync as renameSync3 } from "fs";
|
|
28335
|
+
import { mkdir } from "fs/promises";
|
|
28336
|
+
import { homedir } from "os";
|
|
28337
|
+
import { join as join4 } from "path";
|
|
28338
|
+
function defaultDataDir() {
|
|
28339
|
+
return join4(homedir(), ".aka");
|
|
28340
|
+
}
|
|
28341
|
+
function settingsDir(base = defaultDataDir()) {
|
|
28342
|
+
return join4(base, "settings");
|
|
28343
|
+
}
|
|
28344
|
+
function dataDir(base = defaultDataDir()) {
|
|
28345
|
+
return join4(base, "data");
|
|
28346
|
+
}
|
|
28347
|
+
function dbPath(base = defaultDataDir()) {
|
|
28348
|
+
return join4(dataDir(base), "aka.db");
|
|
28349
|
+
}
|
|
28350
|
+
function keysDir(base = defaultDataDir()) {
|
|
28351
|
+
return join4(base, "keys");
|
|
28352
|
+
}
|
|
28353
|
+
async function ensureDataDir(dir = defaultDataDir()) {
|
|
28354
|
+
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
28355
|
+
tightenDir(dir);
|
|
28356
|
+
}
|
|
28357
|
+
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
28358
|
+
ensureDataDirSync(dir);
|
|
28359
|
+
}
|
|
28360
|
+
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
28361
|
+
const moves = [
|
|
28362
|
+
{ name: "config.json", dest: settingsDir(base) },
|
|
28363
|
+
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
28364
|
+
];
|
|
28365
|
+
for (const { name, dest } of moves) {
|
|
28366
|
+
try {
|
|
28367
|
+
ensureDataDirSync(dest);
|
|
28368
|
+
const moved = join4(dest, name);
|
|
28369
|
+
renameSync3(join4(base, name), moved);
|
|
28370
|
+
tightenFile(moved);
|
|
28371
|
+
} catch {
|
|
28372
|
+
}
|
|
28373
|
+
}
|
|
28374
|
+
}
|
|
28375
|
+
|
|
28376
|
+
// ../../packages/persistence/src/settings.ts
|
|
28377
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
28378
|
+
import { join as join5 } from "path";
|
|
28379
|
+
|
|
28380
|
+
// ../../packages/persistence/src/file-lock.ts
|
|
28381
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
28382
|
+
import {
|
|
28383
|
+
closeSync,
|
|
28384
|
+
existsSync as existsSync2,
|
|
28385
|
+
openSync,
|
|
28386
|
+
readFileSync as readFileSync2,
|
|
28387
|
+
rmSync as rmSync5,
|
|
28388
|
+
statSync as statSync3,
|
|
28389
|
+
writeFileSync as writeFileSync2
|
|
28390
|
+
} from "fs";
|
|
28391
|
+
import { hostname as hostname3 } from "os";
|
|
28392
|
+
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
28393
|
+
|
|
28394
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
28395
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
28396
|
+
import { posix, win32 } from "path";
|
|
28397
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
28398
|
+
if (platform2 === "darwin") {
|
|
28399
|
+
return [
|
|
28400
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
28401
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
28402
|
+
];
|
|
28403
|
+
}
|
|
28404
|
+
if (platform2 === "win32") {
|
|
28405
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
28406
|
+
}
|
|
28407
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28408
|
+
}
|
|
28409
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
28410
|
+
for (const path of paths) {
|
|
28411
|
+
let text;
|
|
28412
|
+
try {
|
|
28413
|
+
text = readFileSync3(path, "utf8");
|
|
28414
|
+
} catch {
|
|
28415
|
+
continue;
|
|
28416
|
+
}
|
|
28417
|
+
const record2 = parseJsonObject(text);
|
|
28418
|
+
if (!record2) continue;
|
|
28419
|
+
const parsed2 = ManagedSettings.safeParse(record2);
|
|
28420
|
+
if (parsed2.success) return parsed2.data;
|
|
28421
|
+
}
|
|
28422
|
+
return null;
|
|
28423
|
+
}
|
|
28424
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
28425
|
+
if (!managed) return settings;
|
|
28426
|
+
const { values } = managed;
|
|
28427
|
+
const merged = { ...settings };
|
|
28428
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
28429
|
+
if (values.controlPlane !== void 0) {
|
|
28430
|
+
merged.controlPlane = {
|
|
28431
|
+
...values.controlPlane,
|
|
28432
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
28433
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
28434
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
28435
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
28436
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
28437
|
+
};
|
|
28438
|
+
}
|
|
28439
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
28440
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
28441
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
28442
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
28443
|
+
if (values.redactFallback !== void 0) merged.redactFallback = values.redactFallback;
|
|
28444
|
+
if (values.vaultConsent !== void 0) {
|
|
28445
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
28446
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
28447
|
+
// at the current version otherwise.
|
|
28448
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
28449
|
+
) : void 0;
|
|
28450
|
+
}
|
|
28451
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
28452
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
28453
|
+
acknowledgedAt: now().toISOString(),
|
|
28454
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
28455
|
+
} : void 0;
|
|
28456
|
+
}
|
|
28457
|
+
return merged;
|
|
28458
|
+
}
|
|
28459
|
+
|
|
28460
|
+
// ../../packages/persistence/src/settings.ts
|
|
28461
|
+
var SETTINGS_FILENAME = "settings.json";
|
|
28462
|
+
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
28463
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
28464
|
+
}
|
|
28465
|
+
function readUserSettings(base) {
|
|
28466
|
+
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
28467
|
+
if (!record2) return defaultWorkspaceSettings();
|
|
28468
|
+
try {
|
|
28469
|
+
return WorkspaceSettings.parse(record2);
|
|
28470
|
+
} catch {
|
|
28471
|
+
return defaultWorkspaceSettings();
|
|
28472
|
+
}
|
|
28473
|
+
}
|
|
28474
|
+
function readJson(file2) {
|
|
28475
|
+
let text;
|
|
28476
|
+
try {
|
|
28477
|
+
text = readFileSync4(file2, "utf8");
|
|
28478
|
+
} catch {
|
|
28479
|
+
return null;
|
|
28480
|
+
}
|
|
28481
|
+
return parseJsonObject(text) ?? null;
|
|
28482
|
+
}
|
|
28483
|
+
|
|
28484
|
+
// ../../packages/persistence/src/policy-floor.ts
|
|
28485
|
+
function refusalMessage(pack, attempted, floor, refusal) {
|
|
28486
|
+
switch (refusal) {
|
|
28487
|
+
case "lock":
|
|
28488
|
+
return `refusing to re-assign '${pack}': its policy is set by the connected control plane`;
|
|
28489
|
+
case "disable":
|
|
28490
|
+
return `refusing to disable '${pack}': it is governed by the connected control plane`;
|
|
28491
|
+
case "floor":
|
|
28492
|
+
return `refusing to set '${pack}' to '${attempted ?? "unassigned"}': the connected control plane requires at least '${floor}'`;
|
|
28493
|
+
}
|
|
28494
|
+
}
|
|
28495
|
+
var PolicyFloorError = class extends Error {
|
|
28496
|
+
/** `namespace/packId` of the detection whose write was refused. */
|
|
28497
|
+
pack;
|
|
28498
|
+
/**
|
|
28499
|
+
* The archetype the caller asked for, or null when the write named none —
|
|
28500
|
+
* clearing the assignment, or switching the detection off.
|
|
28501
|
+
*/
|
|
28502
|
+
attempted;
|
|
28503
|
+
/** The weakest archetype the control plane permits for this pack. */
|
|
28504
|
+
floor;
|
|
28505
|
+
refusal;
|
|
28506
|
+
constructor(pack, attempted, floor, refusal) {
|
|
28507
|
+
super(refusalMessage(pack, attempted, floor, refusal));
|
|
28508
|
+
this.name = "PolicyFloorError";
|
|
28509
|
+
this.pack = pack;
|
|
28510
|
+
this.attempted = attempted;
|
|
28511
|
+
this.floor = floor;
|
|
28512
|
+
this.refusal = refusal;
|
|
28513
|
+
}
|
|
28514
|
+
};
|
|
28515
|
+
function readCachedPolicyBundle(base = defaultDataDir()) {
|
|
28516
|
+
try {
|
|
28517
|
+
const raw = readFileSync5(join6(dataDir(base), POLICY_CACHE_FILENAME), "utf8");
|
|
28518
|
+
const parsed2 = JSON.parse(raw);
|
|
28519
|
+
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
28520
|
+
return PolicyBundle.parse(parsed2.bundle);
|
|
28521
|
+
} catch {
|
|
28522
|
+
return null;
|
|
28523
|
+
}
|
|
28524
|
+
}
|
|
28525
|
+
function indexEnabled(policies) {
|
|
28526
|
+
const byRuleId = /* @__PURE__ */ new Map();
|
|
28527
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
28528
|
+
for (const policy of policies) {
|
|
28529
|
+
if (!policy.enabled) continue;
|
|
28530
|
+
if ("ruleId" in policy.target) {
|
|
28531
|
+
if (!byRuleId.has(policy.target.ruleId)) byRuleId.set(policy.target.ruleId, policy.action);
|
|
28532
|
+
} else if (!byCategory.has(policy.target.category)) {
|
|
28533
|
+
byCategory.set(policy.target.category, policy.action);
|
|
28534
|
+
}
|
|
28535
|
+
}
|
|
28536
|
+
return { byRuleId, byCategory };
|
|
28537
|
+
}
|
|
28538
|
+
function hasAuthoredPolicy(policies, rules, byRuleId) {
|
|
28539
|
+
const ruleIds = new Set(rules.map((rule) => rule.id));
|
|
28540
|
+
const categories = new Set(rules.map((rule) => rule.category));
|
|
28541
|
+
const bundleNamesPack = rules.some((rule) => byRuleId.has(rule.id));
|
|
28542
|
+
return policies.some((policy) => {
|
|
28543
|
+
if (!policy.enabled || policy.provenance !== "authored") return false;
|
|
28544
|
+
return "ruleId" in policy.target ? ruleIds.has(policy.target.ruleId) : bundleNamesPack && categories.has(policy.target.category);
|
|
28545
|
+
});
|
|
28546
|
+
}
|
|
28547
|
+
function controlPlanePolicyFloor(rules, base = defaultDataDir()) {
|
|
28548
|
+
const floors = openControlPlaneFloors(base);
|
|
28549
|
+
return floors === null ? null : floors.floorFor(rules);
|
|
28550
|
+
}
|
|
28551
|
+
function openControlPlaneFloors(base = defaultDataDir()) {
|
|
28552
|
+
if (!isAttached(readWorkspaceSettings(base))) return null;
|
|
28553
|
+
const bundle = readCachedPolicyBundle(base);
|
|
28554
|
+
if (bundle === null) return null;
|
|
28555
|
+
const indexes = indexEnabled(bundle.policies);
|
|
28556
|
+
return { floorFor: (rules) => resolveFloor(rules, bundle.policies, indexes) };
|
|
28557
|
+
}
|
|
28558
|
+
function resolveFloor(rules, policies, { byRuleId, byCategory }) {
|
|
28559
|
+
let action = null;
|
|
28560
|
+
for (const rule of rules) {
|
|
28561
|
+
const resolved = byRuleId.get(rule.id) ?? byCategory.get(rule.category);
|
|
28562
|
+
if (resolved === void 0) continue;
|
|
28563
|
+
action = action === null ? resolved : strongerAction(action, resolved);
|
|
28564
|
+
}
|
|
28565
|
+
if (action === null) return null;
|
|
28566
|
+
return {
|
|
28567
|
+
floor: weakestBuiltinAtLeast(action),
|
|
28568
|
+
locked: hasAuthoredPolicy(policies, rules, byRuleId)
|
|
28569
|
+
};
|
|
28570
|
+
}
|
|
28571
|
+
function policyAssignmentRefusal(policyId, floor) {
|
|
28572
|
+
if (floor.locked) return "lock";
|
|
28573
|
+
const effective = policyId ?? DEFAULT_PACK_POLICY_ID;
|
|
28574
|
+
return isActionAtLeast(builtinPolicyToAction(effective), builtinPolicyToAction(floor.floor)) ? null : "floor";
|
|
28575
|
+
}
|
|
28576
|
+
function packEnablementRefusal(enabled, floor) {
|
|
28577
|
+
if (floor === null || enabled) return null;
|
|
28578
|
+
return "disable";
|
|
28579
|
+
}
|
|
28082
28580
|
|
|
28083
28581
|
// ../../packages/persistence/src/semver.ts
|
|
28084
28582
|
function parse3(version2) {
|
|
@@ -28172,8 +28670,19 @@ function ruleIdsOf(rulesJson) {
|
|
|
28172
28670
|
return ids;
|
|
28173
28671
|
}
|
|
28174
28672
|
var SqliteInstalledPacksRepository = class {
|
|
28175
|
-
|
|
28673
|
+
/**
|
|
28674
|
+
* `baseDir` is the `~/.aka` LAYOUT BASE, not the data dir — the control-plane
|
|
28675
|
+
* floor needs both halves of it (settings/ says whether this machine is
|
|
28676
|
+
* attached, data/ holds the cached bundle). It is optional because a caller
|
|
28677
|
+
* holding only a DatabaseSync — every test construction site, and any embedder
|
|
28678
|
+
* that opens the store itself — has no layout to point at, and such a caller
|
|
28679
|
+
* gets the pre-existing behaviour: no floor, no lock. Production threads it in
|
|
28680
|
+
* from `openLocalDatabase`, which is the single construction site that owns a
|
|
28681
|
+
* real `~/.aka`.
|
|
28682
|
+
*/
|
|
28683
|
+
constructor(db, baseDir) {
|
|
28176
28684
|
this.db = db;
|
|
28685
|
+
this.baseDir = baseDir;
|
|
28177
28686
|
this.insertMissingStmt = db.prepare(
|
|
28178
28687
|
`INSERT INTO installed_packs (id, namespace, pack_id, version, name, rules_json, enabled, created_at, updated_at)
|
|
28179
28688
|
VALUES (:id, :namespace, :packId, :version, :name, :rulesJson, 1, :now, :now)
|
|
@@ -28195,11 +28704,17 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28195
28704
|
this.signatureStmt = db.prepare(
|
|
28196
28705
|
`SELECT namespace, pack_id AS packId, version, rules_json AS rulesJson FROM available_packs`
|
|
28197
28706
|
);
|
|
28707
|
+
this.packRulesStmt = db.prepare(
|
|
28708
|
+
`SELECT rules_json AS rulesJson FROM installed_packs
|
|
28709
|
+
WHERE namespace = ? AND pack_id = ?`
|
|
28710
|
+
);
|
|
28198
28711
|
}
|
|
28199
28712
|
db;
|
|
28713
|
+
baseDir;
|
|
28200
28714
|
insertMissingStmt;
|
|
28201
28715
|
upsertAvailableStmt;
|
|
28202
28716
|
signatureStmt;
|
|
28717
|
+
packRulesStmt;
|
|
28203
28718
|
/**
|
|
28204
28719
|
* Record the running binary's detection inventory. Refreshes the
|
|
28205
28720
|
* available_packs mirror (pruning packs the binary no longer ships) and
|
|
@@ -28241,7 +28756,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28241
28756
|
let behind = false;
|
|
28242
28757
|
for (const row of rows) {
|
|
28243
28758
|
const params = {
|
|
28244
|
-
id:
|
|
28759
|
+
id: randomUUID4(),
|
|
28245
28760
|
namespace: row.namespace,
|
|
28246
28761
|
packId: row.packId,
|
|
28247
28762
|
version: row.version,
|
|
@@ -28253,7 +28768,7 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28253
28768
|
if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
|
|
28254
28769
|
this.upsertAvailableStmt.run({
|
|
28255
28770
|
...params,
|
|
28256
|
-
id:
|
|
28771
|
+
id: randomUUID4(),
|
|
28257
28772
|
recordedBy: meta4?.recordedBy ?? null
|
|
28258
28773
|
});
|
|
28259
28774
|
} else {
|
|
@@ -28499,9 +29014,65 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28499
29014
|
// NOT on the hook path — so, unlike recordInventory, these surface errors to the
|
|
28500
29015
|
// caller rather than swallowing them. Each returns whether a row matched, so the
|
|
28501
29016
|
// caller can tell an edit from a no-such-detection.
|
|
29017
|
+
/**
|
|
29018
|
+
* The rules one installed pack owns, reduced to what a floor computation
|
|
29019
|
+
* reads. Display-tolerant parsing on purpose: a pack whose snapshot is
|
|
29020
|
+
* unreadable contributes no rules to a scan either, so it is not a detection
|
|
29021
|
+
* the control plane can be governing, and an empty list correctly imposes no
|
|
29022
|
+
* floor. Enabled state is deliberately not filtered — a disabled pack is one
|
|
29023
|
+
* the user can re-enable, and its assignment stays governed meanwhile.
|
|
29024
|
+
*/
|
|
29025
|
+
packFloorRules(namespace, packId) {
|
|
29026
|
+
const row = getRow(this.packRulesStmt, [namespace, packId]);
|
|
29027
|
+
if (!row) return [];
|
|
29028
|
+
return parseRules(row.rulesJson).map((rule) => ({ id: rule.id, category: rule.category }));
|
|
29029
|
+
}
|
|
29030
|
+
/**
|
|
29031
|
+
* What the connected control plane imposes on one installed pack, or null on a
|
|
29032
|
+
* machine that is its own authority (standalone, no cached bundle, or a
|
|
29033
|
+
* repository constructed without a layout base).
|
|
29034
|
+
*
|
|
29035
|
+
* Exposed as a READ so a surface can render the constraint — grey out the
|
|
29036
|
+
* choices below the floor, mark a locked detection as locked — rather than
|
|
29037
|
+
* offer the user a picker whose selections it will then be told it may not
|
|
29038
|
+
* make. The refusal in `setPolicy` does not depend on any surface calling this.
|
|
29039
|
+
*/
|
|
29040
|
+
policyFloor(namespace, packId) {
|
|
29041
|
+
if (this.baseDir === void 0) return null;
|
|
29042
|
+
return controlPlanePolicyFloor(this.packFloorRules(namespace, packId), this.baseDir);
|
|
29043
|
+
}
|
|
29044
|
+
/**
|
|
29045
|
+
* The same answer for several packs, keyed `namespace/packId` and carrying an
|
|
29046
|
+
* entry only for a pack the control plane actually governs.
|
|
29047
|
+
*
|
|
29048
|
+
* A surface listing every detection asks per pack, and asking through
|
|
29049
|
+
* `policyFloor` re-reads the settings, re-reads and re-parses the whole cached
|
|
29050
|
+
* bundle and rebuilds its indexes once per pack — the entire cost of one
|
|
29051
|
+
* answer, repeated for each row, on every render. This reads all of that once.
|
|
29052
|
+
* Packs whose rules the snapshot cannot produce simply contribute no entry,
|
|
29053
|
+
* exactly as the single-pack read returns null for them.
|
|
29054
|
+
*/
|
|
29055
|
+
policyFloors(packs2) {
|
|
29056
|
+
const floors = /* @__PURE__ */ new Map();
|
|
29057
|
+
if (this.baseDir === void 0) return floors;
|
|
29058
|
+
const source = openControlPlaneFloors(this.baseDir);
|
|
29059
|
+
if (source === null) return floors;
|
|
29060
|
+
for (const pack of packs2) {
|
|
29061
|
+
const floor = source.floorFor(this.packFloorRules(pack.namespace, pack.packId));
|
|
29062
|
+
if (floor !== null) floors.set(`${pack.namespace}/${pack.packId}`, floor);
|
|
29063
|
+
}
|
|
29064
|
+
return floors;
|
|
29065
|
+
}
|
|
28502
29066
|
/**
|
|
28503
29067
|
* Assign (or clear, with null) the enforcement policy for one installed pack.
|
|
28504
|
-
* `policyId` must be a known built-in id (monitor/warn/redact/block).
|
|
29068
|
+
* `policyId` must be a known built-in id (monitor/warn/redact/block/vault).
|
|
29069
|
+
*
|
|
29070
|
+
* On an ATTACHED machine the organization's bundle is a floor this refuses to
|
|
29071
|
+
* write below, and a detection the organization has authored a policy for is
|
|
29072
|
+
* refused outright — see policy-floor.ts for both, and for why the refusal is
|
|
29073
|
+
* a throw rather than a silently substituted value. This is the one device-local
|
|
29074
|
+
* write path for the assignment, so the check belongs here rather than on any
|
|
29075
|
+
* surface that offers the choice.
|
|
28505
29076
|
*/
|
|
28506
29077
|
setPolicy(namespace, packId, policyId) {
|
|
28507
29078
|
if (policyId !== null && !BuiltinPolicyId.safeParse(policyId).success) {
|
|
@@ -28509,14 +29080,38 @@ var SqliteInstalledPacksRepository = class {
|
|
|
28509
29080
|
`Unknown policy '${policyId}'. Must be one of: ${KNOWN_BUILTIN_IDS.join(", ")}.`
|
|
28510
29081
|
);
|
|
28511
29082
|
}
|
|
29083
|
+
const requested = policyId;
|
|
29084
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29085
|
+
if (floor !== null) {
|
|
29086
|
+
const refusal = policyAssignmentRefusal(requested, floor);
|
|
29087
|
+
if (refusal !== null) {
|
|
29088
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, requested, floor.floor, refusal);
|
|
29089
|
+
}
|
|
29090
|
+
}
|
|
28512
29091
|
const res = this.db.prepare(
|
|
28513
29092
|
`UPDATE installed_packs SET policy_id = :policyId, updated_at = :now
|
|
28514
29093
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
28515
29094
|
).run({ policyId, now: Date.now(), namespace, packId });
|
|
28516
29095
|
return Number(res.changes) > 0;
|
|
28517
29096
|
}
|
|
28518
|
-
/**
|
|
29097
|
+
/**
|
|
29098
|
+
* Enable or disable one installed pack.
|
|
29099
|
+
*
|
|
29100
|
+
* On an ATTACHED machine a detection the organization's bundle governs at all
|
|
29101
|
+
* may not be switched OFF here — see packEnablementRefusal for why that is not
|
|
29102
|
+
* merely another point below the floor, and why re-enabling stays open. Like
|
|
29103
|
+
* the assignment above, the check belongs at this write path rather than on a
|
|
29104
|
+
* surface: this is the one device-local writer of the column, and a refusal
|
|
29105
|
+
* that lived in a page would leave the CLI free.
|
|
29106
|
+
*/
|
|
28519
29107
|
setEnabled(namespace, packId, enabled) {
|
|
29108
|
+
const floor = this.policyFloor(namespace, packId);
|
|
29109
|
+
if (floor !== null) {
|
|
29110
|
+
const refusal = packEnablementRefusal(enabled, floor);
|
|
29111
|
+
if (refusal !== null) {
|
|
29112
|
+
throw new PolicyFloorError(`${namespace}/${packId}`, null, floor.floor, refusal);
|
|
29113
|
+
}
|
|
29114
|
+
}
|
|
28520
29115
|
const res = this.db.prepare(
|
|
28521
29116
|
`UPDATE installed_packs SET enabled = :enabled, updated_at = :now
|
|
28522
29117
|
WHERE namespace = :namespace AND pack_id = :packId`
|
|
@@ -28602,7 +29197,7 @@ var SqliteInventoryRepository = class {
|
|
|
28602
29197
|
};
|
|
28603
29198
|
|
|
28604
29199
|
// ../../packages/persistence/src/repositories/inventory-assets.ts
|
|
28605
|
-
import { randomUUID as
|
|
29200
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
28606
29201
|
var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
28607
29202
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
28608
29203
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
@@ -29091,7 +29686,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29091
29686
|
`INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
|
|
29092
29687
|
VALUES (:id, :projectId, :path, :access, :now, :now)
|
|
29093
29688
|
ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
|
|
29094
|
-
).run({ id:
|
|
29689
|
+
).run({ id: randomUUID5(), projectId, path, access, now: Date.now() });
|
|
29095
29690
|
}
|
|
29096
29691
|
return true;
|
|
29097
29692
|
}
|
|
@@ -29112,7 +29707,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29112
29707
|
`INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
|
|
29113
29708
|
VALUES (:id, :assetId, :trust, :now, :now)
|
|
29114
29709
|
ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
|
|
29115
|
-
).run({ id:
|
|
29710
|
+
).run({ id: randomUUID5(), assetId, trust, now: Date.now() });
|
|
29116
29711
|
}
|
|
29117
29712
|
this.configRowsCache = void 0;
|
|
29118
29713
|
return "ok";
|
|
@@ -29409,7 +30004,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
29409
30004
|
};
|
|
29410
30005
|
|
|
29411
30006
|
// ../../packages/persistence/src/repositories/policies.ts
|
|
29412
|
-
import { randomUUID as
|
|
30007
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
29413
30008
|
var SqlitePoliciesRepository = class {
|
|
29414
30009
|
constructor(db) {
|
|
29415
30010
|
this.db = db;
|
|
@@ -29444,7 +30039,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29444
30039
|
failOpenTransaction(this.db, () => {
|
|
29445
30040
|
for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
|
|
29446
30041
|
stmt.run({
|
|
29447
|
-
id:
|
|
30042
|
+
id: randomUUID6(),
|
|
29448
30043
|
target: JSON.stringify({ category }),
|
|
29449
30044
|
action,
|
|
29450
30045
|
now: Date.now()
|
|
@@ -29464,7 +30059,7 @@ var SqlitePoliciesRepository = class {
|
|
|
29464
30059
|
`INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
|
|
29465
30060
|
VALUES (:id, 'global', :target, :action, 1, :now, :now)
|
|
29466
30061
|
ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
|
|
29467
|
-
).run({ id:
|
|
30062
|
+
).run({ id: randomUUID6(), target: JSON.stringify({ category }), action, now });
|
|
29468
30063
|
}
|
|
29469
30064
|
// Caps every global per-category policy currently set to block/redact down
|
|
29470
30065
|
// to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
|
|
@@ -29532,7 +30127,7 @@ var SqlitePolicyCatalogRepository = class {
|
|
|
29532
30127
|
};
|
|
29533
30128
|
|
|
29534
30129
|
// ../../packages/persistence/src/repositories/project-files.ts
|
|
29535
|
-
import { randomUUID as
|
|
30130
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
29536
30131
|
var SqliteProjectFilesRepository = class {
|
|
29537
30132
|
constructor(db) {
|
|
29538
30133
|
this.db = db;
|
|
@@ -29564,7 +30159,7 @@ var SqliteProjectFilesRepository = class {
|
|
|
29564
30159
|
const stamp = Math.max(now, maxStamp + 1);
|
|
29565
30160
|
for (const file2 of scan2.files) {
|
|
29566
30161
|
this.upsertStmt.run({
|
|
29567
|
-
id:
|
|
30162
|
+
id: randomUUID7(),
|
|
29568
30163
|
projectId,
|
|
29569
30164
|
path: file2.path,
|
|
29570
30165
|
name: file2.name,
|
|
@@ -29578,9 +30173,9 @@ var SqliteProjectFilesRepository = class {
|
|
|
29578
30173
|
};
|
|
29579
30174
|
|
|
29580
30175
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
29581
|
-
import { randomUUID as
|
|
30176
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
29582
30177
|
var SqliteResolutionsRepository = class {
|
|
29583
|
-
constructor(db, now = () => Date.now(), newId = () =>
|
|
30178
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID8()) {
|
|
29584
30179
|
this.db = db;
|
|
29585
30180
|
this.now = now;
|
|
29586
30181
|
this.newId = newId;
|
|
@@ -29793,7 +30388,7 @@ var SqliteScanLedgerRepository = class {
|
|
|
29793
30388
|
};
|
|
29794
30389
|
|
|
29795
30390
|
// ../../packages/persistence/src/repositories/secret-vault.ts
|
|
29796
|
-
import { randomUUID as
|
|
30391
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
29797
30392
|
function pageLimit(requested, fallback) {
|
|
29798
30393
|
if (requested === void 0) return fallback;
|
|
29799
30394
|
return Math.min(Math.max(1, Math.trunc(requested)), MAX_VAULT_PAGE_LIMIT);
|
|
@@ -29839,12 +30434,14 @@ var SELECT_COLUMNS = `
|
|
|
29839
30434
|
ciphertext,
|
|
29840
30435
|
nonce,
|
|
29841
30436
|
auth_tag AS authTag,
|
|
30437
|
+
user_authorized AS userAuthorized,
|
|
29842
30438
|
occurrence_count AS occurrenceCount,
|
|
29843
30439
|
first_seen AS firstSeen,
|
|
29844
30440
|
last_seen AS lastSeen`;
|
|
29845
30441
|
function toRow(raw) {
|
|
29846
|
-
const { provider, ...rest } = raw;
|
|
29847
|
-
|
|
30442
|
+
const { provider, userAuthorized, ...rest } = raw;
|
|
30443
|
+
const row = { ...rest, userAuthorized: userAuthorized !== 0 };
|
|
30444
|
+
return provider === null ? row : { ...row, provider };
|
|
29848
30445
|
}
|
|
29849
30446
|
var SqliteSecretVaultRepository = class {
|
|
29850
30447
|
constructor(db) {
|
|
@@ -29854,17 +30451,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29854
30451
|
pointer_id, value_fingerprint, fingerprint_key_version, key_version,
|
|
29855
30452
|
format_version, category, rule_id, masked_match, provider,
|
|
29856
30453
|
ciphertext, nonce, auth_tag,
|
|
29857
|
-
occurrence_count, first_seen, last_seen
|
|
30454
|
+
user_authorized, occurrence_count, first_seen, last_seen
|
|
29858
30455
|
) VALUES (
|
|
29859
30456
|
:pointerId, :valueFingerprint, :fingerprintKeyVersion, :keyVersion,
|
|
29860
30457
|
:formatVersion, :category, :ruleId, :maskedMatch, :provider,
|
|
29861
30458
|
:ciphertext, :nonce, :authTag,
|
|
29862
|
-
1, :now, :now
|
|
30459
|
+
:userAuthorized, 1, :now, :now
|
|
29863
30460
|
)`
|
|
29864
30461
|
);
|
|
29865
30462
|
this.bumpStmt = db.prepare(
|
|
29866
30463
|
`UPDATE secret_vault
|
|
29867
|
-
SET occurrence_count = occurrence_count + 1, last_seen = :now
|
|
30464
|
+
SET occurrence_count = occurrence_count + 1, last_seen = :now,
|
|
30465
|
+
user_authorized = max(user_authorized, :userAuthorized)
|
|
29868
30466
|
WHERE value_fingerprint = :valueFingerprint`
|
|
29869
30467
|
);
|
|
29870
30468
|
this.byPointerStmt = db.prepare(
|
|
@@ -29884,6 +30482,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29884
30482
|
SET value_fingerprint = :valueFingerprint, fingerprint_key_version = :fingerprintKeyVersion
|
|
29885
30483
|
WHERE pointer_id = :pointerId`
|
|
29886
30484
|
);
|
|
30485
|
+
this.deleteByPointerStmt = db.prepare(`DELETE FROM secret_vault WHERE pointer_id = :pointerId`);
|
|
29887
30486
|
this.derefStmt = db.prepare(
|
|
29888
30487
|
`INSERT INTO secret_vault_deref (id, pointer_id, at, target, reason, outcome, grant_id, pointer_count)
|
|
29889
30488
|
VALUES (:id, :pointerId, :at, :target, :reason, :outcome, :grantId, :pointerCount)`
|
|
@@ -29897,6 +30496,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
29897
30496
|
listStmt;
|
|
29898
30497
|
replaceCiphertextStmt;
|
|
29899
30498
|
refreshFingerprintStmt;
|
|
30499
|
+
deleteByPointerStmt;
|
|
29900
30500
|
derefStmt;
|
|
29901
30501
|
/**
|
|
29902
30502
|
* Vault a value, or record another sighting of one already vaulted. Keyed on
|
|
@@ -29905,6 +30505,11 @@ var SqliteSecretVaultRepository = class {
|
|
|
29905
30505
|
* pointer, category and ciphertext, so the same secret always resolves to one
|
|
29906
30506
|
* wire token. `minted` is true only when this call created the row.
|
|
29907
30507
|
*
|
|
30508
|
+
* `userAuthorized` is the one field a repeat call may still change, and only
|
|
30509
|
+
* upwards: it records that a PERSON asked for this value to be replaced, and
|
|
30510
|
+
* the row is shared with every automatic path that vaults the same value. See
|
|
30511
|
+
* `bumpStmt` for why clearing it is the defect this shape exists to refuse.
|
|
30512
|
+
*
|
|
29908
30513
|
* The read-then-write runs in one IMMEDIATE transaction so two concurrent
|
|
29909
30514
|
* writers cannot both decide they are minting.
|
|
29910
30515
|
*/
|
|
@@ -29931,13 +30536,18 @@ var SqliteSecretVaultRepository = class {
|
|
|
29931
30536
|
ciphertext: input2.ciphertext,
|
|
29932
30537
|
nonce: input2.nonce,
|
|
29933
30538
|
authTag: input2.authTag,
|
|
30539
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
29934
30540
|
now
|
|
29935
30541
|
})
|
|
29936
30542
|
);
|
|
29937
30543
|
minted = true;
|
|
29938
30544
|
return;
|
|
29939
30545
|
}
|
|
29940
|
-
this.bumpStmt.run({
|
|
30546
|
+
this.bumpStmt.run({
|
|
30547
|
+
valueFingerprint: input2.valueFingerprint,
|
|
30548
|
+
userAuthorized: input2.userAuthorized === true ? 1 : 0,
|
|
30549
|
+
now
|
|
30550
|
+
});
|
|
29941
30551
|
},
|
|
29942
30552
|
"IMMEDIATE"
|
|
29943
30553
|
);
|
|
@@ -29997,6 +30607,42 @@ var SqliteSecretVaultRepository = class {
|
|
|
29997
30607
|
);
|
|
29998
30608
|
return destroyed;
|
|
29999
30609
|
}
|
|
30610
|
+
/**
|
|
30611
|
+
* Destroy the named entries and report WHICH ones went — the scoped
|
|
30612
|
+
* counterpart to `purgeAll`, for a caller that has already put those specific
|
|
30613
|
+
* values back where they came from. Ids the store does not hold are absent
|
|
30614
|
+
* from the answer rather than an error, so a set assembled from a stale read
|
|
30615
|
+
* is not a fault. The deref audit is left alone, exactly as the purge leaves
|
|
30616
|
+
* it.
|
|
30617
|
+
*
|
|
30618
|
+
* The ids come back rather than a count because the caller's next act is to
|
|
30619
|
+
* write a purge row per destroyed entry, and a record of destruction has to
|
|
30620
|
+
* be a record of what was really destroyed: a selection is a claim about a
|
|
30621
|
+
* read that has since gone stale, and auditing from it invents a purge for an
|
|
30622
|
+
* entry still sitting in the vault.
|
|
30623
|
+
*
|
|
30624
|
+
* One transaction over the whole set rather than a statement per id: the
|
|
30625
|
+
* caller hands this the result of a restore pass it has completed, and a
|
|
30626
|
+
* fault partway through must leave the vault as it was found rather than
|
|
30627
|
+
* destroying a prefix of it. The vault holds the only copy of what a pointer
|
|
30628
|
+
* stands for, so half a delete is not a state anything can recover from.
|
|
30629
|
+
*/
|
|
30630
|
+
deleteByPointerIds(pointerIds) {
|
|
30631
|
+
if (pointerIds.length === 0) return [];
|
|
30632
|
+
const deleted = [];
|
|
30633
|
+
withTransaction(
|
|
30634
|
+
this.db,
|
|
30635
|
+
() => {
|
|
30636
|
+
for (const pointerId of pointerIds) {
|
|
30637
|
+
if (Number(this.deleteByPointerStmt.run({ pointerId }).changes) > 0) {
|
|
30638
|
+
deleted.push(pointerId);
|
|
30639
|
+
}
|
|
30640
|
+
}
|
|
30641
|
+
},
|
|
30642
|
+
"IMMEDIATE"
|
|
30643
|
+
);
|
|
30644
|
+
return deleted;
|
|
30645
|
+
}
|
|
30000
30646
|
/**
|
|
30001
30647
|
* Record (or re-stamp) one place a pointer has been written. One row per
|
|
30002
30648
|
* (pointer, location); a re-sighting bumps last_seen. Best-effort bookkeeping
|
|
@@ -30009,7 +30655,7 @@ var SqliteSecretVaultRepository = class {
|
|
|
30009
30655
|
VALUES (:id, :pointerId, :location, :kind, :now, :now)
|
|
30010
30656
|
ON CONFLICT (pointer_id, location) DO UPDATE SET last_seen = :now`
|
|
30011
30657
|
).run({
|
|
30012
|
-
id:
|
|
30658
|
+
id: randomUUID9(),
|
|
30013
30659
|
pointerId: entry.pointerId,
|
|
30014
30660
|
location: entry.location,
|
|
30015
30661
|
kind: entry.kind,
|
|
@@ -30522,15 +31168,15 @@ var SqliteSecurityRepository = class {
|
|
|
30522
31168
|
const from = now - RANGE_DAYS[range] * DAY_MS4;
|
|
30523
31169
|
const rows = allRows(
|
|
30524
31170
|
this.db.prepare(
|
|
30525
|
-
`SELECT
|
|
31171
|
+
`SELECT e.repo AS repo, count(*) AS c
|
|
30526
31172
|
FROM inspection_findings f
|
|
30527
31173
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
30528
31174
|
WHERE e.started_at >= :from AND e.started_at < :to
|
|
30529
31175
|
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
30530
|
-
AND
|
|
30531
|
-
AND
|
|
30532
|
-
GROUP BY repo
|
|
30533
|
-
ORDER BY c DESC, repo
|
|
31176
|
+
AND e.repo IS NOT NULL
|
|
31177
|
+
AND e.repo != ''
|
|
31178
|
+
GROUP BY e.repo
|
|
31179
|
+
ORDER BY c DESC, e.repo
|
|
30534
31180
|
LIMIT :limit`
|
|
30535
31181
|
),
|
|
30536
31182
|
{ from, to: now, limit }
|
|
@@ -30592,7 +31238,7 @@ var SqliteSecurityRepository = class {
|
|
|
30592
31238
|
`SELECT f.finding_key AS finding_key,
|
|
30593
31239
|
d.rule_id AS rule_id,
|
|
30594
31240
|
d.severity AS severity,
|
|
30595
|
-
|
|
31241
|
+
e.file_path AS path,
|
|
30596
31242
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
30597
31243
|
latest.resolved_at AS latest_resolved_at
|
|
30598
31244
|
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
@@ -30645,7 +31291,7 @@ var SqliteSecurityRepository = class {
|
|
|
30645
31291
|
};
|
|
30646
31292
|
|
|
30647
31293
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
30648
|
-
import { randomUUID as
|
|
31294
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
30649
31295
|
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
30650
31296
|
var IN_CHUNK = 500;
|
|
30651
31297
|
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
@@ -30733,7 +31379,7 @@ function buildSummary(dest, endpoints) {
|
|
|
30733
31379
|
callSiteCount,
|
|
30734
31380
|
transports: distinctTransports(transports),
|
|
30735
31381
|
dataClasses: distinctDataClasses(dataClasses),
|
|
30736
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31382
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30737
31383
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30738
31384
|
endpoints: endpoints.map(toEndpointSummary)
|
|
30739
31385
|
};
|
|
@@ -30760,7 +31406,7 @@ function buildDetail(dest, endpoints, callSites) {
|
|
|
30760
31406
|
lastSeen: new Date(lastSeenMs).toISOString(),
|
|
30761
31407
|
transports: distinctTransports(transports),
|
|
30762
31408
|
dataClasses: distinctDataClasses(endpoints.map((e) => e.dataClass)),
|
|
30763
|
-
review: buildReviewInfo(dest.trust, transports),
|
|
31409
|
+
review: buildReviewInfo(dest.trust, transports, dest.overrideDecision !== null),
|
|
30764
31410
|
network: dest.kind === "provider" ? null : parseNetwork(dest.networkJson),
|
|
30765
31411
|
note: dest.note,
|
|
30766
31412
|
endpoints: endpoints.map((ep) => ({
|
|
@@ -30789,7 +31435,11 @@ var SqliteSharesRepository = class {
|
|
|
30789
31435
|
FROM share_destination d
|
|
30790
31436
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
30791
31437
|
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
30792
|
-
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL
|
|
31438
|
+
WHERE (d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL)
|
|
31439
|
+
AND NOT EXISTS (
|
|
31440
|
+
SELECT 1 FROM egress_decision_override o
|
|
31441
|
+
WHERE o.host = d.host OR (o.destination_id = d.id AND o.host IS NULL)
|
|
31442
|
+
)`
|
|
30793
31443
|
);
|
|
30794
31444
|
const kindCounts = countBy(
|
|
30795
31445
|
this.db,
|
|
@@ -30901,7 +31551,7 @@ var SqliteSharesRepository = class {
|
|
|
30901
31551
|
(id, destination_id, host, decision, created_at, updated_at)
|
|
30902
31552
|
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
30903
31553
|
).run({
|
|
30904
|
-
id:
|
|
31554
|
+
id: randomUUID10(),
|
|
30905
31555
|
destinationId,
|
|
30906
31556
|
host: dest.host,
|
|
30907
31557
|
decision,
|
|
@@ -31050,7 +31700,7 @@ var SqliteSharesRepository = class {
|
|
|
31050
31700
|
let destinationId = destIds.get(hit.host);
|
|
31051
31701
|
if (destinationId === void 0) {
|
|
31052
31702
|
destStmt.run({
|
|
31053
|
-
id:
|
|
31703
|
+
id: randomUUID10(),
|
|
31054
31704
|
kind: hit.kind,
|
|
31055
31705
|
name: hit.name,
|
|
31056
31706
|
host: hit.host,
|
|
@@ -31066,7 +31716,7 @@ var SqliteSharesRepository = class {
|
|
|
31066
31716
|
let endpointId = endpointIds.get(endpointKey);
|
|
31067
31717
|
if (endpointId === void 0) {
|
|
31068
31718
|
endpointStmt.run({
|
|
31069
|
-
id:
|
|
31719
|
+
id: randomUUID10(),
|
|
31070
31720
|
destinationId,
|
|
31071
31721
|
method: hit.method,
|
|
31072
31722
|
transport: hit.transport,
|
|
@@ -31079,7 +31729,7 @@ var SqliteSharesRepository = class {
|
|
|
31079
31729
|
endpointIds.set(endpointKey, endpointId);
|
|
31080
31730
|
}
|
|
31081
31731
|
siteStmt.run({
|
|
31082
|
-
id:
|
|
31732
|
+
id: randomUUID10(),
|
|
31083
31733
|
endpointId,
|
|
31084
31734
|
project: input2.project,
|
|
31085
31735
|
projectKey: input2.projectKey,
|
|
@@ -31444,6 +32094,7 @@ function purgeSampleData(db) {
|
|
|
31444
32094
|
}
|
|
31445
32095
|
|
|
31446
32096
|
// ../../packages/persistence/src/database.ts
|
|
32097
|
+
var CAPTURE_GRAIN = new Set(EventKind.options);
|
|
31447
32098
|
var UNSAFE_TEST_ONLY_RAW_HANDLE = /* @__PURE__ */ Symbol(
|
|
31448
32099
|
"aka.persistence.unsafeTestOnlyRawHandle"
|
|
31449
32100
|
);
|
|
@@ -31491,7 +32142,7 @@ function backupLegacyStore(db, file2) {
|
|
|
31491
32142
|
discardStore(file2, backup);
|
|
31492
32143
|
return backup;
|
|
31493
32144
|
}
|
|
31494
|
-
function openAndInitialize(file2) {
|
|
32145
|
+
function openAndInitialize(file2, base) {
|
|
31495
32146
|
let db = openWithPragmas(file2);
|
|
31496
32147
|
try {
|
|
31497
32148
|
if (isForeignSqliteLineage(db)) {
|
|
@@ -31504,7 +32155,7 @@ function openAndInitialize(file2) {
|
|
|
31504
32155
|
applyMigrations(db, file2);
|
|
31505
32156
|
tightenPerms(file2);
|
|
31506
32157
|
const policies = new SqlitePoliciesRepository(db);
|
|
31507
|
-
const installedPacks = new SqliteInstalledPacksRepository(db);
|
|
32158
|
+
const installedPacks = new SqliteInstalledPacksRepository(db, base);
|
|
31508
32159
|
const repositories = {
|
|
31509
32160
|
events: new SqliteEventsRepository(db),
|
|
31510
32161
|
findings: new SqliteFindingsRepository(db),
|
|
@@ -31540,7 +32191,7 @@ function openAndInitialize(file2) {
|
|
|
31540
32191
|
}
|
|
31541
32192
|
function openLocalDatabase(dir) {
|
|
31542
32193
|
ensureDataDirSync(dir);
|
|
31543
|
-
const file2 =
|
|
32194
|
+
const file2 = join7(dir, DB_FILENAME);
|
|
31544
32195
|
reapStalePartials(file2);
|
|
31545
32196
|
const {
|
|
31546
32197
|
db,
|
|
@@ -31568,7 +32219,13 @@ function openLocalDatabase(dir) {
|
|
|
31568
32219
|
inspectionDefinitions,
|
|
31569
32220
|
inspectionFindings,
|
|
31570
32221
|
configInventory
|
|
31571
|
-
} = openAndInitialize(
|
|
32222
|
+
} = openAndInitialize(
|
|
32223
|
+
file2,
|
|
32224
|
+
// `dir` is always `<base>/data` — every caller resolves it through
|
|
32225
|
+
// `dataDir()` — so its parent is the `~/.aka` base the layout splits into
|
|
32226
|
+
// settings/ and data/, and the pack-policy floor needs both halves.
|
|
32227
|
+
dirname2(dir)
|
|
32228
|
+
);
|
|
31572
32229
|
function captureRowId(event) {
|
|
31573
32230
|
return captureId(
|
|
31574
32231
|
event.metadata?.sessionId ?? null,
|
|
@@ -31581,6 +32238,21 @@ function openLocalDatabase(dir) {
|
|
|
31581
32238
|
historySync.markSynced([captureRowId(event)], atMs);
|
|
31582
32239
|
});
|
|
31583
32240
|
}
|
|
32241
|
+
function markCaptureOwed(event) {
|
|
32242
|
+
failOpenTransaction(db, () => {
|
|
32243
|
+
historySync.markCaptureOwed(captureRowId(event));
|
|
32244
|
+
});
|
|
32245
|
+
}
|
|
32246
|
+
function markAuditEventsDelivered(events2, atMs) {
|
|
32247
|
+
const stampable = events2.filter((event) => !CAPTURE_GRAIN.has(event.eventType));
|
|
32248
|
+
if (stampable.length === 0) return;
|
|
32249
|
+
failOpenTransaction(db, () => {
|
|
32250
|
+
historySync.markSynced(
|
|
32251
|
+
stampable.map((event) => event.id),
|
|
32252
|
+
atMs
|
|
32253
|
+
);
|
|
32254
|
+
});
|
|
32255
|
+
}
|
|
31584
32256
|
function recordCapture(event, detected) {
|
|
31585
32257
|
failOpenTransaction(db, () => {
|
|
31586
32258
|
const sessionId = event.metadata?.sessionId;
|
|
@@ -31667,7 +32339,7 @@ function openLocalDatabase(dir) {
|
|
|
31667
32339
|
const definitionId = definitionIds.get(`${finding2.ruleId}@${finding2.version}`);
|
|
31668
32340
|
if (!definitionId) continue;
|
|
31669
32341
|
inspectionFindings.insertFinding({
|
|
31670
|
-
id:
|
|
32342
|
+
id: randomUUID11(),
|
|
31671
32343
|
auditEventId: record2.scanEvent.id,
|
|
31672
32344
|
inspectionDefinitionId: definitionId,
|
|
31673
32345
|
span: finding2.span,
|
|
@@ -31763,6 +32435,8 @@ function openLocalDatabase(dir) {
|
|
|
31763
32435
|
inspectionFindings,
|
|
31764
32436
|
recordCapture,
|
|
31765
32437
|
markCaptureDelivered,
|
|
32438
|
+
markCaptureOwed,
|
|
32439
|
+
markAuditEventsDelivered,
|
|
31766
32440
|
ensureInventory,
|
|
31767
32441
|
recordConfigScan,
|
|
31768
32442
|
recordProjectFiles,
|
|
@@ -31781,32 +32455,18 @@ function openLocalDatabase(dir) {
|
|
|
31781
32455
|
};
|
|
31782
32456
|
}
|
|
31783
32457
|
|
|
31784
|
-
// ../../packages/persistence/src/file-lock.ts
|
|
31785
|
-
import { randomUUID as randomUUID11 } from "crypto";
|
|
31786
|
-
import {
|
|
31787
|
-
closeSync,
|
|
31788
|
-
existsSync as existsSync2,
|
|
31789
|
-
openSync,
|
|
31790
|
-
readFileSync as readFileSync2,
|
|
31791
|
-
rmSync as rmSync5,
|
|
31792
|
-
statSync as statSync3,
|
|
31793
|
-
writeFileSync as writeFileSync2
|
|
31794
|
-
} from "fs";
|
|
31795
|
-
import { hostname as hostname3 } from "os";
|
|
31796
|
-
var PARK = new Int32Array(new SharedArrayBuffer(4));
|
|
31797
|
-
|
|
31798
32458
|
// ../../packages/persistence/src/finding-key.ts
|
|
31799
32459
|
import { createHash as createHash3 } from "crypto";
|
|
31800
32460
|
|
|
31801
32461
|
// ../../packages/persistence/src/fingerprint.ts
|
|
31802
32462
|
import { createHmac, randomBytes } from "crypto";
|
|
31803
|
-
import { existsSync as existsSync3, readFileSync as
|
|
31804
|
-
import { join as
|
|
32463
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
32464
|
+
import { join as join8 } from "path";
|
|
31805
32465
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
31806
32466
|
var EXCEPTION_KEY_FILENAME = "exception.key";
|
|
31807
32467
|
var KEY_MATERIAL_BYTES = 32;
|
|
31808
32468
|
function keyFilePath(dataDir2) {
|
|
31809
|
-
return
|
|
32469
|
+
return join8(dataDir2, EXCEPTION_KEY_FILENAME);
|
|
31810
32470
|
}
|
|
31811
32471
|
function parseKeyFile(raw) {
|
|
31812
32472
|
const parsed2 = JSON.parse(raw);
|
|
@@ -31829,7 +32489,7 @@ function parseKeyFile(raw) {
|
|
|
31829
32489
|
function readFingerprintKey(dataDir2) {
|
|
31830
32490
|
let raw;
|
|
31831
32491
|
try {
|
|
31832
|
-
raw =
|
|
32492
|
+
raw = readFileSync6(keyFilePath(dataDir2), "utf8");
|
|
31833
32493
|
} catch (err) {
|
|
31834
32494
|
if (err.code === "ENOENT") return null;
|
|
31835
32495
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -31839,146 +32499,12 @@ function readFingerprintKey(dataDir2) {
|
|
|
31839
32499
|
|
|
31840
32500
|
// ../../packages/persistence/src/history-preview.ts
|
|
31841
32501
|
import { existsSync as existsSync4 } from "fs";
|
|
31842
|
-
import { join as
|
|
32502
|
+
import { join as join9 } from "path";
|
|
31843
32503
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
31844
32504
|
|
|
31845
|
-
// ../../packages/persistence/src/local-layout.ts
|
|
31846
|
-
import { renameSync as renameSync3 } from "fs";
|
|
31847
|
-
import { mkdir } from "fs/promises";
|
|
31848
|
-
import { homedir } from "os";
|
|
31849
|
-
import { join as join7 } from "path";
|
|
31850
|
-
function defaultDataDir() {
|
|
31851
|
-
return join7(homedir(), ".aka");
|
|
31852
|
-
}
|
|
31853
|
-
function settingsDir(base = defaultDataDir()) {
|
|
31854
|
-
return join7(base, "settings");
|
|
31855
|
-
}
|
|
31856
|
-
function dataDir(base = defaultDataDir()) {
|
|
31857
|
-
return join7(base, "data");
|
|
31858
|
-
}
|
|
31859
|
-
function dbPath(base = defaultDataDir()) {
|
|
31860
|
-
return join7(dataDir(base), "aka.db");
|
|
31861
|
-
}
|
|
31862
|
-
function keysDir(base = defaultDataDir()) {
|
|
31863
|
-
return join7(base, "keys");
|
|
31864
|
-
}
|
|
31865
|
-
async function ensureDataDir(dir = defaultDataDir()) {
|
|
31866
|
-
await mkdir(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
31867
|
-
tightenDir(dir);
|
|
31868
|
-
}
|
|
31869
|
-
function ensureLayoutDirSync(dir = defaultDataDir()) {
|
|
31870
|
-
ensureDataDirSync(dir);
|
|
31871
|
-
}
|
|
31872
|
-
function migrateLegacyLayout(base = defaultDataDir()) {
|
|
31873
|
-
const moves = [
|
|
31874
|
-
{ name: "config.json", dest: settingsDir(base) },
|
|
31875
|
-
{ name: "policy-cache.json", dest: dataDir(base) }
|
|
31876
|
-
];
|
|
31877
|
-
for (const { name, dest } of moves) {
|
|
31878
|
-
try {
|
|
31879
|
-
ensureDataDirSync(dest);
|
|
31880
|
-
const moved = join7(dest, name);
|
|
31881
|
-
renameSync3(join7(base, name), moved);
|
|
31882
|
-
tightenFile(moved);
|
|
31883
|
-
} catch {
|
|
31884
|
-
}
|
|
31885
|
-
}
|
|
31886
|
-
}
|
|
31887
|
-
|
|
31888
|
-
// ../../packages/persistence/src/managed-settings.ts
|
|
31889
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
31890
|
-
import { posix, win32 } from "path";
|
|
31891
|
-
function managedSettingsPaths(platform2 = process.platform) {
|
|
31892
|
-
if (platform2 === "darwin") {
|
|
31893
|
-
return [
|
|
31894
|
-
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
31895
|
-
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
31896
|
-
];
|
|
31897
|
-
}
|
|
31898
|
-
if (platform2 === "win32") {
|
|
31899
|
-
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
31900
|
-
}
|
|
31901
|
-
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
31902
|
-
}
|
|
31903
|
-
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
31904
|
-
for (const path of paths) {
|
|
31905
|
-
let text;
|
|
31906
|
-
try {
|
|
31907
|
-
text = readFileSync4(path, "utf8");
|
|
31908
|
-
} catch {
|
|
31909
|
-
continue;
|
|
31910
|
-
}
|
|
31911
|
-
const record2 = parseJsonObject(text);
|
|
31912
|
-
if (!record2) continue;
|
|
31913
|
-
const parsed2 = ManagedSettings.safeParse(record2);
|
|
31914
|
-
if (parsed2.success) return parsed2.data;
|
|
31915
|
-
}
|
|
31916
|
-
return null;
|
|
31917
|
-
}
|
|
31918
|
-
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
31919
|
-
if (!managed) return settings;
|
|
31920
|
-
const { values } = managed;
|
|
31921
|
-
const merged = { ...settings };
|
|
31922
|
-
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
31923
|
-
if (values.controlPlane !== void 0) {
|
|
31924
|
-
merged.controlPlane = {
|
|
31925
|
-
...values.controlPlane,
|
|
31926
|
-
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
31927
|
-
// joined it. Keep the user's own attach time when the endpoint is
|
|
31928
|
-
// unchanged, so a managed machine does not appear to re-attach on every
|
|
31929
|
-
// read; stamp a fresh one when the administrator moved it.
|
|
31930
|
-
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
31931
|
-
};
|
|
31932
|
-
}
|
|
31933
|
-
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
31934
|
-
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
31935
|
-
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
31936
|
-
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
31937
|
-
if (values.vaultConsent !== void 0) {
|
|
31938
|
-
merged.vaultConsent = values.vaultConsent ? (
|
|
31939
|
-
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
31940
|
-
// at the current version otherwise.
|
|
31941
|
-
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
31942
|
-
) : void 0;
|
|
31943
|
-
}
|
|
31944
|
-
if (values.modelJudgeConsent !== void 0) {
|
|
31945
|
-
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
31946
|
-
acknowledgedAt: now().toISOString(),
|
|
31947
|
-
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
31948
|
-
} : void 0;
|
|
31949
|
-
}
|
|
31950
|
-
return merged;
|
|
31951
|
-
}
|
|
31952
|
-
|
|
31953
|
-
// ../../packages/persistence/src/settings.ts
|
|
31954
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
31955
|
-
import { join as join8 } from "path";
|
|
31956
|
-
var SETTINGS_FILENAME = "settings.json";
|
|
31957
|
-
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
31958
|
-
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
31959
|
-
}
|
|
31960
|
-
function readUserSettings(base) {
|
|
31961
|
-
const record2 = readJson(join8(settingsDir(base), SETTINGS_FILENAME));
|
|
31962
|
-
if (!record2) return defaultWorkspaceSettings();
|
|
31963
|
-
try {
|
|
31964
|
-
return WorkspaceSettings.parse(record2);
|
|
31965
|
-
} catch {
|
|
31966
|
-
return defaultWorkspaceSettings();
|
|
31967
|
-
}
|
|
31968
|
-
}
|
|
31969
|
-
function readJson(file2) {
|
|
31970
|
-
let text;
|
|
31971
|
-
try {
|
|
31972
|
-
text = readFileSync5(file2, "utf8");
|
|
31973
|
-
} catch {
|
|
31974
|
-
return null;
|
|
31975
|
-
}
|
|
31976
|
-
return parseJsonObject(text) ?? null;
|
|
31977
|
-
}
|
|
31978
|
-
|
|
31979
32505
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
31980
32506
|
import { existsSync as existsSync5, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
31981
|
-
import { dirname as
|
|
32507
|
+
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
31982
32508
|
var STORE_DB = "the store database (including the prompt corpus)";
|
|
31983
32509
|
var STORE_SETTINGS = "your settings file";
|
|
31984
32510
|
function storeContents(home) {
|
|
@@ -31987,7 +32513,7 @@ function storeContents(home) {
|
|
|
31987
32513
|
[settingsDir(home), STORE_SETTINGS],
|
|
31988
32514
|
[dataDir(home), STORE_DB],
|
|
31989
32515
|
[keysDir(home), "the vault key"],
|
|
31990
|
-
[
|
|
32516
|
+
[join10(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
31991
32517
|
[dbPath(home), STORE_DB]
|
|
31992
32518
|
]);
|
|
31993
32519
|
}
|
|
@@ -32015,7 +32541,7 @@ function linkTarget(path) {
|
|
|
32015
32541
|
try {
|
|
32016
32542
|
return realpathSync(path);
|
|
32017
32543
|
} catch {
|
|
32018
|
-
return resolve(
|
|
32544
|
+
return resolve(dirname3(path), readlinkSync(path));
|
|
32019
32545
|
}
|
|
32020
32546
|
}
|
|
32021
32547
|
function targetMode(path, platform2) {
|
|
@@ -32039,19 +32565,19 @@ import {
|
|
|
32039
32565
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
32040
32566
|
import { execFileSync } from "child_process";
|
|
32041
32567
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32042
|
-
import { chmodSync as chmodSync3, readFileSync as
|
|
32043
|
-
import { join as
|
|
32568
|
+
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32569
|
+
import { join as join11 } from "path";
|
|
32044
32570
|
|
|
32045
32571
|
// ../../packages/persistence/src/vault/vault.ts
|
|
32046
32572
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32047
32573
|
|
|
32048
32574
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32049
32575
|
import { existsSync as existsSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
32050
|
-
import { join as
|
|
32576
|
+
import { join as join12 } from "path";
|
|
32051
32577
|
var MARKER = "warn-era-capped";
|
|
32052
32578
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32053
32579
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32054
|
-
const marker =
|
|
32580
|
+
const marker = join12(dataDir2, MARKER);
|
|
32055
32581
|
if (existsSync6(marker)) return { capped: 0, skipped: "already-run" };
|
|
32056
32582
|
const capped = db.policies.capCategoryActions();
|
|
32057
32583
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
@@ -32095,6 +32621,307 @@ function toEgressIngestRequest(input2) {
|
|
|
32095
32621
|
};
|
|
32096
32622
|
}
|
|
32097
32623
|
|
|
32624
|
+
// ../../packages/remote/src/http.ts
|
|
32625
|
+
import { request as httpRequest } from "http";
|
|
32626
|
+
import { request as httpsRequest } from "https";
|
|
32627
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
32628
|
+
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
32629
|
+
var RemoteRequestError = class extends Error {
|
|
32630
|
+
constructor(status) {
|
|
32631
|
+
super(`control-plane request failed with status ${String(status)}`);
|
|
32632
|
+
this.status = status;
|
|
32633
|
+
this.name = "RemoteRequestError";
|
|
32634
|
+
}
|
|
32635
|
+
status;
|
|
32636
|
+
};
|
|
32637
|
+
var RemoteRouteAbsent = class extends Error {
|
|
32638
|
+
constructor(route) {
|
|
32639
|
+
super(`control plane does not serve ${route}`);
|
|
32640
|
+
this.route = route;
|
|
32641
|
+
this.name = "RemoteRouteAbsent";
|
|
32642
|
+
}
|
|
32643
|
+
route;
|
|
32644
|
+
};
|
|
32645
|
+
var RemoteRequestInvalid = class extends Error {
|
|
32646
|
+
constructor(route, cause) {
|
|
32647
|
+
super(`refusing to send a malformed body to ${route}`);
|
|
32648
|
+
this.cause = cause;
|
|
32649
|
+
this.name = "RemoteRequestInvalid";
|
|
32650
|
+
}
|
|
32651
|
+
cause;
|
|
32652
|
+
};
|
|
32653
|
+
var RemoteResponseInvalid = class extends Error {
|
|
32654
|
+
constructor(route, detail) {
|
|
32655
|
+
super(`control plane answered ${route} with ${detail}`);
|
|
32656
|
+
this.name = "RemoteResponseInvalid";
|
|
32657
|
+
}
|
|
32658
|
+
};
|
|
32659
|
+
var RemoteTransportError = class extends Error {
|
|
32660
|
+
/**
|
|
32661
|
+
* The status the peer sent, when headers arrived and only the BODY was
|
|
32662
|
+
* refused.
|
|
32663
|
+
*
|
|
32664
|
+
* Undefined for the ordinary case this class was written for — no answer at
|
|
32665
|
+
* all. It exists because two paths reject after a status has already been
|
|
32666
|
+
* delivered: an oversized body and an aborted response. Discarding it there
|
|
32667
|
+
* reported a deployment answering 401 with a verbose body as a network
|
|
32668
|
+
* outage, which sends the reader to look at their network instead of their
|
|
32669
|
+
* credential.
|
|
32670
|
+
*/
|
|
32671
|
+
constructor(reason, status) {
|
|
32672
|
+
super(`control-plane request did not complete: ${reason}`);
|
|
32673
|
+
this.status = status;
|
|
32674
|
+
this.name = "RemoteTransportError";
|
|
32675
|
+
}
|
|
32676
|
+
status;
|
|
32677
|
+
};
|
|
32678
|
+
async function send(options) {
|
|
32679
|
+
const url2 = new URL(options.url);
|
|
32680
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
32681
|
+
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
32682
|
+
const requestOptions = {
|
|
32683
|
+
method: options.method,
|
|
32684
|
+
headers: {
|
|
32685
|
+
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
32686
|
+
// last they win, and two of the values below are ones no caller may
|
|
32687
|
+
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
32688
|
+
// byte count that stops a multi-byte body being truncated by the
|
|
32689
|
+
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
32690
|
+
// function, so "no caller does that today" is not the guarantee to rely
|
|
32691
|
+
// on. The one header any caller actually passes — `if-none-match` on the
|
|
32692
|
+
// conditional GET — is untouched by this order.
|
|
32693
|
+
...options.headers,
|
|
32694
|
+
// The credential. One header, matching what the deployment authenticates
|
|
32695
|
+
// on; a second copy in an `Authorization` header would be one more place
|
|
32696
|
+
// it can be logged by an intermediary for no gain.
|
|
32697
|
+
//
|
|
32698
|
+
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
32699
|
+
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
32700
|
+
// key differently from an absent one, and "the header is not there" is
|
|
32701
|
+
// the property the attach flow needs.
|
|
32702
|
+
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
32703
|
+
accept: "application/json",
|
|
32704
|
+
...options.body === void 0 ? {} : {
|
|
32705
|
+
"content-type": "application/json",
|
|
32706
|
+
// Byte length, not string length: a multi-byte body sent with a
|
|
32707
|
+
// character count is truncated by the receiver.
|
|
32708
|
+
"content-length": String(Buffer.byteLength(options.body))
|
|
32709
|
+
}
|
|
32710
|
+
}
|
|
32711
|
+
};
|
|
32712
|
+
return new Promise((resolve2, reject) => {
|
|
32713
|
+
let settled = false;
|
|
32714
|
+
const fail = (reason, status) => {
|
|
32715
|
+
if (settled) return;
|
|
32716
|
+
settled = true;
|
|
32717
|
+
reject(new RemoteTransportError(reason, status));
|
|
32718
|
+
};
|
|
32719
|
+
const req = send_(url2, requestOptions, (res) => {
|
|
32720
|
+
const chunks = [];
|
|
32721
|
+
let size = 0;
|
|
32722
|
+
res.on("data", (chunk) => {
|
|
32723
|
+
size += chunk.length;
|
|
32724
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
32725
|
+
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
32726
|
+
res.destroy();
|
|
32727
|
+
req.destroy();
|
|
32728
|
+
return;
|
|
32729
|
+
}
|
|
32730
|
+
chunks.push(chunk);
|
|
32731
|
+
});
|
|
32732
|
+
res.on("aborted", () => {
|
|
32733
|
+
fail("the response was aborted", res.statusCode);
|
|
32734
|
+
});
|
|
32735
|
+
res.on("end", () => {
|
|
32736
|
+
if (settled) return;
|
|
32737
|
+
settled = true;
|
|
32738
|
+
resolve2({
|
|
32739
|
+
status: res.statusCode ?? 0,
|
|
32740
|
+
headers: res.headers,
|
|
32741
|
+
body: Buffer.concat(chunks).toString("utf8")
|
|
32742
|
+
});
|
|
32743
|
+
});
|
|
32744
|
+
});
|
|
32745
|
+
const deadline = setTimeout(() => {
|
|
32746
|
+
fail(`no response within ${String(timeoutMs)}ms`);
|
|
32747
|
+
req.destroy();
|
|
32748
|
+
}, timeoutMs);
|
|
32749
|
+
deadline.unref();
|
|
32750
|
+
req.on("upgrade", (_res, socket) => {
|
|
32751
|
+
fail("the deployment answered with a protocol upgrade");
|
|
32752
|
+
socket.destroy();
|
|
32753
|
+
});
|
|
32754
|
+
req.on("close", () => {
|
|
32755
|
+
fail("the connection closed before a response was read");
|
|
32756
|
+
clearTimeout(deadline);
|
|
32757
|
+
});
|
|
32758
|
+
req.on("error", (err) => {
|
|
32759
|
+
fail(err.message);
|
|
32760
|
+
});
|
|
32761
|
+
if (options.body !== void 0) req.write(options.body);
|
|
32762
|
+
req.end();
|
|
32763
|
+
});
|
|
32764
|
+
}
|
|
32765
|
+
|
|
32766
|
+
// ../../packages/remote/src/client.ts
|
|
32767
|
+
var ROUTES = {
|
|
32768
|
+
events: "/v1/events",
|
|
32769
|
+
auditEvents: "/v1/audit-events",
|
|
32770
|
+
auditEventsBatch: "/v1/audit-events/batch",
|
|
32771
|
+
inventory: "/v1/inventory",
|
|
32772
|
+
storePosture: "/v1/store-posture",
|
|
32773
|
+
policyBundle: "/v1/policy-bundle",
|
|
32774
|
+
whoami: "/v1/plugin/whoami",
|
|
32775
|
+
shares: "/v1/shares",
|
|
32776
|
+
commands: "/v1/plugin/commands"
|
|
32777
|
+
};
|
|
32778
|
+
function ackRoute(id) {
|
|
32779
|
+
return `${ROUTES.commands}/${encodeURIComponent(id)}/ack`;
|
|
32780
|
+
}
|
|
32781
|
+
function headerValue(response, name) {
|
|
32782
|
+
const raw = response.headers[name];
|
|
32783
|
+
if (raw === void 0) return void 0;
|
|
32784
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
32785
|
+
}
|
|
32786
|
+
function okBody(response) {
|
|
32787
|
+
if (response.status < 200 || response.status >= 300) {
|
|
32788
|
+
throw new RemoteRequestError(response.status);
|
|
32789
|
+
}
|
|
32790
|
+
return response.body;
|
|
32791
|
+
}
|
|
32792
|
+
function parsed(schema, body, route) {
|
|
32793
|
+
let json2;
|
|
32794
|
+
try {
|
|
32795
|
+
json2 = JSON.parse(body);
|
|
32796
|
+
} catch {
|
|
32797
|
+
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
32798
|
+
}
|
|
32799
|
+
const result = schema.safeParse(json2);
|
|
32800
|
+
if (!result.success) {
|
|
32801
|
+
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
32802
|
+
}
|
|
32803
|
+
return result.data;
|
|
32804
|
+
}
|
|
32805
|
+
function withoutTrailingSlashes(endpoint) {
|
|
32806
|
+
let end = endpoint.length;
|
|
32807
|
+
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
32808
|
+
return endpoint.slice(0, end);
|
|
32809
|
+
}
|
|
32810
|
+
var SLASH = "/".charCodeAt(0);
|
|
32811
|
+
function createRemoteClient(options) {
|
|
32812
|
+
const base = withoutTrailingSlashes(options.endpoint);
|
|
32813
|
+
const url2 = (route) => `${base}${route}`;
|
|
32814
|
+
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
32815
|
+
const sendOne = async (event) => {
|
|
32816
|
+
const validated = RecordAuditEventRequest.safeParse(event);
|
|
32817
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
32818
|
+
const response = await send({
|
|
32819
|
+
...common,
|
|
32820
|
+
method: "POST",
|
|
32821
|
+
url: url2(ROUTES.auditEvents),
|
|
32822
|
+
body: JSON.stringify(validated.data)
|
|
32823
|
+
});
|
|
32824
|
+
okBody(response);
|
|
32825
|
+
};
|
|
32826
|
+
return {
|
|
32827
|
+
async ingestEvents(batch) {
|
|
32828
|
+
const response = await send({
|
|
32829
|
+
...common,
|
|
32830
|
+
method: "POST",
|
|
32831
|
+
url: url2(ROUTES.events),
|
|
32832
|
+
body: JSON.stringify(batch)
|
|
32833
|
+
});
|
|
32834
|
+
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
32835
|
+
},
|
|
32836
|
+
async ingestInventory(context) {
|
|
32837
|
+
const response = await send({
|
|
32838
|
+
...common,
|
|
32839
|
+
method: "POST",
|
|
32840
|
+
url: url2(ROUTES.inventory),
|
|
32841
|
+
body: JSON.stringify(context)
|
|
32842
|
+
});
|
|
32843
|
+
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
32844
|
+
},
|
|
32845
|
+
async recordAuditEvent(event) {
|
|
32846
|
+
await sendOne(event);
|
|
32847
|
+
},
|
|
32848
|
+
async recordAuditEvents(events, opts) {
|
|
32849
|
+
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
32850
|
+
if (!validated.success) {
|
|
32851
|
+
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
32852
|
+
}
|
|
32853
|
+
const response = await send({
|
|
32854
|
+
...common,
|
|
32855
|
+
method: "POST",
|
|
32856
|
+
url: url2(ROUTES.auditEventsBatch),
|
|
32857
|
+
body: JSON.stringify(validated.data)
|
|
32858
|
+
});
|
|
32859
|
+
if (response.status === 404) {
|
|
32860
|
+
if (opts?.fallbackToSingleEvents !== true) {
|
|
32861
|
+
throw new RemoteRouteAbsent(ROUTES.auditEventsBatch);
|
|
32862
|
+
}
|
|
32863
|
+
for (const event of validated.data.events) await sendOne(event);
|
|
32864
|
+
return { accepted: validated.data.events.length };
|
|
32865
|
+
}
|
|
32866
|
+
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
32867
|
+
},
|
|
32868
|
+
async reportStorePosture(snapshot) {
|
|
32869
|
+
const response = await send({
|
|
32870
|
+
...common,
|
|
32871
|
+
method: "POST",
|
|
32872
|
+
url: url2(ROUTES.storePosture),
|
|
32873
|
+
body: JSON.stringify(snapshot)
|
|
32874
|
+
});
|
|
32875
|
+
okBody(response);
|
|
32876
|
+
},
|
|
32877
|
+
async getPolicyBundle(etag) {
|
|
32878
|
+
const response = await send({
|
|
32879
|
+
...common,
|
|
32880
|
+
method: "GET",
|
|
32881
|
+
url: url2(ROUTES.policyBundle),
|
|
32882
|
+
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
32883
|
+
});
|
|
32884
|
+
if (response.status === 304) {
|
|
32885
|
+
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
32886
|
+
}
|
|
32887
|
+
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
32888
|
+
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
32889
|
+
},
|
|
32890
|
+
async whoami() {
|
|
32891
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
32892
|
+
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
32893
|
+
},
|
|
32894
|
+
async recordProjectEgress(request) {
|
|
32895
|
+
const validated = EgressIngestRequest.safeParse(request);
|
|
32896
|
+
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
32897
|
+
const response = await send({
|
|
32898
|
+
...common,
|
|
32899
|
+
method: "POST",
|
|
32900
|
+
url: url2(ROUTES.shares),
|
|
32901
|
+
body: JSON.stringify(validated.data)
|
|
32902
|
+
});
|
|
32903
|
+
okBody(response);
|
|
32904
|
+
},
|
|
32905
|
+
async pollCommand() {
|
|
32906
|
+
const response = await send({ ...common, method: "GET", url: url2(ROUTES.commands) });
|
|
32907
|
+
if (response.status === 404) return null;
|
|
32908
|
+
return parsed(DeviceCommandPollResponse, okBody(response), ROUTES.commands).command;
|
|
32909
|
+
},
|
|
32910
|
+
async ackCommand(id, body) {
|
|
32911
|
+
const validated = DeviceCommandAckBody.safeParse(body);
|
|
32912
|
+
const route = ackRoute(id);
|
|
32913
|
+
if (!validated.success) throw new RemoteRequestInvalid(route, validated.error);
|
|
32914
|
+
const response = await send({
|
|
32915
|
+
...common,
|
|
32916
|
+
method: "POST",
|
|
32917
|
+
url: url2(route),
|
|
32918
|
+
body: JSON.stringify(validated.data)
|
|
32919
|
+
});
|
|
32920
|
+
okBody(response);
|
|
32921
|
+
}
|
|
32922
|
+
};
|
|
32923
|
+
}
|
|
32924
|
+
|
|
32098
32925
|
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
32099
32926
|
function statusOf(err) {
|
|
32100
32927
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
@@ -32113,12 +32940,27 @@ function classifyFailure(err) {
|
|
|
32113
32940
|
}
|
|
32114
32941
|
}
|
|
32115
32942
|
|
|
32943
|
+
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
32944
|
+
var REQUEST_TIMEOUT_MS = 2e3;
|
|
32945
|
+
function withTimeout(promise2, ms) {
|
|
32946
|
+
let timer;
|
|
32947
|
+
const timeout = new Promise((_, reject) => {
|
|
32948
|
+
timer = setTimeout(() => {
|
|
32949
|
+
reject(new Error("attached gateway request timed out"));
|
|
32950
|
+
}, ms);
|
|
32951
|
+
});
|
|
32952
|
+
promise2.catch(() => void 0);
|
|
32953
|
+
return Promise.race([promise2, timeout]).finally(() => {
|
|
32954
|
+
clearTimeout(timer);
|
|
32955
|
+
});
|
|
32956
|
+
}
|
|
32957
|
+
|
|
32116
32958
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
32117
|
-
import { readFileSync as
|
|
32118
|
-
import { join as
|
|
32959
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
32960
|
+
import { join as join13 } from "path";
|
|
32119
32961
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
32120
32962
|
function forwardDropsPath(dataDir2) {
|
|
32121
|
-
return
|
|
32963
|
+
return join13(dataDir2, FORWARD_DROPS_FILENAME);
|
|
32122
32964
|
}
|
|
32123
32965
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
32124
32966
|
if (count <= 0) return;
|
|
@@ -32136,7 +32978,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
32136
32978
|
}
|
|
32137
32979
|
function readForwardDrops(dataDir2) {
|
|
32138
32980
|
try {
|
|
32139
|
-
const parsed2 = JSON.parse(
|
|
32981
|
+
const parsed2 = JSON.parse(readFileSync8(forwardDropsPath(dataDir2), "utf8"));
|
|
32140
32982
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
32141
32983
|
const record2 = parsed2;
|
|
32142
32984
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -32154,13 +32996,13 @@ function readForwardDrops(dataDir2) {
|
|
|
32154
32996
|
|
|
32155
32997
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
32156
32998
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
32157
|
-
import { readFileSync as
|
|
32999
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
32158
33000
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
32159
|
-
import { join as
|
|
33001
|
+
import { join as join22 } from "path";
|
|
32160
33002
|
|
|
32161
33003
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32162
33004
|
import { existsSync as existsSync7 } from "fs";
|
|
32163
|
-
import { join as
|
|
33005
|
+
import { join as join14 } from "path";
|
|
32164
33006
|
|
|
32165
33007
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32166
33008
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32214,7 +33056,7 @@ function resolveProvider() {
|
|
|
32214
33056
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32215
33057
|
try {
|
|
32216
33058
|
ensureLayoutDirSync(base);
|
|
32217
|
-
const settingsFile =
|
|
33059
|
+
const settingsFile = join14(settingsDir(base), "settings.json");
|
|
32218
33060
|
if (existsSync7(settingsFile)) tightenFile(settingsFile);
|
|
32219
33061
|
} catch {
|
|
32220
33062
|
}
|
|
@@ -32238,9 +33080,9 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32238
33080
|
}
|
|
32239
33081
|
|
|
32240
33082
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32241
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
33083
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32242
33084
|
import { homedir as homedir2 } from "os";
|
|
32243
|
-
import { basename as basename4, join as
|
|
33085
|
+
import { basename as basename4, join as join16 } from "path";
|
|
32244
33086
|
|
|
32245
33087
|
// ../../packages/detections/src/egress/registry.ts
|
|
32246
33088
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -35369,8 +36211,8 @@ function maskText(text) {
|
|
|
35369
36211
|
}
|
|
35370
36212
|
|
|
35371
36213
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
35372
|
-
import { existsSync as existsSync8, readFileSync as
|
|
35373
|
-
import { basename as basename3, dirname as
|
|
36214
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
36215
|
+
import { basename as basename3, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
|
|
35374
36216
|
function resolveRepoIdentity(cwd) {
|
|
35375
36217
|
try {
|
|
35376
36218
|
const root = findGitRoot(cwd);
|
|
@@ -35420,7 +36262,7 @@ function resolveGitBranch(cwd) {
|
|
|
35420
36262
|
try {
|
|
35421
36263
|
const root = findGitRoot(cwd);
|
|
35422
36264
|
if (!root) return void 0;
|
|
35423
|
-
const dotGit =
|
|
36265
|
+
const dotGit = join15(root, ".git");
|
|
35424
36266
|
let gitdir;
|
|
35425
36267
|
try {
|
|
35426
36268
|
gitdir = statSync6(dotGit).isDirectory() ? dotGit : resolveWorktreeGitdir(root, dotGit);
|
|
@@ -35428,7 +36270,7 @@ function resolveGitBranch(cwd) {
|
|
|
35428
36270
|
return void 0;
|
|
35429
36271
|
}
|
|
35430
36272
|
if (gitdir === void 0) return void 0;
|
|
35431
|
-
const head = safeRead(
|
|
36273
|
+
const head = safeRead(join15(gitdir, "HEAD"));
|
|
35432
36274
|
if (!head) return void 0;
|
|
35433
36275
|
return /^ref:\s*refs\/heads\/(.+?)\s*$/m.exec(head)?.[1];
|
|
35434
36276
|
} catch {
|
|
@@ -35438,41 +36280,41 @@ function resolveGitBranch(cwd) {
|
|
|
35438
36280
|
function resolveWorktreeGitdir(root, dotGitFile) {
|
|
35439
36281
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGitFile) ?? "")?.[1];
|
|
35440
36282
|
if (!target) return void 0;
|
|
35441
|
-
return isAbsolute(target) ? target :
|
|
36283
|
+
return isAbsolute(target) ? target : join15(root, target);
|
|
35442
36284
|
}
|
|
35443
36285
|
function findGitRoot(start) {
|
|
35444
36286
|
let dir = start;
|
|
35445
36287
|
for (; ; ) {
|
|
35446
|
-
if (existsSync8(
|
|
35447
|
-
const parent =
|
|
36288
|
+
if (existsSync8(join15(dir, ".git"))) return dir;
|
|
36289
|
+
const parent = dirname4(dir);
|
|
35448
36290
|
if (parent === dir) return void 0;
|
|
35449
36291
|
dir = parent;
|
|
35450
36292
|
}
|
|
35451
36293
|
}
|
|
35452
36294
|
function resolveGitContext(root) {
|
|
35453
|
-
const dotGit =
|
|
36295
|
+
const dotGit = join15(root, ".git");
|
|
35454
36296
|
try {
|
|
35455
36297
|
if (statSync6(dotGit).isDirectory()) {
|
|
35456
|
-
return { configPath:
|
|
36298
|
+
return { configPath: join15(dotGit, "config"), headRoot: root };
|
|
35457
36299
|
}
|
|
35458
36300
|
} catch {
|
|
35459
36301
|
return void 0;
|
|
35460
36302
|
}
|
|
35461
36303
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
35462
36304
|
if (!target) return void 0;
|
|
35463
|
-
const gitdir = isAbsolute(target) ? target :
|
|
35464
|
-
if (existsSync8(
|
|
35465
|
-
return { configPath:
|
|
36305
|
+
const gitdir = isAbsolute(target) ? target : join15(root, target);
|
|
36306
|
+
if (existsSync8(join15(gitdir, "config"))) {
|
|
36307
|
+
return { configPath: join15(gitdir, "config"), headRoot: root };
|
|
35466
36308
|
}
|
|
35467
|
-
const commonRaw = safeRead(
|
|
36309
|
+
const commonRaw = safeRead(join15(gitdir, "commondir"))?.trim();
|
|
35468
36310
|
if (!commonRaw) return void 0;
|
|
35469
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
35470
|
-
const headRoot = basename3(commonGitDir) === ".git" ?
|
|
35471
|
-
return { configPath:
|
|
36311
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join15(gitdir, commonRaw);
|
|
36312
|
+
const headRoot = basename3(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
36313
|
+
return { configPath: join15(commonGitDir, "config"), headRoot };
|
|
35472
36314
|
}
|
|
35473
36315
|
function safeRead(path) {
|
|
35474
36316
|
try {
|
|
35475
|
-
return
|
|
36317
|
+
return readFileSync9(path, "utf8");
|
|
35476
36318
|
} catch {
|
|
35477
36319
|
return void 0;
|
|
35478
36320
|
}
|
|
@@ -35534,31 +36376,31 @@ function resolveConfigInventory(input2) {
|
|
|
35534
36376
|
};
|
|
35535
36377
|
try {
|
|
35536
36378
|
const home = input2.homeDir ?? homedir2();
|
|
35537
|
-
const claudeDir =
|
|
36379
|
+
const claudeDir = join16(home, ".claude");
|
|
35538
36380
|
const repo = resolveRepoIdentity(input2.cwd);
|
|
35539
36381
|
const repoIdentity = repo?.url ?? input2.cwd;
|
|
35540
36382
|
const projectSource = `project:${repoIdentity}`;
|
|
35541
|
-
collectSettingsHooks(scan2,
|
|
35542
|
-
collectSettingsHooks(scan2,
|
|
35543
|
-
collectSettingsHooks(scan2,
|
|
36383
|
+
collectSettingsHooks(scan2, join16(claudeDir, "settings.json"), "user");
|
|
36384
|
+
collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.json"), "project");
|
|
36385
|
+
collectSettingsHooks(scan2, join16(input2.cwd, ".claude", "settings.local.json"), "local");
|
|
35544
36386
|
const projectOrigin = { scope: "project", project: repoIdentity };
|
|
35545
|
-
collectMcpFile(scan2,
|
|
35546
|
-
collectUserClaudeJson(scan2,
|
|
35547
|
-
collectMcpFile(scan2,
|
|
35548
|
-
collectMcpFile(scan2,
|
|
35549
|
-
collectMcpFile(scan2,
|
|
36387
|
+
collectMcpFile(scan2, join16(input2.cwd, ".mcp.json"), projectOrigin, { recordErrors: true });
|
|
36388
|
+
collectUserClaudeJson(scan2, join16(home, ".claude.json"), input2.cwd, repoIdentity);
|
|
36389
|
+
collectMcpFile(scan2, join16(claudeDir, "settings.json"), { scope: "user" });
|
|
36390
|
+
collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.json"), projectOrigin);
|
|
36391
|
+
collectMcpFile(scan2, join16(input2.cwd, ".claude", "settings.local.json"), {
|
|
35550
36392
|
scope: "local",
|
|
35551
36393
|
project: repoIdentity
|
|
35552
36394
|
});
|
|
35553
36395
|
collectConfigFiles(scan2, claudeDir, input2.cwd);
|
|
35554
|
-
collectSkillsDir(scan2,
|
|
35555
|
-
collectSkillsDir(scan2,
|
|
36396
|
+
collectSkillsDir(scan2, join16(claudeDir, "skills"), { source: "local", scope: "user" });
|
|
36397
|
+
collectSkillsDir(scan2, join16(input2.cwd, ".claude", "skills"), {
|
|
35556
36398
|
source: projectSource,
|
|
35557
36399
|
scope: "project"
|
|
35558
36400
|
});
|
|
35559
36401
|
collectInstalledPlugins(scan2, claudeDir);
|
|
35560
36402
|
collectMarketplaceSkills(scan2, claudeDir);
|
|
35561
|
-
collectSkillsDir(scan2,
|
|
36403
|
+
collectSkillsDir(scan2, join16(input2.cwd, "skills"), { source: projectSource, scope: "project" });
|
|
35562
36404
|
scan2.skills = dedupeSkills(scan2.skills);
|
|
35563
36405
|
scan2.mcpServers = dedupeMcpServers(scan2.mcpServers);
|
|
35564
36406
|
} catch (err) {
|
|
@@ -35687,7 +36529,7 @@ function projectEntryFor(projects, cwd) {
|
|
|
35687
36529
|
return void 0;
|
|
35688
36530
|
}
|
|
35689
36531
|
function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
35690
|
-
const manifestPath =
|
|
36532
|
+
const manifestPath = join16(installPath, ".claude-plugin", "plugin.json");
|
|
35691
36533
|
const raw = readOptional(manifestPath);
|
|
35692
36534
|
if (raw === void 0) return;
|
|
35693
36535
|
try {
|
|
@@ -35695,7 +36537,7 @@ function collectPluginManifestMcp(scan2, installPath, origin) {
|
|
|
35695
36537
|
if (typeof parsed2 !== "object" || parsed2 === null) return;
|
|
35696
36538
|
const declared = parsed2.mcpServers;
|
|
35697
36539
|
if (typeof declared === "string" && declared.length > 0) {
|
|
35698
|
-
collectMcpFile(scan2,
|
|
36540
|
+
collectMcpFile(scan2, join16(installPath, declared), origin, { recordErrors: true });
|
|
35699
36541
|
} else {
|
|
35700
36542
|
collectMcpObject(scan2, declared, manifestPath, origin);
|
|
35701
36543
|
}
|
|
@@ -35712,14 +36554,14 @@ var SETTINGS_KEY_LABELS = [
|
|
|
35712
36554
|
["statusLine", "status line"]
|
|
35713
36555
|
];
|
|
35714
36556
|
function collectConfigFiles(scan2, claudeDir, cwd) {
|
|
35715
|
-
settingsConfigFile(scan2,
|
|
35716
|
-
settingsConfigFile(scan2,
|
|
35717
|
-
settingsConfigFile(scan2,
|
|
35718
|
-
memoryConfigFile(scan2,
|
|
35719
|
-
memoryConfigFile(scan2,
|
|
35720
|
-
mcpJsonConfigFile(scan2,
|
|
35721
|
-
dirConfigFile(scan2,
|
|
35722
|
-
dirConfigFile(scan2,
|
|
36557
|
+
settingsConfigFile(scan2, join16(claudeDir, "settings.json"), "user", "User settings");
|
|
36558
|
+
settingsConfigFile(scan2, join16(cwd, ".claude", "settings.json"), "project", "Project settings");
|
|
36559
|
+
settingsConfigFile(scan2, join16(cwd, ".claude", "settings.local.json"), "local", "Local overrides");
|
|
36560
|
+
memoryConfigFile(scan2, join16(claudeDir, "CLAUDE.md"), "user", "User memory");
|
|
36561
|
+
memoryConfigFile(scan2, join16(cwd, "CLAUDE.md"), "project", "Project memory");
|
|
36562
|
+
mcpJsonConfigFile(scan2, join16(cwd, ".mcp.json"));
|
|
36563
|
+
dirConfigFile(scan2, join16(cwd, ".claude", "commands"), "Slash commands", "command");
|
|
36564
|
+
dirConfigFile(scan2, join16(cwd, ".claude", "agents"), "Subagents", "subagent");
|
|
35723
36565
|
}
|
|
35724
36566
|
function configFileEntry(path, scope, kind) {
|
|
35725
36567
|
try {
|
|
@@ -35794,7 +36636,7 @@ function countMarkdownFiles(dir, depth) {
|
|
|
35794
36636
|
let count = 0;
|
|
35795
36637
|
for (const dirent of readdirSync2(dir, { withFileTypes: true })) {
|
|
35796
36638
|
if (dirent.name.startsWith(".")) continue;
|
|
35797
|
-
if (dirent.isDirectory()) count += countMarkdownFiles(
|
|
36639
|
+
if (dirent.isDirectory()) count += countMarkdownFiles(join16(dir, dirent.name), depth + 1);
|
|
35798
36640
|
else if (dirent.name.endsWith(".md")) count += 1;
|
|
35799
36641
|
}
|
|
35800
36642
|
return count;
|
|
@@ -35807,7 +36649,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
35807
36649
|
return;
|
|
35808
36650
|
}
|
|
35809
36651
|
for (const name of names) {
|
|
35810
|
-
const skillFile =
|
|
36652
|
+
const skillFile = join16(dir, name, "SKILL.md");
|
|
35811
36653
|
try {
|
|
35812
36654
|
const raw = readOptional(skillFile);
|
|
35813
36655
|
if (raw === void 0) continue;
|
|
@@ -35816,7 +36658,7 @@ function collectSkillsDir(scan2, dir, origin) {
|
|
|
35816
36658
|
name: front.name ?? name,
|
|
35817
36659
|
source: origin.source,
|
|
35818
36660
|
scope: origin.scope,
|
|
35819
|
-
location:
|
|
36661
|
+
location: join16(dir, name),
|
|
35820
36662
|
updatedAt: statSync7(skillFile).mtime.toISOString()
|
|
35821
36663
|
};
|
|
35822
36664
|
const version2 = front.version ?? origin.defaultVersion;
|
|
@@ -35847,7 +36689,7 @@ function parseFrontmatter(raw) {
|
|
|
35847
36689
|
return out;
|
|
35848
36690
|
}
|
|
35849
36691
|
function collectInstalledPlugins(scan2, claudeDir) {
|
|
35850
|
-
const manifestPath =
|
|
36692
|
+
const manifestPath = join16(claudeDir, "plugins", "installed_plugins.json");
|
|
35851
36693
|
const raw = readOptional(manifestPath);
|
|
35852
36694
|
if (raw === void 0) return;
|
|
35853
36695
|
let plugins;
|
|
@@ -35872,7 +36714,7 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
35872
36714
|
if (typeof installPath !== "string" || seen.has(installPath)) continue;
|
|
35873
36715
|
seen.add(installPath);
|
|
35874
36716
|
const version2 = install.version;
|
|
35875
|
-
const hooksPath =
|
|
36717
|
+
const hooksPath = join16(installPath, "hooks", "hooks.json");
|
|
35876
36718
|
const hooksRaw = readOptional(hooksPath);
|
|
35877
36719
|
if (hooksRaw !== void 0) {
|
|
35878
36720
|
try {
|
|
@@ -35892,22 +36734,22 @@ function collectInstalledPlugins(scan2, claudeDir) {
|
|
|
35892
36734
|
}
|
|
35893
36735
|
const origin = { source: marketplace, scope: "plugin", pluginName };
|
|
35894
36736
|
if (typeof version2 === "string") origin.defaultVersion = version2;
|
|
35895
|
-
collectSkillsDir(scan2,
|
|
36737
|
+
collectSkillsDir(scan2, join16(installPath, "skills"), origin);
|
|
35896
36738
|
const mcpOrigin = { scope: "plugin", pluginName, marketplace };
|
|
35897
|
-
collectMcpFile(scan2,
|
|
36739
|
+
collectMcpFile(scan2, join16(installPath, ".mcp.json"), mcpOrigin, { recordErrors: true });
|
|
35898
36740
|
collectPluginManifestMcp(scan2, installPath, mcpOrigin);
|
|
35899
36741
|
}
|
|
35900
36742
|
}
|
|
35901
36743
|
}
|
|
35902
36744
|
function collectMarketplaceSkills(scan2, claudeDir) {
|
|
35903
|
-
for (const mp of readMarketplaces(
|
|
36745
|
+
for (const mp of readMarketplaces(join16(claudeDir, "plugins", "known_marketplaces.json"))) {
|
|
35904
36746
|
if (isClaudeOfficialMarketplace(mp.name, mp.repo)) continue;
|
|
35905
|
-
collectSkillsDir(scan2,
|
|
36747
|
+
collectSkillsDir(scan2, join16(mp.installLocation, "skills"), {
|
|
35906
36748
|
source: mp.name,
|
|
35907
36749
|
scope: "plugin"
|
|
35908
36750
|
});
|
|
35909
|
-
collectPluginSkillDirs(scan2,
|
|
35910
|
-
collectPluginSkillDirs(scan2,
|
|
36751
|
+
collectPluginSkillDirs(scan2, join16(mp.installLocation, "plugins"), mp.name);
|
|
36752
|
+
collectPluginSkillDirs(scan2, join16(mp.installLocation, "external_plugins"), mp.name);
|
|
35911
36753
|
}
|
|
35912
36754
|
}
|
|
35913
36755
|
function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
@@ -35918,7 +36760,7 @@ function collectPluginSkillDirs(scan2, pluginsDir, marketplace) {
|
|
|
35918
36760
|
return;
|
|
35919
36761
|
}
|
|
35920
36762
|
for (const plugin of plugins) {
|
|
35921
|
-
collectSkillsDir(scan2,
|
|
36763
|
+
collectSkillsDir(scan2, join16(pluginsDir, plugin, "skills"), {
|
|
35922
36764
|
source: marketplace,
|
|
35923
36765
|
scope: "plugin",
|
|
35924
36766
|
pluginName: plugin
|
|
@@ -35972,7 +36814,7 @@ function dedupeMcpServers(servers) {
|
|
|
35972
36814
|
}
|
|
35973
36815
|
function readOptional(path) {
|
|
35974
36816
|
try {
|
|
35975
|
-
return
|
|
36817
|
+
return readFileSync10(path, "utf8");
|
|
35976
36818
|
} catch {
|
|
35977
36819
|
return void 0;
|
|
35978
36820
|
}
|
|
@@ -36006,11 +36848,11 @@ import { Worker } from "worker_threads";
|
|
|
36006
36848
|
|
|
36007
36849
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
36008
36850
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
36009
|
-
import { readFileSync as
|
|
36010
|
-
import { join as
|
|
36851
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
36852
|
+
import { join as join17 } from "path";
|
|
36011
36853
|
function readIgnoreLayer(dir, filename, anchorLen) {
|
|
36012
36854
|
try {
|
|
36013
|
-
return { matcher: (0, import_ignore.default)().add(
|
|
36855
|
+
return { matcher: (0, import_ignore.default)().add(readFileSync11(join17(dir, filename), "utf8")), anchorLen };
|
|
36014
36856
|
} catch {
|
|
36015
36857
|
return void 0;
|
|
36016
36858
|
}
|
|
@@ -36072,18 +36914,18 @@ import {
|
|
|
36072
36914
|
fstatSync,
|
|
36073
36915
|
mkdirSync as mkdirSync2,
|
|
36074
36916
|
openSync as openSync2,
|
|
36075
|
-
readFileSync as
|
|
36917
|
+
readFileSync as readFileSync12,
|
|
36076
36918
|
readSync,
|
|
36077
36919
|
writeFileSync as writeFileSync5
|
|
36078
36920
|
} from "fs";
|
|
36079
|
-
import { join as
|
|
36921
|
+
import { join as join18 } from "path";
|
|
36080
36922
|
var SESSION_MODEL_MARKER = "session-model";
|
|
36081
36923
|
function recordSessionModel(dataDir2, sessionId, model) {
|
|
36082
36924
|
if (sessionId === void 0 || sessionId === "") return;
|
|
36083
36925
|
if (model === void 0 || model === "") return;
|
|
36084
36926
|
try {
|
|
36085
36927
|
mkdirSync2(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
36086
|
-
writeFileSync5(
|
|
36928
|
+
writeFileSync5(join18(dataDir2, SESSION_MODEL_MARKER), JSON.stringify({ sessionId, model }), {
|
|
36087
36929
|
encoding: "utf8",
|
|
36088
36930
|
mode: DATA_FILE_MODE
|
|
36089
36931
|
});
|
|
@@ -36093,17 +36935,17 @@ function recordSessionModel(dataDir2, sessionId, model) {
|
|
|
36093
36935
|
var TAIL_BYTES = 256 * 1024;
|
|
36094
36936
|
|
|
36095
36937
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
36096
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
36097
|
-
import { join as
|
|
36938
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
36939
|
+
import { join as join19 } from "path";
|
|
36098
36940
|
var SESSION_START_MARKER = "session-start-last";
|
|
36099
36941
|
function claimSessionStart(dataDir2, sessionId) {
|
|
36100
36942
|
return claimOncePerSession(dataDir2, SESSION_START_MARKER, sessionId);
|
|
36101
36943
|
}
|
|
36102
36944
|
function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
36103
36945
|
if (!sessionId) return true;
|
|
36104
|
-
const path =
|
|
36946
|
+
const path = join19(dataDir2, marker);
|
|
36105
36947
|
try {
|
|
36106
|
-
if (
|
|
36948
|
+
if (readFileSync13(path, "utf8") === sessionId) return false;
|
|
36107
36949
|
} catch {
|
|
36108
36950
|
}
|
|
36109
36951
|
try {
|
|
@@ -36116,11 +36958,11 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
|
36116
36958
|
|
|
36117
36959
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
36118
36960
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
36119
|
-
import { basename as basename5, dirname as
|
|
36961
|
+
import { basename as basename5, dirname as dirname5, sep as sep3 } from "path";
|
|
36120
36962
|
|
|
36121
36963
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
36122
36964
|
import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
36123
|
-
import { basename as basename6, join as
|
|
36965
|
+
import { basename as basename6, join as join20 } from "path";
|
|
36124
36966
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
36125
36967
|
".git",
|
|
36126
36968
|
"node_modules",
|
|
@@ -36217,8 +37059,8 @@ function resolveProjectFiles(cwd, opts = {}) {
|
|
|
36217
37059
|
}
|
|
36218
37060
|
if (entry.isDirectory()) {
|
|
36219
37061
|
if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, dirRel, entry.name, true)) continue;
|
|
36220
|
-
const fullPath =
|
|
36221
|
-
if (existsSync10(
|
|
37062
|
+
const fullPath = join20(dir, entry.name);
|
|
37063
|
+
if (existsSync10(join20(fullPath, ".git"))) continue;
|
|
36222
37064
|
if (depth >= bounds.maxDepth) {
|
|
36223
37065
|
walk.omitted = true;
|
|
36224
37066
|
continue;
|
|
@@ -36291,9 +37133,9 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
36291
37133
|
|
|
36292
37134
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
36293
37135
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
36294
|
-
import { join as
|
|
37136
|
+
import { join as join21 } from "path";
|
|
36295
37137
|
function throttled(dataDir2, markerName, windowMs) {
|
|
36296
|
-
const marker =
|
|
37138
|
+
const marker = join21(dataDir2, markerName);
|
|
36297
37139
|
try {
|
|
36298
37140
|
if (Date.now() - statSync8(marker).mtimeMs < windowMs) return true;
|
|
36299
37141
|
} catch {
|
|
@@ -36306,25 +37148,17 @@ function throttled(dataDir2, markerName, windowMs) {
|
|
|
36306
37148
|
return false;
|
|
36307
37149
|
}
|
|
36308
37150
|
|
|
36309
|
-
// ../../packages/plugin-runtime/src/attached/with-timeout.ts
|
|
36310
|
-
var REQUEST_TIMEOUT_MS = 2e3;
|
|
36311
|
-
function withTimeout(promise2, ms) {
|
|
36312
|
-
let timer;
|
|
36313
|
-
const timeout = new Promise((_, reject) => {
|
|
36314
|
-
timer = setTimeout(() => {
|
|
36315
|
-
reject(new Error("attached gateway request timed out"));
|
|
36316
|
-
}, ms);
|
|
36317
|
-
});
|
|
36318
|
-
promise2.catch(() => void 0);
|
|
36319
|
-
return Promise.race([promise2, timeout]).finally(() => {
|
|
36320
|
-
clearTimeout(timer);
|
|
36321
|
-
});
|
|
36322
|
-
}
|
|
36323
|
-
|
|
36324
37151
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
36325
37152
|
function isInvalidRequest(err) {
|
|
36326
37153
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
36327
37154
|
}
|
|
37155
|
+
function isRouteAbsent(err) {
|
|
37156
|
+
return typeof err === "object" && err !== null && err.name === "RemoteRouteAbsent";
|
|
37157
|
+
}
|
|
37158
|
+
function isServerRejection(err) {
|
|
37159
|
+
const status = statusOf(err);
|
|
37160
|
+
return status !== null && status >= 400 && status <= 499 && status !== 401 && status !== 403 && status !== 404 && status !== 429;
|
|
37161
|
+
}
|
|
36328
37162
|
var FORWARD_BUDGET_MS = 1500;
|
|
36329
37163
|
var DECISION_PATH_BUDGET_MS = 800;
|
|
36330
37164
|
var BREAKER_FAILURE_THRESHOLD = 3;
|
|
@@ -36352,7 +37186,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
36352
37186
|
}
|
|
36353
37187
|
function createForwardPolicy(deps) {
|
|
36354
37188
|
const now = deps.now ?? (() => Date.now());
|
|
36355
|
-
const file2 =
|
|
37189
|
+
const file2 = join22(deps.dir, STATE_FILENAME);
|
|
36356
37190
|
let state = null;
|
|
36357
37191
|
let loading = null;
|
|
36358
37192
|
async function readState() {
|
|
@@ -36392,6 +37226,20 @@ function createForwardPolicy(deps) {
|
|
|
36392
37226
|
} catch {
|
|
36393
37227
|
current = { ...CLOSED };
|
|
36394
37228
|
}
|
|
37229
|
+
const restoreOpenedAtMs = (openedAtMs) => persist({
|
|
37230
|
+
consecutiveFailures: current.consecutiveFailures,
|
|
37231
|
+
openedAtMs,
|
|
37232
|
+
lastFailure: current.lastFailure
|
|
37233
|
+
});
|
|
37234
|
+
const recordFailure = (cause) => {
|
|
37235
|
+
const failures = current.consecutiveFailures + 1;
|
|
37236
|
+
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
37237
|
+
return persist({
|
|
37238
|
+
consecutiveFailures: failures,
|
|
37239
|
+
openedAtMs: shouldOpen ? now() : null,
|
|
37240
|
+
lastFailure: cause
|
|
37241
|
+
});
|
|
37242
|
+
};
|
|
36395
37243
|
const at = now();
|
|
36396
37244
|
if (current.openedAtMs !== null) {
|
|
36397
37245
|
if (at - current.openedAtMs < BREAKER_COOLDOWN_MS) {
|
|
@@ -36410,15 +37258,20 @@ function createForwardPolicy(deps) {
|
|
|
36410
37258
|
}
|
|
36411
37259
|
return { ok: true, value };
|
|
36412
37260
|
} catch (err) {
|
|
36413
|
-
if (isInvalidRequest(err))
|
|
37261
|
+
if (isInvalidRequest(err)) {
|
|
37262
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(current.openedAtMs);
|
|
37263
|
+
return { ok: false, reason: "invalid-request" };
|
|
37264
|
+
}
|
|
37265
|
+
if (isRouteAbsent(err)) {
|
|
37266
|
+
if (current.openedAtMs !== null) await restoreOpenedAtMs(null);
|
|
37267
|
+
return { ok: false, reason: "route-absent" };
|
|
37268
|
+
}
|
|
37269
|
+
if (isServerRejection(err)) {
|
|
37270
|
+
await recordFailure("unreachable");
|
|
37271
|
+
return { ok: false, reason: "rejected" };
|
|
37272
|
+
}
|
|
36414
37273
|
const reason = classifyFailure(err);
|
|
36415
|
-
|
|
36416
|
-
const shouldOpen = current.openedAtMs !== null || failures >= BREAKER_FAILURE_THRESHOLD;
|
|
36417
|
-
await persist({
|
|
36418
|
-
consecutiveFailures: failures,
|
|
36419
|
-
openedAtMs: shouldOpen ? now() : null,
|
|
36420
|
-
lastFailure: reason
|
|
36421
|
-
});
|
|
37274
|
+
await recordFailure(reason);
|
|
36422
37275
|
return { ok: false, reason };
|
|
36423
37276
|
}
|
|
36424
37277
|
}
|
|
@@ -36426,13 +37279,11 @@ function createForwardPolicy(deps) {
|
|
|
36426
37279
|
}
|
|
36427
37280
|
|
|
36428
37281
|
// ../../packages/plugin-runtime/src/attached/gateway.ts
|
|
36429
|
-
|
|
36430
|
-
|
|
36431
|
-
|
|
36432
|
-
|
|
36433
|
-
|
|
36434
|
-
block: 4
|
|
36435
|
-
};
|
|
37282
|
+
function strongerOf(a, b) {
|
|
37283
|
+
if (a === null) return b;
|
|
37284
|
+
if (b === null) return a;
|
|
37285
|
+
return strongerAction(a, b);
|
|
37286
|
+
}
|
|
36436
37287
|
function ruleCategoryMap(wireRules, localRules) {
|
|
36437
37288
|
const map2 = /* @__PURE__ */ new Map();
|
|
36438
37289
|
for (const rule of wireRules ?? []) map2.set(rule.id, rule.category);
|
|
@@ -36442,11 +37293,6 @@ function ruleCategoryMap(wireRules, localRules) {
|
|
|
36442
37293
|
}
|
|
36443
37294
|
return map2;
|
|
36444
37295
|
}
|
|
36445
|
-
function strongerOf(a, b) {
|
|
36446
|
-
if (a === null) return b;
|
|
36447
|
-
if (b === null) return a;
|
|
36448
|
-
return ACTION_STRENGTH[a] >= ACTION_STRENGTH[b] ? a : b;
|
|
36449
|
-
}
|
|
36450
37296
|
function policyKey(policy) {
|
|
36451
37297
|
return "ruleId" in policy.target ? `rule:${policy.target.ruleId}` : `category:${policy.target.category}`;
|
|
36452
37298
|
}
|
|
@@ -36465,7 +37311,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36465
37311
|
const floor = floorFor(policy, categoryByRuleId);
|
|
36466
37312
|
remoteCategoryAction.set(
|
|
36467
37313
|
policy.target.category,
|
|
36468
|
-
floor !== null &&
|
|
37314
|
+
floor !== null && !isActionAtLeast(policy.action, floor) ? floor : policy.action
|
|
36469
37315
|
);
|
|
36470
37316
|
}
|
|
36471
37317
|
for (const policy of localPolicies) {
|
|
@@ -36482,7 +37328,7 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36482
37328
|
}
|
|
36483
37329
|
merged.set(
|
|
36484
37330
|
key,
|
|
36485
|
-
remoteFloor !== null &&
|
|
37331
|
+
remoteFloor !== null && !isActionAtLeast(policy.action, remoteFloor) ? { ...policy, action: remoteFloor } : policy
|
|
36486
37332
|
);
|
|
36487
37333
|
}
|
|
36488
37334
|
const localCategoryAction = /* @__PURE__ */ new Map();
|
|
@@ -36502,13 +37348,13 @@ function mergeRaiseOnly(localPolicies, remotePolicies, categoryByRuleId) {
|
|
|
36502
37348
|
if (category !== void 0) localFloor = localCategoryAction.get(category) ?? null;
|
|
36503
37349
|
}
|
|
36504
37350
|
const effectiveFloor = strongerOf(floor, localFloor);
|
|
36505
|
-
const clamped = effectiveFloor !== null &&
|
|
37351
|
+
const clamped = effectiveFloor !== null && !isActionAtLeast(policy.action, effectiveFloor) ? { ...policy, action: effectiveFloor } : policy;
|
|
36506
37352
|
const existing = merged.get(key);
|
|
36507
37353
|
if (existing === void 0) {
|
|
36508
37354
|
merged.set(key, clamped);
|
|
36509
37355
|
continue;
|
|
36510
37356
|
}
|
|
36511
|
-
if (
|
|
37357
|
+
if (actionRank(clamped.action) > actionRank(existing.action)) {
|
|
36512
37358
|
merged.set(key, clamped);
|
|
36513
37359
|
}
|
|
36514
37360
|
}
|
|
@@ -36541,6 +37387,8 @@ var AttachedDataGateway = class {
|
|
|
36541
37387
|
);
|
|
36542
37388
|
if (forwarded.ok && forwarded.value.accepted + forwarded.value.duplicates > 0) {
|
|
36543
37389
|
this.deps.local.markCaptureDelivered(record2.event, Date.now());
|
|
37390
|
+
} else {
|
|
37391
|
+
this.deps.local.markCaptureOwed(record2.event);
|
|
36544
37392
|
}
|
|
36545
37393
|
}
|
|
36546
37394
|
async ensureInventory(ctx) {
|
|
@@ -36577,9 +37425,10 @@ var AttachedDataGateway = class {
|
|
|
36577
37425
|
// a retried tool_call, exactly this path — can never stomp a populated row.
|
|
36578
37426
|
async recordAuditEvent(event) {
|
|
36579
37427
|
await this.deps.local.recordAuditEvent(event);
|
|
36580
|
-
await this.deps.forward.run(
|
|
37428
|
+
const forwarded = await this.deps.forward.run(
|
|
36581
37429
|
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
36582
37430
|
);
|
|
37431
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
36583
37432
|
}
|
|
36584
37433
|
// Attached `llm_call` is written locally by the inner gateway, then routed to
|
|
36585
37434
|
// the control plane through the existing `recordAuditEvent` ingest (no dedicated
|
|
@@ -36588,44 +37437,170 @@ var AttachedDataGateway = class {
|
|
|
36588
37437
|
// which would write the event to the local store a second time.
|
|
36589
37438
|
async recordLlmCall(input2) {
|
|
36590
37439
|
await this.deps.local.recordLlmCall(input2);
|
|
36591
|
-
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
)
|
|
37440
|
+
const event = llmAuditEvent(input2);
|
|
37441
|
+
const forwarded = await this.deps.forward.run(
|
|
37442
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
36595
37443
|
);
|
|
37444
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([event], Date.now());
|
|
36596
37445
|
}
|
|
36597
37446
|
/**
|
|
36598
|
-
* Forward one batch
|
|
37447
|
+
* Forward one batch in CHUNKS of AUDIT_EVENT_BATCH_MAX, under ONE aggregate deadline.
|
|
37448
|
+
*
|
|
37449
|
+
* This used to send one HTTP request per event, which is what made the batch
|
|
37450
|
+
* budget bite: at 200ms round-trip a 3s budget admitted ~15 events and threw
|
|
37451
|
+
* away everything after them. The same rows now cross 50 at a time over
|
|
37452
|
+
* `POST /v1/audit-events/batch` — the route the attach-time drain has always
|
|
37453
|
+
* used — so the same budget admits ~750. The wire cap is the server's own
|
|
37454
|
+
* constant, sized against server cost, and the client REFUSES a longer array
|
|
37455
|
+
* client-side, so the chunking here is not a convention.
|
|
36599
37456
|
*
|
|
36600
|
-
*
|
|
36601
|
-
*
|
|
36602
|
-
*
|
|
36603
|
-
*
|
|
36604
|
-
* built to accept (G8) where a blown hook timeout is not.
|
|
37457
|
+
* Still serial, and still for the original reason: firing N requests at once
|
|
37458
|
+
* would trade a latency problem for a burst the plane's per-key rate limiting
|
|
37459
|
+
* answers with the refusals the breaker then counts. Fewer, fuller requests is
|
|
37460
|
+
* the fix; more concurrent ones is not.
|
|
36605
37461
|
*
|
|
36606
|
-
*
|
|
36607
|
-
*
|
|
36608
|
-
*
|
|
37462
|
+
* When the deadline passes the remainder is dropped rather than sent: the
|
|
37463
|
+
* local write has already succeeded, so every caller has a correct result to
|
|
37464
|
+
* return. What is dropped is COUNTED, everywhere it can happen — this path
|
|
37465
|
+
* returns BEFORE `ForwardPolicy.run` is reached, so without the tally in
|
|
37466
|
+
* `forward-drops.ts` a slow-but-answering plane produces no failures, keeps
|
|
37467
|
+
* the breaker closed, renders a healthy block, and discards the tail of every
|
|
37468
|
+
* batch indefinitely. The SAME tally also covers a single that fails inside
|
|
37469
|
+
* the per-item retry below — the breaker opening mid-retry is a failure the
|
|
37470
|
+
* breaker's own state DOES capture, but the events still in this chunk once
|
|
37471
|
+
* that happens are neither delivered nor otherwise counted anywhere, which is
|
|
37472
|
+
* the same invisibility with a different cause.
|
|
36609
37473
|
*
|
|
36610
|
-
*
|
|
36611
|
-
*
|
|
36612
|
-
*
|
|
36613
|
-
*
|
|
36614
|
-
*
|
|
36615
|
-
*
|
|
37474
|
+
* `ok` ALONE IS NOT DELIVERY, the same rule `recordCapture` states for the
|
|
37475
|
+
* single-event ack and at fifty times the blast radius here:
|
|
37476
|
+
* `AuditEventBatchAck.accepted` is an aggregate count the wire contract does
|
|
37477
|
+
* not tie to the chunk's own length, so a 2xx answering `{accepted: 30}` for
|
|
37478
|
+
* fifty events is well-formed. Trusting `ok` alone would stamp all fifty as
|
|
37479
|
+
* delivered and never re-offer the twenty the plane did not take. So success
|
|
37480
|
+
* is checked against `chunk.length`; anything short of it falls into the same
|
|
37481
|
+
* per-item pass as a refused chunk, which is the only way to recover the
|
|
37482
|
+
* rows that did not land, since the ack carries no per-row verdict to
|
|
37483
|
+
* resend by.
|
|
37484
|
+
*
|
|
37485
|
+
* That fallback ASSUMES a re-send of an already-landed row is a harmless
|
|
37486
|
+
* no-op rather than a second cost — an assumption this file cannot verify.
|
|
37487
|
+
* `AuditEventBatchAck` carries only `accepted`, unlike its sibling
|
|
37488
|
+
* `IngestAck` (`accepted` + `duplicates`, with `accepted + duplicates ==`
|
|
37489
|
+
* the batch size as the invariant `recordCapture` reads), so whether a
|
|
37490
|
+
* duplicate counts toward THIS route's `accepted` is not expressed
|
|
37491
|
+
* anywhere in this repo. If it follows its sibling's convention and does
|
|
37492
|
+
* NOT, a chunk containing even one already-delivered row — the ordinary
|
|
37493
|
+
* consequence of a lost stamp, which this file already treats as cheap —
|
|
37494
|
+
* answers short forever and enters the per-item pass on every pass it is
|
|
37495
|
+
* offered again. The cost of that is bounded rather than silent: the
|
|
37496
|
+
* pass converges (every row lands and stamps), so it is one wasted round
|
|
37497
|
+
* of singles rather than a stall, and it errs toward an extra resend
|
|
37498
|
+
* rather than toward the lost row the alternative risks.
|
|
37499
|
+
*
|
|
37500
|
+
* BATCH-ATOMIC SETTLEMENT is otherwise the rule: the receiver wraps a chunk in
|
|
37501
|
+
* one transaction, so a full 2xx settles every event in it and a non-2xx
|
|
37502
|
+
* settles none — which is why the whole chunk is stamped together on a FULL
|
|
37503
|
+
* accept and none of it otherwise. THREE reasons do not deserve whole-chunk
|
|
37504
|
+
* treatment, alongside a short accept, and all are re-sent one event at a
|
|
37505
|
+
* time:
|
|
37506
|
+
*
|
|
37507
|
+
* `invalid-request` a chunk the client refused to send at all. One malformed
|
|
37508
|
+
* event would otherwise cost the 49 good ones beside it —
|
|
37509
|
+
* a new way to lose data introduced by the very change
|
|
37510
|
+
* meant to stop losing it.
|
|
37511
|
+
* `route-absent` a deployment that predates the batch route. The
|
|
37512
|
+
* single-event route is the one it serves, and re-sending
|
|
37513
|
+
* here rather than inside the client is what gives each
|
|
37514
|
+
* request its own budget instead of 50 inside one.
|
|
37515
|
+
* `rejected` the deployment's SERVER-side twin of `invalid-request` —
|
|
37516
|
+
* a 4xx body refusal from schema drift on the other side
|
|
37517
|
+
* of the wire. Settlement is batch-atomic on this reason
|
|
37518
|
+
* exactly as on the others, so leaving it out would cost
|
|
37519
|
+
* the whole chunk for one event the DEPLOYMENT considers
|
|
37520
|
+
* malformed, where the per-item form cost only that one.
|
|
37521
|
+
*
|
|
37522
|
+
* Every other reason (breaker-open, a refusal, a timeout) applies to the whole
|
|
37523
|
+
* chunk, and re-sending it item by item would just spend the budget failing 50
|
|
37524
|
+
* more times — for those, the blast radius stays exactly what it was before
|
|
37525
|
+
* batching.
|
|
36616
37526
|
*/
|
|
36617
37527
|
async forwardBatch(inputs, toEvent) {
|
|
36618
37528
|
const deadline = Date.now() + BATCH_FORWARD_BUDGET_MS;
|
|
36619
|
-
|
|
36620
|
-
|
|
36621
|
-
|
|
36622
|
-
|
|
36623
|
-
|
|
37529
|
+
const delivered = [];
|
|
37530
|
+
try {
|
|
37531
|
+
for (let i = 0; i < inputs.length; i += AUDIT_EVENT_BATCH_MAX) {
|
|
37532
|
+
const now = Date.now();
|
|
37533
|
+
if (now >= deadline) {
|
|
37534
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i, now);
|
|
37535
|
+
return;
|
|
37536
|
+
}
|
|
37537
|
+
const chunk = inputs.slice(i, i + AUDIT_EVENT_BATCH_MAX).map((input2) => toEvent(input2));
|
|
37538
|
+
const forwarded = await this.deps.forward.run(
|
|
37539
|
+
() => this.deps.client.recordAuditEvents(
|
|
37540
|
+
chunk.map((event) => reKeyForForward(event, this.remoteInventory))
|
|
37541
|
+
)
|
|
37542
|
+
);
|
|
37543
|
+
if (forwarded.ok) {
|
|
37544
|
+
if (forwarded.value.accepted === chunk.length) {
|
|
37545
|
+
delivered.push(...chunk);
|
|
37546
|
+
continue;
|
|
37547
|
+
}
|
|
37548
|
+
} else if (
|
|
37549
|
+
// THREE reasons are worth a second pass, one at a time, and they are
|
|
37550
|
+
// the three settled BEFORE the control plane refused anything, or
|
|
37551
|
+
// (for `rejected`) refused the BODY rather than the connection.
|
|
37552
|
+
//
|
|
37553
|
+
// `invalid-request` — the CLIENT refused the body before any request
|
|
37554
|
+
// went out: a defect in one event, not an outage. Re-sending singly
|
|
37555
|
+
// isolates the bad one instead of charging its 49 neighbours for it.
|
|
37556
|
+
//
|
|
37557
|
+
// `route-absent` — the deployment predates the batch route and serves
|
|
37558
|
+
// only the single-event one. The retry IS the compatibility path, and
|
|
37559
|
+
// it has to live HERE rather than inside the client: each single gets
|
|
37560
|
+
// its own FORWARD_BUDGET_MS through `run`, whereas the client's own
|
|
37561
|
+
// fallback would spend 50 sequential round trips inside the ONE
|
|
37562
|
+
// budget wrapping this call — turning a working older deployment into
|
|
37563
|
+
// a timeout, three of those into an open breaker, and every row into
|
|
37564
|
+
// a silent drop while the status surface called an answering
|
|
37565
|
+
// deployment down.
|
|
37566
|
+
//
|
|
37567
|
+
// `rejected` — the deployment's own 4xx refusal of the body, the
|
|
37568
|
+
// server-side twin of `invalid-request`: isolating it the same way
|
|
37569
|
+
// costs one event instead of the whole chunk for a defect the
|
|
37570
|
+
// deployment considers local to one row.
|
|
37571
|
+
//
|
|
37572
|
+
// Every other reason (breaker-open, a refusal, a timeout) applies to
|
|
37573
|
+
// the whole chunk; re-sending it item by item would just spend the
|
|
37574
|
+
// budget failing 50 more times.
|
|
37575
|
+
forwarded.reason !== "invalid-request" && forwarded.reason !== "route-absent" && forwarded.reason !== "rejected"
|
|
37576
|
+
) {
|
|
37577
|
+
continue;
|
|
37578
|
+
}
|
|
37579
|
+
for (const [j, event] of chunk.entries()) {
|
|
37580
|
+
const at = Date.now();
|
|
37581
|
+
if (at >= deadline) {
|
|
37582
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
37583
|
+
return;
|
|
37584
|
+
}
|
|
37585
|
+
const single = await this.deps.forward.run(
|
|
37586
|
+
() => this.deps.client.recordAuditEvent(reKeyForForward(event, this.remoteInventory))
|
|
37587
|
+
);
|
|
37588
|
+
if (single.ok) {
|
|
37589
|
+
delivered.push(event);
|
|
37590
|
+
continue;
|
|
37591
|
+
}
|
|
37592
|
+
if (single.reason === "breaker-open") {
|
|
37593
|
+
recordForwardDrops(this.deps.dataDir, inputs.length - i - j, at);
|
|
37594
|
+
return;
|
|
37595
|
+
}
|
|
37596
|
+
recordForwardDrops(this.deps.dataDir, 1, at);
|
|
37597
|
+
}
|
|
37598
|
+
}
|
|
37599
|
+
} finally {
|
|
37600
|
+
try {
|
|
37601
|
+
this.deps.local.markAuditEventsDelivered(delivered, Date.now());
|
|
37602
|
+
} catch {
|
|
36624
37603
|
}
|
|
36625
|
-
const input2 = inputs[i];
|
|
36626
|
-
await this.deps.forward.run(
|
|
36627
|
-
() => this.deps.client.recordAuditEvent(reKeyForForward(toEvent(input2), this.remoteInventory))
|
|
36628
|
-
);
|
|
36629
37604
|
}
|
|
36630
37605
|
}
|
|
36631
37606
|
// Delegated as a BATCH rather than looped over recordLlmCall: the inner
|
|
@@ -36668,9 +37643,10 @@ var AttachedDataGateway = class {
|
|
|
36668
37643
|
// local store.
|
|
36669
37644
|
async recordConfigScan(record2) {
|
|
36670
37645
|
await this.deps.local.recordConfigScan(record2);
|
|
36671
|
-
await this.deps.forward.run(
|
|
37646
|
+
const forwarded = await this.deps.forward.run(
|
|
36672
37647
|
() => this.deps.client.recordAuditEvent(reKeyForForward(record2.scanEvent, this.remoteInventory))
|
|
36673
37648
|
);
|
|
37649
|
+
if (forwarded.ok) this.deps.local.markAuditEventsDelivered([record2.scanEvent], Date.now());
|
|
36674
37650
|
}
|
|
36675
37651
|
async recordBlockedDetection(entry) {
|
|
36676
37652
|
return this.deps.local.recordBlockedDetection(entry);
|
|
@@ -36804,6 +37780,18 @@ var AttachedDataGateway = class {
|
|
|
36804
37780
|
// exactly what it did, leaving the whole control inert on every device
|
|
36805
37781
|
// while every test around it stayed green.
|
|
36806
37782
|
prohibitedModels: cached2.prohibitedModels
|
|
37783
|
+
// ALSO HONOURED FROM THE CACHE, and not a bundle field at all: each
|
|
37784
|
+
// merged policy's own `provenance`. `mergeRaiseOnly` spreads the policies
|
|
37785
|
+
// it emits, so an 'authored' policy arriving from the control plane
|
|
37786
|
+
// keeps that marker even where the clamp rebuilds it with a stronger
|
|
37787
|
+
// action. The device reads it in exactly one direction — the rules such a
|
|
37788
|
+
// policy targets are not locally re-assignable — so it sits on the
|
|
37789
|
+
// `prohibitedModels` side of the line for the same reason that field
|
|
37790
|
+
// does: it can only ever ADD a refusal, never relax one, and an unsigned
|
|
37791
|
+
// cache therefore has no relaxation to grant by carrying it. Dropping it
|
|
37792
|
+
// would be the silent failure rather than the safe one — the action would
|
|
37793
|
+
// still be enforced while the local override the organization authored
|
|
37794
|
+
// away quietly came back.
|
|
36807
37795
|
// `rulesComplete` is a STANDALONE-ONLY signal (the user's local installed
|
|
36808
37796
|
// snapshot) and is taken from the LOCAL bundle only — never from the wire
|
|
36809
37797
|
// or the on-disk cache. Honoring a cached one would hand the control plane, or
|
|
@@ -36843,10 +37831,10 @@ var AttachedDataGateway = class {
|
|
|
36843
37831
|
//
|
|
36844
37832
|
// Implementing these is what actually closes the skipped-local-maintenance
|
|
36845
37833
|
// gap: the OSS structural guard `hasLocalStoreMaintenance()` is satisfied by
|
|
36846
|
-
// any object carrying all
|
|
37834
|
+
// any object carrying them all, so the composite qualifies and SessionStart
|
|
36847
37835
|
// runs maintenance on the device's real store.
|
|
36848
37836
|
//
|
|
36849
|
-
// ⚠
|
|
37837
|
+
// ⚠ Several of them are SYNCHRONOUS and must stay that way. `handle-session-start`
|
|
36850
37838
|
// calls `capWarnEraEnforcement` without `await` and uses `staleBinaryNotice`'s
|
|
36851
37839
|
// return value directly; declaring them `async` here would hand those call
|
|
36852
37840
|
// sites a Promise and silently break both.
|
|
@@ -36869,9 +37857,15 @@ var AttachedDataGateway = class {
|
|
|
36869
37857
|
// Delegated like the rest, and SYNCHRONOUS for the reason the note above
|
|
36870
37858
|
// gives: `recordCapture` calls it after the forward has already settled, on a
|
|
36871
37859
|
// path that has nothing left to await.
|
|
37860
|
+
markCaptureOwed(event) {
|
|
37861
|
+
this.deps.local.markCaptureOwed(event);
|
|
37862
|
+
}
|
|
36872
37863
|
markCaptureDelivered(event, atMs) {
|
|
36873
37864
|
this.deps.local.markCaptureDelivered(event, atMs);
|
|
36874
37865
|
}
|
|
37866
|
+
markAuditEventsDelivered(events, atMs) {
|
|
37867
|
+
this.deps.local.markAuditEventsDelivered(events, atMs);
|
|
37868
|
+
}
|
|
36875
37869
|
};
|
|
36876
37870
|
function reKeyForForward(event, remote) {
|
|
36877
37871
|
if (remote === null) {
|
|
@@ -36914,281 +37908,17 @@ function toolAuditEvent(input2) {
|
|
|
36914
37908
|
}
|
|
36915
37909
|
|
|
36916
37910
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
36917
|
-
import { readFileSync as
|
|
36918
|
-
import { join as
|
|
37911
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
37912
|
+
import { join as join23 } from "path";
|
|
36919
37913
|
|
|
36920
37914
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
36921
37915
|
import { createHash as createHash6 } from "crypto";
|
|
36922
37916
|
import { hostname as hostname5 } from "os";
|
|
36923
37917
|
|
|
36924
|
-
// ../../packages/
|
|
36925
|
-
|
|
36926
|
-
|
|
36927
|
-
var
|
|
36928
|
-
var MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
36929
|
-
var RemoteRequestError = class extends Error {
|
|
36930
|
-
constructor(status) {
|
|
36931
|
-
super(`control-plane request failed with status ${String(status)}`);
|
|
36932
|
-
this.status = status;
|
|
36933
|
-
this.name = "RemoteRequestError";
|
|
36934
|
-
}
|
|
36935
|
-
status;
|
|
36936
|
-
};
|
|
36937
|
-
var RemoteRequestInvalid = class extends Error {
|
|
36938
|
-
constructor(route, cause) {
|
|
36939
|
-
super(`refusing to send a malformed body to ${route}`);
|
|
36940
|
-
this.cause = cause;
|
|
36941
|
-
this.name = "RemoteRequestInvalid";
|
|
36942
|
-
}
|
|
36943
|
-
cause;
|
|
36944
|
-
};
|
|
36945
|
-
var RemoteResponseInvalid = class extends Error {
|
|
36946
|
-
constructor(route, detail) {
|
|
36947
|
-
super(`control plane answered ${route} with ${detail}`);
|
|
36948
|
-
this.name = "RemoteResponseInvalid";
|
|
36949
|
-
}
|
|
36950
|
-
};
|
|
36951
|
-
var RemoteTransportError = class extends Error {
|
|
36952
|
-
/**
|
|
36953
|
-
* The status the peer sent, when headers arrived and only the BODY was
|
|
36954
|
-
* refused.
|
|
36955
|
-
*
|
|
36956
|
-
* Undefined for the ordinary case this class was written for — no answer at
|
|
36957
|
-
* all. It exists because two paths reject after a status has already been
|
|
36958
|
-
* delivered: an oversized body and an aborted response. Discarding it there
|
|
36959
|
-
* reported a deployment answering 401 with a verbose body as a network
|
|
36960
|
-
* outage, which sends the reader to look at their network instead of their
|
|
36961
|
-
* credential.
|
|
36962
|
-
*/
|
|
36963
|
-
constructor(reason, status) {
|
|
36964
|
-
super(`control-plane request did not complete: ${reason}`);
|
|
36965
|
-
this.status = status;
|
|
36966
|
-
this.name = "RemoteTransportError";
|
|
36967
|
-
}
|
|
36968
|
-
status;
|
|
36969
|
-
};
|
|
36970
|
-
async function send(options) {
|
|
36971
|
-
const url2 = new URL(options.url);
|
|
36972
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
36973
|
-
const send_ = url2.protocol === "http:" ? httpRequest : httpsRequest;
|
|
36974
|
-
const requestOptions = {
|
|
36975
|
-
method: options.method,
|
|
36976
|
-
headers: {
|
|
36977
|
-
// CALLER HEADERS FIRST, so this module's own are not overridable. Spread
|
|
36978
|
-
// last they win, and two of the values below are ones no caller may
|
|
36979
|
-
// replace: `x-api-key` is the credential, and `content-length` is the
|
|
36980
|
-
// byte count that stops a multi-byte body being truncated by the
|
|
36981
|
-
// receiver. `SendOptions.headers` is a free-form record on an exported
|
|
36982
|
-
// function, so "no caller does that today" is not the guarantee to rely
|
|
36983
|
-
// on. The one header any caller actually passes — `if-none-match` on the
|
|
36984
|
-
// conditional GET — is untouched by this order.
|
|
36985
|
-
...options.headers,
|
|
36986
|
-
// The credential. One header, matching what the deployment authenticates
|
|
36987
|
-
// on; a second copy in an `Authorization` header would be one more place
|
|
36988
|
-
// it can be logged by an intermediary for no gain.
|
|
36989
|
-
//
|
|
36990
|
-
// Spread conditionally rather than assigned as `undefined`: Node's header
|
|
36991
|
-
// handling and `content-length` bookkeeping treat a present-but-undefined
|
|
36992
|
-
// key differently from an absent one, and "the header is not there" is
|
|
36993
|
-
// the property the attach flow needs.
|
|
36994
|
-
...options.apiKey === void 0 ? {} : { "x-api-key": options.apiKey },
|
|
36995
|
-
accept: "application/json",
|
|
36996
|
-
...options.body === void 0 ? {} : {
|
|
36997
|
-
"content-type": "application/json",
|
|
36998
|
-
// Byte length, not string length: a multi-byte body sent with a
|
|
36999
|
-
// character count is truncated by the receiver.
|
|
37000
|
-
"content-length": String(Buffer.byteLength(options.body))
|
|
37001
|
-
}
|
|
37002
|
-
}
|
|
37003
|
-
};
|
|
37004
|
-
return new Promise((resolve2, reject) => {
|
|
37005
|
-
let settled = false;
|
|
37006
|
-
const fail = (reason, status) => {
|
|
37007
|
-
if (settled) return;
|
|
37008
|
-
settled = true;
|
|
37009
|
-
reject(new RemoteTransportError(reason, status));
|
|
37010
|
-
};
|
|
37011
|
-
const req = send_(url2, requestOptions, (res) => {
|
|
37012
|
-
const chunks = [];
|
|
37013
|
-
let size = 0;
|
|
37014
|
-
res.on("data", (chunk) => {
|
|
37015
|
-
size += chunk.length;
|
|
37016
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
37017
|
-
fail(`response exceeded ${String(MAX_RESPONSE_BYTES)} bytes`, res.statusCode);
|
|
37018
|
-
res.destroy();
|
|
37019
|
-
req.destroy();
|
|
37020
|
-
return;
|
|
37021
|
-
}
|
|
37022
|
-
chunks.push(chunk);
|
|
37023
|
-
});
|
|
37024
|
-
res.on("aborted", () => {
|
|
37025
|
-
fail("the response was aborted", res.statusCode);
|
|
37026
|
-
});
|
|
37027
|
-
res.on("end", () => {
|
|
37028
|
-
if (settled) return;
|
|
37029
|
-
settled = true;
|
|
37030
|
-
resolve2({
|
|
37031
|
-
status: res.statusCode ?? 0,
|
|
37032
|
-
headers: res.headers,
|
|
37033
|
-
body: Buffer.concat(chunks).toString("utf8")
|
|
37034
|
-
});
|
|
37035
|
-
});
|
|
37036
|
-
});
|
|
37037
|
-
const deadline = setTimeout(() => {
|
|
37038
|
-
fail(`no response within ${String(timeoutMs)}ms`);
|
|
37039
|
-
req.destroy();
|
|
37040
|
-
}, timeoutMs);
|
|
37041
|
-
deadline.unref();
|
|
37042
|
-
req.on("upgrade", (_res, socket) => {
|
|
37043
|
-
fail("the deployment answered with a protocol upgrade");
|
|
37044
|
-
socket.destroy();
|
|
37045
|
-
});
|
|
37046
|
-
req.on("close", () => {
|
|
37047
|
-
fail("the connection closed before a response was read");
|
|
37048
|
-
clearTimeout(deadline);
|
|
37049
|
-
});
|
|
37050
|
-
req.on("error", (err) => {
|
|
37051
|
-
fail(err.message);
|
|
37052
|
-
});
|
|
37053
|
-
if (options.body !== void 0) req.write(options.body);
|
|
37054
|
-
req.end();
|
|
37055
|
-
});
|
|
37056
|
-
}
|
|
37057
|
-
|
|
37058
|
-
// ../../packages/remote/src/client.ts
|
|
37059
|
-
var ROUTES = {
|
|
37060
|
-
events: "/v1/events",
|
|
37061
|
-
auditEvents: "/v1/audit-events",
|
|
37062
|
-
auditEventsBatch: "/v1/audit-events/batch",
|
|
37063
|
-
inventory: "/v1/inventory",
|
|
37064
|
-
storePosture: "/v1/store-posture",
|
|
37065
|
-
policyBundle: "/v1/policy-bundle",
|
|
37066
|
-
whoami: "/v1/plugin/whoami",
|
|
37067
|
-
shares: "/v1/shares"
|
|
37068
|
-
};
|
|
37069
|
-
function headerValue(response, name) {
|
|
37070
|
-
const raw = response.headers[name];
|
|
37071
|
-
if (raw === void 0) return void 0;
|
|
37072
|
-
return Array.isArray(raw) ? raw[0] : raw;
|
|
37073
|
-
}
|
|
37074
|
-
function okBody(response) {
|
|
37075
|
-
if (response.status < 200 || response.status >= 300) {
|
|
37076
|
-
throw new RemoteRequestError(response.status);
|
|
37077
|
-
}
|
|
37078
|
-
return response.body;
|
|
37079
|
-
}
|
|
37080
|
-
function parsed(schema, body, route) {
|
|
37081
|
-
let json2;
|
|
37082
|
-
try {
|
|
37083
|
-
json2 = JSON.parse(body);
|
|
37084
|
-
} catch {
|
|
37085
|
-
throw new RemoteResponseInvalid(route, "a body that is not JSON");
|
|
37086
|
-
}
|
|
37087
|
-
const result = schema.safeParse(json2);
|
|
37088
|
-
if (!result.success) {
|
|
37089
|
-
throw new RemoteResponseInvalid(route, "a body this client cannot read");
|
|
37090
|
-
}
|
|
37091
|
-
return result.data;
|
|
37092
|
-
}
|
|
37093
|
-
function withoutTrailingSlashes(endpoint) {
|
|
37094
|
-
let end = endpoint.length;
|
|
37095
|
-
while (end > 0 && endpoint.charCodeAt(end - 1) === SLASH) end -= 1;
|
|
37096
|
-
return endpoint.slice(0, end);
|
|
37097
|
-
}
|
|
37098
|
-
var SLASH = "/".charCodeAt(0);
|
|
37099
|
-
function createRemoteClient(options) {
|
|
37100
|
-
const base = withoutTrailingSlashes(options.endpoint);
|
|
37101
|
-
const url2 = (route) => `${base}${route}`;
|
|
37102
|
-
const common = { apiKey: options.apiKey, timeoutMs: options.timeoutMs };
|
|
37103
|
-
const sendOne = async (event) => {
|
|
37104
|
-
const validated = RecordAuditEventRequest.safeParse(event);
|
|
37105
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.auditEvents, validated.error);
|
|
37106
|
-
const response = await send({
|
|
37107
|
-
...common,
|
|
37108
|
-
method: "POST",
|
|
37109
|
-
url: url2(ROUTES.auditEvents),
|
|
37110
|
-
body: JSON.stringify(validated.data)
|
|
37111
|
-
});
|
|
37112
|
-
okBody(response);
|
|
37113
|
-
};
|
|
37114
|
-
return {
|
|
37115
|
-
async ingestEvents(batch) {
|
|
37116
|
-
const response = await send({
|
|
37117
|
-
...common,
|
|
37118
|
-
method: "POST",
|
|
37119
|
-
url: url2(ROUTES.events),
|
|
37120
|
-
body: JSON.stringify(batch)
|
|
37121
|
-
});
|
|
37122
|
-
return parsed(IngestAck, okBody(response), ROUTES.events);
|
|
37123
|
-
},
|
|
37124
|
-
async ingestInventory(context) {
|
|
37125
|
-
const response = await send({
|
|
37126
|
-
...common,
|
|
37127
|
-
method: "POST",
|
|
37128
|
-
url: url2(ROUTES.inventory),
|
|
37129
|
-
body: JSON.stringify(context)
|
|
37130
|
-
});
|
|
37131
|
-
return parsed(ResolvedInventory, okBody(response), ROUTES.inventory);
|
|
37132
|
-
},
|
|
37133
|
-
async recordAuditEvent(event) {
|
|
37134
|
-
await sendOne(event);
|
|
37135
|
-
},
|
|
37136
|
-
async recordAuditEvents(events) {
|
|
37137
|
-
const validated = RecordAuditEventBatch.safeParse({ events });
|
|
37138
|
-
if (!validated.success) {
|
|
37139
|
-
throw new RemoteRequestInvalid(ROUTES.auditEventsBatch, validated.error);
|
|
37140
|
-
}
|
|
37141
|
-
const response = await send({
|
|
37142
|
-
...common,
|
|
37143
|
-
method: "POST",
|
|
37144
|
-
url: url2(ROUTES.auditEventsBatch),
|
|
37145
|
-
body: JSON.stringify(validated.data)
|
|
37146
|
-
});
|
|
37147
|
-
if (response.status === 404) {
|
|
37148
|
-
for (const event of validated.data.events) await sendOne(event);
|
|
37149
|
-
return { accepted: validated.data.events.length };
|
|
37150
|
-
}
|
|
37151
|
-
return parsed(AuditEventBatchAck, okBody(response), ROUTES.auditEventsBatch);
|
|
37152
|
-
},
|
|
37153
|
-
async reportStorePosture(snapshot) {
|
|
37154
|
-
const response = await send({
|
|
37155
|
-
...common,
|
|
37156
|
-
method: "POST",
|
|
37157
|
-
url: url2(ROUTES.storePosture),
|
|
37158
|
-
body: JSON.stringify(snapshot)
|
|
37159
|
-
});
|
|
37160
|
-
okBody(response);
|
|
37161
|
-
},
|
|
37162
|
-
async getPolicyBundle(etag) {
|
|
37163
|
-
const response = await send({
|
|
37164
|
-
...common,
|
|
37165
|
-
method: "GET",
|
|
37166
|
-
url: url2(ROUTES.policyBundle),
|
|
37167
|
-
...etag === void 0 ? {} : { headers: { "if-none-match": etag } }
|
|
37168
|
-
});
|
|
37169
|
-
if (response.status === 304) {
|
|
37170
|
-
return { changed: false, etag: headerValue(response, "etag") ?? etag };
|
|
37171
|
-
}
|
|
37172
|
-
const bundle = parsed(PolicyBundle, okBody(response), ROUTES.policyBundle);
|
|
37173
|
-
return { changed: true, bundle, etag: headerValue(response, "etag") };
|
|
37174
|
-
},
|
|
37175
|
-
async whoami() {
|
|
37176
|
-
const response = await send({ ...common, method: "GET", url: url2(ROUTES.whoami) });
|
|
37177
|
-
return parsed(PluginWhoami, okBody(response), ROUTES.whoami);
|
|
37178
|
-
},
|
|
37179
|
-
async recordProjectEgress(request) {
|
|
37180
|
-
const validated = EgressIngestRequest.safeParse(request);
|
|
37181
|
-
if (!validated.success) throw new RemoteRequestInvalid(ROUTES.shares, validated.error);
|
|
37182
|
-
const response = await send({
|
|
37183
|
-
...common,
|
|
37184
|
-
method: "POST",
|
|
37185
|
-
url: url2(ROUTES.shares),
|
|
37186
|
-
body: JSON.stringify(validated.data)
|
|
37187
|
-
});
|
|
37188
|
-
okBody(response);
|
|
37189
|
-
}
|
|
37190
|
-
};
|
|
37191
|
-
}
|
|
37918
|
+
// ../../packages/plugin-runtime/src/attached/capture-rebuild.ts
|
|
37919
|
+
var CORRELATION_ID = EventMetadata.shape.correlationId;
|
|
37920
|
+
var TRACE_ID = EventMetadata.shape.traceId;
|
|
37921
|
+
var EXCEPTION_ID = EventMetadata.shape.exceptionIds.unwrap().element;
|
|
37192
37922
|
|
|
37193
37923
|
// ../../packages/plugin-runtime/src/attached/history-sync-trigger.ts
|
|
37194
37924
|
import { spawn } from "child_process";
|
|
@@ -37220,14 +37950,14 @@ function spawnDetached(scriptPath) {
|
|
|
37220
37950
|
}
|
|
37221
37951
|
|
|
37222
37952
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
37223
|
-
import { readFileSync as
|
|
37953
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
37224
37954
|
var manifestBuildCache = /* @__PURE__ */ new Map();
|
|
37225
37955
|
function readManifestBuild(manifestUrl, packageName) {
|
|
37226
37956
|
const key = manifestUrl.href;
|
|
37227
37957
|
if (!manifestBuildCache.has(key)) {
|
|
37228
37958
|
let build;
|
|
37229
37959
|
try {
|
|
37230
|
-
const manifest = JSON.parse(
|
|
37960
|
+
const manifest = JSON.parse(readFileSync16(manifestUrl, "utf8"));
|
|
37231
37961
|
build = typeof manifest.version === "string" && manifest.version.length > 0 ? { package: packageName, version: manifest.version } : void 0;
|
|
37232
37962
|
} catch {
|
|
37233
37963
|
build = void 0;
|
|
@@ -37254,7 +37984,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
37254
37984
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
37255
37985
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
37256
37986
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
37257
|
-
import { join as
|
|
37987
|
+
import { join as join24 } from "path";
|
|
37258
37988
|
|
|
37259
37989
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
37260
37990
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -37278,7 +38008,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
37278
38008
|
|
|
37279
38009
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
37280
38010
|
function createPolicyStore(dir = dataDir()) {
|
|
37281
|
-
const file2 =
|
|
38011
|
+
const file2 = join24(dir, "policy-cache.json");
|
|
37282
38012
|
async function read() {
|
|
37283
38013
|
try {
|
|
37284
38014
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -37287,22 +38017,32 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
37287
38017
|
const record2 = parsed2;
|
|
37288
38018
|
const bundle = PolicyBundle.parse(record2.bundle);
|
|
37289
38019
|
const fetchedAtMs = typeof record2.fetchedAtMs === "number" ? record2.fetchedAtMs : 0;
|
|
37290
|
-
const
|
|
38020
|
+
const stored = typeof record2.etag === "string" ? record2.etag : void 0;
|
|
38021
|
+
const replayable = record2.shapeId === POLICY_BUNDLE_SHAPE_ID || knowsMoreThanThisBuild(record2.shapeId);
|
|
38022
|
+
const etag = replayable ? stored : void 0;
|
|
37291
38023
|
return { bundle, fetchedAtMs, ...etag === void 0 ? {} : { etag } };
|
|
37292
38024
|
} catch {
|
|
37293
38025
|
return null;
|
|
37294
38026
|
}
|
|
37295
38027
|
}
|
|
37296
|
-
|
|
37297
|
-
|
|
37298
|
-
const
|
|
37299
|
-
|
|
37300
|
-
|
|
37301
|
-
|
|
37302
|
-
|
|
38028
|
+
function knowsMoreThanThisBuild(shapeId) {
|
|
38029
|
+
if (typeof shapeId !== "string" || shapeId === "") return false;
|
|
38030
|
+
const theirs = new Set(shapeId.split(","));
|
|
38031
|
+
const ours = new Set(POLICY_BUNDLE_SHAPE_ID.split(","));
|
|
38032
|
+
return theirs.size > ours.size && [...ours].every((key) => theirs.has(key));
|
|
38033
|
+
}
|
|
38034
|
+
async function priorRecord() {
|
|
38035
|
+
try {
|
|
38036
|
+
const parsed2 = JSON.parse(await readFile2(file2, "utf8"));
|
|
38037
|
+
return typeof parsed2 === "object" && parsed2 !== null ? parsed2 : null;
|
|
38038
|
+
} catch {
|
|
38039
|
+
return null;
|
|
38040
|
+
}
|
|
38041
|
+
}
|
|
38042
|
+
async function publishRecord(record2) {
|
|
37303
38043
|
const tmp = `${file2}.${randomUUID16()}.tmp`;
|
|
37304
38044
|
try {
|
|
37305
|
-
await writeFile2(tmp, JSON.stringify(
|
|
38045
|
+
await writeFile2(tmp, JSON.stringify(record2), {
|
|
37306
38046
|
encoding: "utf8",
|
|
37307
38047
|
mode: DATA_FILE_MODE,
|
|
37308
38048
|
flag: "wx"
|
|
@@ -37313,6 +38053,27 @@ function createPolicyStore(dir = dataDir()) {
|
|
|
37313
38053
|
throw err;
|
|
37314
38054
|
}
|
|
37315
38055
|
}
|
|
38056
|
+
async function write(bundle, etag) {
|
|
38057
|
+
await ensureDataDir(dir);
|
|
38058
|
+
const prior = await priorRecord();
|
|
38059
|
+
const priorVersion = prior?.bundle?.version;
|
|
38060
|
+
if (prior !== null && knowsMoreThanThisBuild(prior.shapeId) && priorVersion === bundle.version) {
|
|
38061
|
+
await publishRecord({
|
|
38062
|
+
...prior,
|
|
38063
|
+
fetchedAtMs: Date.now()
|
|
38064
|
+
});
|
|
38065
|
+
return;
|
|
38066
|
+
}
|
|
38067
|
+
await publishRecord({
|
|
38068
|
+
bundle,
|
|
38069
|
+
fetchedAtMs: Date.now(),
|
|
38070
|
+
// Stamped on EVERY write, the 304 arm's included: that arm hands back the
|
|
38071
|
+
// bundle it already holds, and the point of the stamp is to describe the
|
|
38072
|
+
// build that last narrowed those bytes, which is this one.
|
|
38073
|
+
shapeId: POLICY_BUNDLE_SHAPE_ID,
|
|
38074
|
+
...etag === void 0 ? {} : { etag }
|
|
38075
|
+
});
|
|
38076
|
+
}
|
|
37316
38077
|
return { read, write, file: file2 };
|
|
37317
38078
|
}
|
|
37318
38079
|
|
|
@@ -37478,11 +38239,11 @@ function readStorePosture(dbPath2) {
|
|
|
37478
38239
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
37479
38240
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
37480
38241
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
37481
|
-
import { join as
|
|
38242
|
+
import { join as join25 } from "path";
|
|
37482
38243
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
37483
38244
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
37484
|
-
const file2 =
|
|
37485
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
38245
|
+
const file2 = join25(dir, "posture-state.json");
|
|
38246
|
+
const legacyFile = legacyDir === void 0 ? null : join25(legacyDir, "posture-state.json");
|
|
37486
38247
|
async function persist(state) {
|
|
37487
38248
|
await ensureDataDir(dir);
|
|
37488
38249
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -37550,8 +38311,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
37550
38311
|
}
|
|
37551
38312
|
|
|
37552
38313
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
37553
|
-
import { readFileSync as
|
|
37554
|
-
import { join as
|
|
38314
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
38315
|
+
import { join as join26 } from "path";
|
|
37555
38316
|
|
|
37556
38317
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
37557
38318
|
var REFUSAL_LINES = {
|
|
@@ -37893,9 +38654,21 @@ var StandaloneDataGateway = class {
|
|
|
37893
38654
|
// for the whole of it, so a member that threw would make that answer a lie
|
|
37894
38655
|
// the moment a composite delegated to it. A store-level no-op is the honest
|
|
37895
38656
|
// shape — a standalone machine has nothing delivered to record.
|
|
38657
|
+
markCaptureOwed(event) {
|
|
38658
|
+
this.db.markCaptureOwed(event);
|
|
38659
|
+
}
|
|
37896
38660
|
markCaptureDelivered(event, atMs) {
|
|
37897
38661
|
this.db.markCaptureDelivered(event, atMs);
|
|
37898
38662
|
}
|
|
38663
|
+
// Implemented, not stubbed, for the same reason its sibling above is: the
|
|
38664
|
+
// attached gateway is a DECORATOR over an instance of this class
|
|
38665
|
+
// (`attached/factory.ts` builds one and passes it as `deps.local`), so every
|
|
38666
|
+
// stamp the live forward makes lands here with a non-empty array. This is the
|
|
38667
|
+
// production write path for that feature, not a shape-satisfying no-op — a
|
|
38668
|
+
// machine that is merely standalone simply never calls it.
|
|
38669
|
+
markAuditEventsDelivered(events, atMs) {
|
|
38670
|
+
this.db.markAuditEventsDelivered(events, atMs);
|
|
38671
|
+
}
|
|
37899
38672
|
staleBinaryNotice(currentVersion) {
|
|
37900
38673
|
try {
|
|
37901
38674
|
const newest = this.db.installedPacks.newestRecordedBinary();
|
|
@@ -38179,7 +38952,7 @@ function pluginBuild() {
|
|
|
38179
38952
|
|
|
38180
38953
|
// src/history/reconcile-trigger.ts
|
|
38181
38954
|
import { spawn as spawn3 } from "child_process";
|
|
38182
|
-
import { dirname as
|
|
38955
|
+
import { dirname as dirname6, join as join28 } from "path";
|
|
38183
38956
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
38184
38957
|
|
|
38185
38958
|
// src/history/tail.ts
|
|
@@ -38189,11 +38962,11 @@ import {
|
|
|
38189
38962
|
fstatSync as fstatSync2,
|
|
38190
38963
|
mkdirSync as mkdirSync5,
|
|
38191
38964
|
openSync as openSync3,
|
|
38192
|
-
readFileSync as
|
|
38965
|
+
readFileSync as readFileSync18,
|
|
38193
38966
|
readSync as readSync2,
|
|
38194
38967
|
writeFileSync as writeFileSync8
|
|
38195
38968
|
} from "fs";
|
|
38196
|
-
import { join as
|
|
38969
|
+
import { join as join27 } from "path";
|
|
38197
38970
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]+$/;
|
|
38198
38971
|
function safeSessionId(sessionId) {
|
|
38199
38972
|
if (SAFE_SESSION_ID.test(sessionId) && sessionId !== "." && sessionId !== "..") {
|
|
@@ -38209,8 +38982,8 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
|
|
|
38209
38982
|
try {
|
|
38210
38983
|
const marker = `${RECONCILE_MARKER_PREFIX}-${safeSessionId(sessionId)}`;
|
|
38211
38984
|
if (throttled(dataDir2, marker, RECONCILE_THROTTLE_MS)) return;
|
|
38212
|
-
const here =
|
|
38213
|
-
const child = spawn3(process.execPath, [
|
|
38985
|
+
const here = dirname6(fileURLToPath4(import.meta.url));
|
|
38986
|
+
const child = spawn3(process.execPath, [join28(here, "reconcile.js"), sessionId, transcriptPath], {
|
|
38214
38987
|
detached: true,
|
|
38215
38988
|
stdio: "ignore"
|
|
38216
38989
|
});
|
|
@@ -38221,17 +38994,17 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
|
|
|
38221
38994
|
|
|
38222
38995
|
// src/protocol/marker.ts
|
|
38223
38996
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
38224
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
38225
|
-
import { join as
|
|
38997
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync19, renameSync as renameSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
38998
|
+
import { join as join29 } from "path";
|
|
38226
38999
|
var MARKER_FILE = "protocol-marker";
|
|
38227
39000
|
function mintMarker() {
|
|
38228
39001
|
return randomBytes4(8).toString("hex");
|
|
38229
39002
|
}
|
|
38230
39003
|
function sessionProtocolMarker(dataDir2, sessionId) {
|
|
38231
39004
|
if (!sessionId) return mintMarker();
|
|
38232
|
-
const path =
|
|
39005
|
+
const path = join29(dataDir2, MARKER_FILE);
|
|
38233
39006
|
try {
|
|
38234
|
-
const stored = JSON.parse(
|
|
39007
|
+
const stored = JSON.parse(readFileSync19(path, "utf8"));
|
|
38235
39008
|
if (stored.sessionId === sessionId && typeof stored.marker === "string" && /^[0-9a-f]{16}$/.test(stored.marker)) {
|
|
38236
39009
|
return stored.marker;
|
|
38237
39010
|
}
|
|
@@ -38240,7 +39013,7 @@ function sessionProtocolMarker(dataDir2, sessionId) {
|
|
|
38240
39013
|
const marker = mintMarker();
|
|
38241
39014
|
try {
|
|
38242
39015
|
mkdirSync6(dataDir2, { recursive: true, mode: DATA_DIR_MODE });
|
|
38243
|
-
const tmp =
|
|
39016
|
+
const tmp = join29(dataDir2, `${MARKER_FILE}.tmp`);
|
|
38244
39017
|
writeFileSync9(tmp, JSON.stringify({ sessionId, marker }), { mode: DATA_FILE_MODE });
|
|
38245
39018
|
renameSync5(tmp, path);
|
|
38246
39019
|
} catch {
|
|
@@ -38303,16 +39076,16 @@ function emit(output2) {
|
|
|
38303
39076
|
}
|
|
38304
39077
|
|
|
38305
39078
|
// src/hooks/store-health.ts
|
|
38306
|
-
import { mkdirSync as mkdirSync7, readFileSync as
|
|
38307
|
-
import { dirname as
|
|
39079
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
|
|
39080
|
+
import { dirname as dirname7, join as join30 } from "path";
|
|
38308
39081
|
var STORE_REDIRECT_MARKER = "store-redirect-last-session";
|
|
38309
39082
|
function markerDirs(dataDir2) {
|
|
38310
|
-
return [dataDir2,
|
|
39083
|
+
return [dataDir2, dirname7(dataDir2)];
|
|
38311
39084
|
}
|
|
38312
39085
|
function alreadyClaimed(dirs, marker, sessionId) {
|
|
38313
39086
|
return dirs.some((dir) => {
|
|
38314
39087
|
try {
|
|
38315
|
-
return
|
|
39088
|
+
return readFileSync20(join30(dir, marker), "utf8") === sessionId;
|
|
38316
39089
|
} catch {
|
|
38317
39090
|
return false;
|
|
38318
39091
|
}
|
|
@@ -38322,7 +39095,7 @@ function recordClaim(dirs, marker, sessionId) {
|
|
|
38322
39095
|
for (const dir of dirs) {
|
|
38323
39096
|
try {
|
|
38324
39097
|
mkdirSync7(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
38325
|
-
writeFileSync10(
|
|
39098
|
+
writeFileSync10(join30(dir, marker), sessionId, { mode: DATA_FILE_MODE });
|
|
38326
39099
|
return;
|
|
38327
39100
|
} catch {
|
|
38328
39101
|
}
|
|
@@ -38347,7 +39120,7 @@ function formatMode(mode) {
|
|
|
38347
39120
|
}
|
|
38348
39121
|
function warnIfStoreRedirected(config2, sessionId, write = (message2) => void process.stderr.write(message2)) {
|
|
38349
39122
|
try {
|
|
38350
|
-
const paths = symlinkedStorePaths(
|
|
39123
|
+
const paths = symlinkedStorePaths(dirname7(config2.dataDir));
|
|
38351
39124
|
if (paths.length === 0) return;
|
|
38352
39125
|
if (!sessionId) {
|
|
38353
39126
|
write(storeRedirectedMessage(paths));
|
|
@@ -38366,7 +39139,7 @@ function harnessVersion() {
|
|
|
38366
39139
|
const manifestPath = process.argv[2];
|
|
38367
39140
|
if (!manifestPath) return void 0;
|
|
38368
39141
|
try {
|
|
38369
|
-
const manifest = JSON.parse(
|
|
39142
|
+
const manifest = JSON.parse(readFileSync21(manifestPath, "utf8"));
|
|
38370
39143
|
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
38371
39144
|
} catch {
|
|
38372
39145
|
return void 0;
|